Skip to content

Files

Latest commit

5479b33 · Jun 18, 2024

History

History
10280 lines (9307 loc) · 337 KB

Manual.org

File metadata and controls

10280 lines (9307 loc) · 337 KB

Ansible Playbooks for yaVDR 0.7

Table of Contents

User Stories

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

Notes

[#A] optimize and document data structure for xorg parsing script

[#A] xorg.conf templates for intel, nvidia, noveau and radeon drivers

BusID für (nvidia) Grafikkarten aus lspci auslesen

  • State “DONE” from “STARTED” [2019-03-20 Mi 19:35]
Format: “%d:%d:%d” (lspci liefert es als “%04x:%02x:%02x.%x”, domain, bus, device, function)
  • Wie mit mehreren GPUs umgehen? Priorität bzw. Reihenfolge?
  • Was ist mit NVidia Optimus/Bumblebee (AFAIK keine VDPAU-Funktionalität)?
  • Wie BusID der xrandr-Ausgabe zuordnen?

NOTE NVIDIA-Documentation about the BusID:

What is the format of a PCI Bus ID?

Different tools have different formats for the PCI Bus ID of a PCI device.

The X server’s “BusID” X configuration file option interprets the BusID string in the format “bus@domain:device:function” (the “@domain” portion is only needed if the PCI domain is non-zero), in decimal. More specifically,

“%d@%d:%d:%d”, bus, domain, device, function in printf(3) syntax. NVIDIA X driver logging, nvidia-xconfig, and nvidia-settings match the X configuration file BusID convention.

The lspci(8) utility, in contrast, reports the PCI BusID of a PCI device in the format “domain:bus:device.function”, printing the values in hexadecimal. More specifically,

“%04x:%02x:%02x.%x”, domain, bus, device, function in printf(3) syntax. The “Bus Location” reported in the /proc/driver/nvidia/gpus/0..N/information files matches the lspci format.

Parsen der /proc/driver/nvidia/gpus/*/information Dateien

# read the BusID for nvidia cards and the model name
# from the /proc/driver/nvidia/gpus/*/information file(s)
from __future__ import print_function
import glob
import re

BusID_RE = re.compile((
    '(?P<domain>[0-9a-fA-F]+)'
    ':'
    '(?P<bus>[0-9a-fA-F]+)'
    ':'
    '(?P<device>[0-9a-fA-F]+)'
    '\.'
    '(?P<function>[0-9a-fA-F]+)'
))
Model_RE = re.compile('Model:\s+(.*)')

def get_BusIDs():
    for gpu_info in glob.glob('/proc/driver/nvidia/gpus/*/information'):
        with open(gpu_info) as f:
            data = f.read()
        match = BusID_RE.search(data)
        if match:
            BusID = "{:d}@{:d}:{:d}:{:d}".format(*(int(n, 16) for n in match.groups()))
            yield BusID, Model_RE.match(data).groups()[0]
if __name__ == '__main__':
    BusIDs = [BusID for BusID in get_BusIDs()]
    print(BusIDs)

[#B] plan for customization of xorg settings by the user

either directly or using a configuration wizard or a web frontend

SOMEDAY [#C] select best frontend based on GPU

intel
softhddevice or vaapidevice
amd
softhddevice-vpp (?)
nvidia
softhddevice (TODO: Check what is necessary to get CUVID support)

Document using vdr, pulseaudio and xorg together

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.

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:

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

. scripts/install-packages.sh

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

. scripts/install-packages.sh

# speed up playbook execution
export ANSIBLE_PIPELINING=1

ansible-playbook yavdr07-headless.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

. scripts/install-packages.sh

# speed up playbook execution
export ANSIBLE_PIPELINING=1

ansible-playbook yavdr07-arm64.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

. scripts/install-packages.sh

# speed up playbook execution
export ANSIBLE_PIPELINING=1

ansible-playbook yavdr07-rpi.yml -b -i 'localhost_inventory' --connection=local --tags="all"

Playbooks

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
  environment:
    LANG: "{{ default_locale | default('c.UTF-8') }}"

  pre_tasks:
    - name: update all packages if the action doesn't happen locally
      apt:
        name: "*"
        state: latest
        update_cache: true
      when: (ansible_host | default(inventory_hostname)) not in ['localhost', '127.0.0.1']

  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)
    - vdr                        # install vdr and related packages
    - vdr-plugin-menuorg         # install vdr-plugin-menuorg and customize menuorg.xml
    - autoinstall-virtualbox-guest
    - lifeguardng                # install and configure vdr-addon-lifeguard-ng
    - 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
    - osd2web                    # configure osd2web to run on the second display if there is any
    - 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-imonvfd        # install vdr-plugin-imonvfd 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-dvbhddevice    # install vdr-plugin-dvbsddevice if a matching card is detected - NOTE: you need to compile and install the drivers yourself
    - 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
    - install-dvb-firmware       # download and install all firmware files from the LibreElec dvb-firmware git repository
    # - autoinstall-dvbsky-firmware # download and install required firmware files for dvbsky cards
    - autoinstall-firmware       # download and install firmware files for dvb devices
    #- install-sundtek            # install and configure sundtek drivers
    #- serial-ir                  # configure a serial port for "homebrew" ir receivers (e.g. Atric V5)
    #- epgd                       # install vdr-epg-daemon (and vdr-plugin-epg2vdr)
    #- channelpedia               # upload channels.conf to channelpedia (see role description in Manual)
    - channellogos               # use channellogos provided by https://github.com/Jasmeet181/mediaportal-*-logos
                                 # set the variable channellogos_languages to a list of the langues you want (see group_vars/all)
                                  # and link them to /var/lib/channellogos/ - this needs at least 300 MB of storage
    - 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
    - set-output-plugin  # ensures that only one output plugin is active (a manual override is possible by setting selected_frontend to the name of the wanted output plugin)

  handlers:
    - import_tasks: handlers/main.yml

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
  environment:
    LANG: "{{ default_locale | default('c.UTF-8') }}"
  roles:
    - yavdr-common
    - collect-facts              # query system facts
    - vdr
    - lifeguardng                # install and configure vdr-addon-lifeguard-ng
    - headless-session           # set up a headless systemd user session, e.g. for udiskie
    - yavdr-network
    - samba-install
    - samba-config
    - nfs-server
    - grub-config
    - autoinstall-dvbsddevice    # install vdr-plugin-dvbsddevice if a matching card is detected
    - install-dvb-firmware       # download and install all firmware files from the LibreElec dvb-firmware git repository
    # - autoinstall-dvbsky-firmware # download and install required firmware files for dvbsky cards
    - autoinstall-firmware       # download and install firmware files for dvb devices
    - autoinstall-hauppauge-pvr  # install vdr-plugin-pvrinput if a matching card is found
    - autoinstall-pvr350         # install vdr-plugin-pvr350 if a matching card is detected
    - autoinstall-satip          # install vdr-plugin-satip if a Sat>IP server has been found
    #- epgd                      # install vdr-epg-daemon
    #- channelpedia               # upload channels.conf to channelpedia (see role description in Manual)
    - channellogos               # use channellogos provided by https://github.com/Jasmeet181/mediaportal-*-logos
    - wakeup
  tags:
    - always
  handlers:
    - import_tasks: handlers/main.yml

yavdr07-arm64.yml

VDR-only setup for Raspberry Pi 4 and 5 on 64 bit installation. Please note that kodi is not available at the moment.

---
# file: yavdr07-arm64.yml
# this playbook set up a yaVDR 0.7 installation on a Raspberry Pi 4 or 5
# please note that kodi is not available at the moment

- name: set up a headless yaVDR server
  hosts: all
  become: true
  environment:
    LANG: "{{ default_locale | default('c.UTF-8') }}"
  roles:
    - yavdr-common
    - collect-facts              # query system facts
    - vdr
    - arm64-output                        # install rpihddevice and let vdr run on tty7
    - lifeguardng                # install and configure vdr-addon-lifeguard-ng
    - headless-session           # set up a headless systemd user session, e.g. for udiskie
    - yavdr-remote
    - yavdr-network
    #- samba-install
    #- samba-config
    #- nfs-server
    - autoinstall-atric-usb      # preconfigure lircd for Atric IR-WakeupUSB receiver
    - autoinstall-dvbsddevice    # install vdr-plugin-dvbsddevice if a matching card is detected
    - install-dvb-firmware       # download and install all firmware files from the LibreElec dvb-firmware git repository
    # - autoinstall-dvbsky-firmware # download and install required firmware files for dvbsky cards
    - autoinstall-firmware       # download and install firmware files for dvb devices
    - autoinstall-hardware-irmp  # install yavdr-hardware-irmp if a matching usb device is detected
    - autoinstall-hauppauge-pvr  # install vdr-plugin-pvrinput if a matching card is found
    - autoinstall-imonlcd        # install vdr-plugin-imonlcd if a matchind display is connected
    - autoinstall-imonvfd        # install vdr-plugin-imonvfd if a matchind display is connected
    - 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-yausbir        # preconfigure lircd for yaUsbIR receiver
    - set-output-plugin  # ensures that only one output plugin is active (a manual override is possible by setting selected_frontend to the name of the wanted output plugin)
    #- epgd                      # install vdr-epg-daemon
    #- channelpedia               # upload channels.conf to channelpedia (see role description in Manual)
    #- wakeup
    #- channellogos               # use channellogos provided by https://github.com/Jasmeet181/mediaportal-*-logos
                                 # set the variable channellogos_languages to a list of the langues you want (see group_vars/all)
                                  # and link them to /var/lib/channellogos/ - this needs at least 300 MB of storage
  tags:
    - always
  handlers:
    - import_tasks: handlers/main.yml

yavdr07-rpi.yml

VDR-only setup for Raspberry Pi 2 and 3. At least 1 GB of RAM is recommended. Please note that kodi is not available at the moment.

---
# file: yavdr07-rpi.yml
# this playbook set up a yaVDR 0.7 installation on a Raspberry Pi 2 or 3
# please note that kodi is not available at the moment

- name: set up a headless yaVDR server
  hosts: all
  become: true
  environment:
    LANG: "{{ default_locale | default('c.UTF-8') }}"
  roles:
    - yavdr-common
    - collect-facts              # query system facts
    - vdr
    - rpi                        # install rpihddevice and let vdr run on tty7
    - lifeguardng                # install and configure vdr-addon-lifeguard-ng
    - headless-session           # set up a headless systemd user session, e.g. for udiskie
    - yavdr-remote
    - yavdr-network
    #- samba-install
    #- samba-config
    #- nfs-server
    - autoinstall-atric-usb      # preconfigure lircd for Atric IR-WakeupUSB receiver
    - autoinstall-dvbsddevice    # install vdr-plugin-dvbsddevice if a matching card is detected
    - install-dvb-firmware       # download and install all firmware files from the LibreElec dvb-firmware git repository
    # - autoinstall-dvbsky-firmware # download and install required firmware files for dvbsky cards
    - autoinstall-firmware       # download and install firmware files for dvb devices
    - autoinstall-hardware-irmp  # install yavdr-hardware-irmp if a matching usb device is detected
    - autoinstall-hauppauge-pvr  # install vdr-plugin-pvrinput if a matching card is found
    - autoinstall-imonlcd        # install vdr-plugin-imonlcd if a matchind display is connected
    - autoinstall-imonvfd        # install vdr-plugin-imonvfd if a matchind display is connected
    - 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-yausbir        # preconfigure lircd for yaUsbIR receiver
    - set-output-plugin  # ensures that only one output plugin is active (a manual override is possible by setting selected_frontend to the name of the wanted output plugin)
    #- epgd                      # install vdr-epg-daemon
    #- channelpedia               # upload channels.conf to channelpedia (see role description in Manual)
    #- wakeup
    #- channellogos               # use channellogos provided by https://github.com/Jasmeet181/mediaportal-*-logos
                                 # set the variable channellogos_languages to a list of the langues you want (see group_vars/all)
                                  # and link them to /var/lib/channellogos/ - this needs at least 300 MB of storage
  tags:
    - always
  handlers:
    - import_tasks: handlers/main.yml

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 ansible_connection=local

Group Variables

PPAs

---
# file: group_vars/all
# Ansible configuration, overwrite if you want to use the playbook(s) remotely
# e.g. in a file `host_vars/my_yavdr_host`
ansible_host: localhost
ansible_connection: local

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

# set to True to add the repository ppa:/kelebek333/nvidia-legacy for nvidia-legacy drivers (340, 390) and disable the ABI version check by the X-Server to use those old drivers
nvidia_legacy: False

Locales and Timezone

timezone: 'Europe/Berlin'
# timezone_hw can be set to 'UTC' or 'local' to set the preferred hardware clock
# timezone. This variable is optional, the default behavior is to let systemd
# set the hw clock to UTC # and sync it with the time aquired via NTP.
# Please note that changing the hw clock is usually not supported within virtual
# environments and systemd-timesyncd won't sync the current time to the hardware
# clock if it is set to local time.
# timezone_hw: 'UTC'  # or 'local'
default_locale: 'de_DE.UTF-8'
generate_locales:  # see entries in /etc/locale.gen
  - 'de_DE.UTF-8'
  - 'en_US.UTF-8'
language_packs:
  - language-pack-de
  - language-pack-en

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: /home/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: ""  # set the desired charset, e.g. "ISO-8859-9"
  instance_id: 0  # set the vdr instance id (see parameter "-i" in man 1 vdr)

# wait for dvb devices by adapter number
# NOTE: This only works for devices that are discoverable by udev, not userspace drivers like sundtek tuners!
# e.g. wait_for_dvb_devices: [0, 1] to wait for /dev/dvb/adapter0 and /dev/dvb/adapter1
wait_for_dvb_devices: []

# this allows to set up streamdev-client - set the server, port and number of devices
# streamdev_client_remote_ip: "192.168.0.104"
# streamdev_client_remote_port: 2004
# streamdev_client_num_provided_systems: 1

# 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-ng

# set the name of the output plugin (as used by vdrctl) - this defaults to softhddevice on x64 platforms and rpihddevice on Raspberry Pi 2 and 3
# selected_frontend: softhddevice

# set the package name of the output plugin - this defaults to vdr-plugin-softhddevice on x64 platforms and vdr-plugin-rpihddevice on Raspberry Pi 2 and 3
# vdr_output_plugin: vdr-plugin-softhddevice-cuvid

# 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

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 }}'

NFS

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

Samba

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

KODI

# install kodi as flatpak instead of a debian package
kodi_as_flatpak: False

Additional packages

# additional packages you want to install
extra_packages:
  - htop
  - tree
  - vim
  - w-scan
  - t2scan
  - vdrpbd

Channellogos

# choose which channellogos to download from the github.com/Jasmeet181 mediaportal-*-logos repositories
# currently suported langugages/regions are: au, be, cz, de, es, ie, il, it, nordic, nz, ru, uk, us
#
#channellogo_languages:
#  - au
#  - be
#  - cz
#  - de
#  - es
#  - ie
#  - il
#  - it
#  - nordic
#  - nz
#  - ru
#  - uk
#  - us

System pre-configuration

frontend: vdr

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

# to force quadratic pixels set this variable to the dpi you want
# nvidia_force_dpi: 96

#system:
#  shutdown: poweroff

# choose one of acpiwakeup, stm32wakeup
wakeup_method: acpiwakeup

# start given number of minutes earlier than the wakeuptime set by vdr
wakeup_start_ahead: 5

# set days of the week for automatic wakeup (1=Monday...7=Sunday),
# set empty string to disable wakeup
# eg. to wake up on Monday and Friday:
# wakeup_days: "1 5"
wakeup_days: ""

# set wakeup time for automatic wakeup
# set empty string to disable
# e.g. for wakeup at midnight:
# wakeup_time: "00:00"
wakeup_time: ""

# this will set the primary display as connected and force KMS to load
# the EDID saved when the role yavdr-xorg detects the connected displays
intel_set_boot_edid: false

# settings for grub
grub:
  timeout: 0
  boot_options: quiet splash

# unload dvb drivers before standby, reload them afterwards.
# disable this setting if
# - you don't have local dvb devices (e.g. satip client)
# - your dvb drivers have problems being unloaded
standby_reload_dvb: true

# settings for vdr-addon-lifeguard-ng
# the following settings prevent the shutdown
# if a nfs, samba or ssh connection to this machine is open
lifeguard_enable_nfs: True
lifeguard_enable_samba: True
lifeguard_enable_ssh: True
# list of ip addresses or hostnames which should prevent shutdown
# if the can be pinged
lifeguard_hosts: []
# list of users which sould prevent shutdown if they are logged in
# this is somewhat redudant because systemd check this, too
lifeguard_users: []
# check for local processes
lifeguard_processes: []
# check for active tcp connections of local processes
# syntax: <processname> [:] <port>
lifeguard_tcp:
  - vdr 3000  # don't shutdown if there is an active streamdev connection

Serial IR

# Serial device to configure for a homebrew receiver.
# Choose one of the devices predefined in the variable serial_ir_data in roles/serial-ir/defaults/main.yml
# ttyS0 (COM1), ttyS1 (COM2), ttyS2(COM3) or ttyS3 (COM4)
# or customize the variable as needed
#
serial_ir_device: ttyS0

RPI specific variables

# Key for the MPEG2-Decoder
#rpi_decode_mpg2: 0xdeadbeef

# Key for the wvc1-Decoder
#rpi_decode_wvc1: 0xdeadbeef

# Pin for the GPIO remote receiver
# rpi_ir_gpio_pin: 24

# Pin for the GPIO remote sender (needs additional lirc configuration,
# which is at the moment beyond the scope of this playbook)
# rpi_ir_gpio_pin_tx: 13

# Set the memory for the GPU core - rpihddevice needs 256 MB, which is the default value set by the playbook
# rpi_gpu_mem = 256

Roles

check known services

---

# workaround for https://bugs.launchpad.net/ubuntu/+source/ansible/+bug/1880359
# TODO: for later ansible versions use ansible.builtin.service_facts (https://docs.ansible.com/ansible/latest/collections/ansible/builtin/service_facts_module.html#service-facts-module)
- name: check known services
  command: systemctl list-unit-files --no-pager --type service --all
  register: output

- name: create list of known services
  set_fact:
     known_services: "{{ output.stdout.split('\n') | map('regex_search', '^.*[.]service') | list }}"

- name: show known services
  debug:
    var: known_services
    verbosity: 1

stop vdr

meta

---
allow_duplicates: true
dependencies:
    - role: get-systemd-services

tasks

---

- name: Stop VDR
  systemd:
    name: vdr.service
    state: stopped
  notify: ['Start VDR']
  when: '"vdr.service" in known_services'
    

stop xorg

meta

---
allow_duplicates: true
dependencies:
    - role: get-systemd-services

defaults

xorg_services:
  - service_name: 'yavdr-xorg.servie'
    instance_name: 'yavdr-xorg.service'
    notification: 'Start yavdr-xorg'

  - service_name: 'xlogin@.service'
    instance_name: 'xlogin@{{ vdr.user | default("vdr") }}.service'
    notification: 'Start xlogin'

  - service_name: 'x@.service'
    instance_name: 'x@vt7.service'
 

main.yml

---

- name: 'Stop {{ item.instance_name }}'
  systemd:
    name: '{{ item.instance_name }}'
    state: stopped
  notify: '{{ item.notification | default(omit) }}'
  loop: '{{ xorg_services }}'
  when: item.service_name in known_services

install-dependencies

tasks

main.yml

---

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

nvidia experimental drivers

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

tasks

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-430 and other required packages
  apt:
    name:
      - nvidia-driver-430
      - nvidia-settings
    state: present
    install_recommends: no
  tags:
    - packages

yavdr-common

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

default variables

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

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

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: experimental
ppa_owner: 'ppa:yavdr'
nvidia_legacy: False

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

rpi_repositories:
  - '{{ ppa_owner }}/{{branch}}-main'
  - '{{ ppa_owner }}/{{branch}}-vdr'

Locales and Timezone

timezone: 'Europe/Berlin'
# timezone_hw can be set to 'UTC' or 'local' to set the preferred hardware clock
# timezone. This variable is optional, the default behavior is to let systemd
# set the hw clock to UTC # and sync it with the time aquired via NTP.
# Please note that changing the hw clock is usually not supported within virtual
# environments and systemd-timesyncd won't sync the current time to the hardware
# clock if it is set to local time.
# timezone_hw: 'UTC'  # or 'local'
default_locale: 'de_DE.UTF-8'
generate_locales:  # see entries in /etc/locale.gen - use just the name
  - 'de_DE.UTF-8'
  - 'en_US.UTF-8'
language_packs:
  - language-pack-de
  - language-pack-en

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

Additional Packages

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

extra_packages:
  - vim
  - tree
  - w-scan

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: /home/vdr
    recdir: /srv/vdr/video
    hide_first_recording_level: false
    safe_dirnames: true
    override_vdr_charset: ""
vdr_allowed_hosts: []

Standby settings

standby_reload_dvb: true

vdr-addon-lifeguardng

variables

lifeguard_enable_nfs: True
lifeguard_enable_samba: True
lifeguard_enable_ssh: True
lifeguard_hosts: []
lifeguard_users: []
lifeguard_processes: []
lifeguard_tcp:
  - vdr 3000

tasks

- name: apt | install vdr-addon-lifeguard-ng
  apt:
    name: vdr-addon-lifeguard-ng
    state: present

- name: expand template for vdr-addon-lifeguard-ng configuration file
  template:
    src: templates/lifeguard.conf.j2
    dest: /etc/lifeguard.conf
  notify: Restart vdr-addon-lifeguard-ng

templates

{{ ansible_managed | comment }}

[Options]
EnableNFS = {{ lifeguard_enable_nfs }}
EnableSamba = {{ lifeguard_enable_samba }}
EnableSSH = {{ lifeguard_enable_ssh }}
[Hosts]
# ping hostnames or IP addresses (which are faster than hostnames)
# Caution: pinging hosts can be quite slow!
{% for host in lifeguard_hosts %}
{{ host }}
{% endfor %}
[Process]
# one process name per line
{% for process in lifeguard_processes %}
{{ process }}
{% endfor %}
[User]
# one user name per line
{% for user in lifeguard_users %}
{{ user }}
{% endfor %}
[TCP]
# process name [:] <port> [<another port>]
{% for connection in lifeguard_tcp %}
{{ connection }}
{% endfor %}

Meta

---
dependencies:
    - role: stop-vdr
    - role: stop-xorg

handlers

- name: reconfigure_tzdata
  command: >
      dpkg-reconfigure --frontend noninteractive tzdata

- name: reconfigure_locales
  command: >
      dpkg-reconfigure --frontend noninteractive locales

- name: reconfigure_dash
  command: >
      dpkg-reconfigure -f noninteractive dash

tasks

yavdr-common executes the following tasks:

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]
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
Set up package repositories
- name: add PPAs
  apt_repository:
      repo: '{{ item }}'
      state: present
      update_cache: no
  loop: "{{ repositories|flatten(levels=1) }}"
  tags:
    - ppa
  when: ansible_architecture is match("x86_.*") or ansible_architecture == "aarch64"

- name: add PPAs for Raspberry Pi
  apt_repository:
      repo: '{{ item }}'
      state: present
      update_cache: no
  loop: "{{ rpi_repositories|flatten(levels=1) }}"
  tags:
    - ppa
  when: ansible_architecture == 'armv7l'

- name: add nvidia legacy PPA
  apt_repository:
      repo: 'ppa:kelebek333/nvidia-legacy'
      state: present
      update_cache: no
  when: nvidia_legacy

- name: run apt-get dist-upgrade
  apt:
      upgrade: dist
      update_cache: yes
ensure cloud-init is not installed

This should at least prevent black screen errors after a reboot, if the Ubuntu Cloud Server Image has been used for install.

- name: package_facts | lookup installed packages
  package_facts:
    manager: auto

- name: remove cloud-init if necessary
  block:
    - name: "debconf | disable all cloud-init datasources"
      debconf:
        name: cloud-init
        question: cloud-init/datasources
        value: ''
        vtype: select
      register: cloud_init_debconf

    - name: "command | reconfigure cloud-init package"
      command: dpkg-reconfigure -f noninteractive cloud-init
      ignore_errors: yes
      when:  cloud_init_debconf.changed
      # notify: ['rerun required']

    - name: "apt | ensure package cloud-init is not installed"
      apt:
        name: cloud-init
        state: absent
  when: "'cloud-init' in ansible_facts.packages"
locales and timezone
- name: apt | ensure packages for tzdata and locales are installed
  apt:
    state: present
    name:
      - tzdata
      - locales

- name: apt | install language packs
  apt:
    state: present
    name: '{{ language_packs }}'


- name: set timezone
  timezone:
    name: "{{ timezone }}"
    hwclock: "{{ timezone_hw | default(omit) }}"
  register: tz_setting

- name: debconf | set timezone
  block:
    - name: set timezone area
      debconf:
        name: tzdata
        question: tzdata/Areas
        value: "{{ timezone | dirname }}"
        vtype: select
      notify: reconfigure_tzdata

    - name: set timezone city
      debconf:
        name: tzdata
        question: "tzdata/Zones/{{ timezone | dirname }}"
        value: "{{ timezone | basename }}"
        vtype: select
      notify: reconfigure_tzdata
  when: tz_setting.changed


# based on https://serverfault.com/a/981742
- block:
   - name: Ensure localisation files for '{{ default_locale }}' are available
     locale_gen:
       name: "{{ default_locale }}"
       state: present

   - name: Ensure localisation files for '{{ generate_locales }}' are available
     locale_gen:
       name: "{{ item }}"
       state: present
     loop: "{{ generate_locales }}"

   - name: Get current locale and language configuration
     command: localectl status
     register: locale_status
     changed_when: false

   - name: Parse 'LANG' from current locale and language configuration
     set_fact:
       locale_lang: "{{ locale_status.stdout | regex_search('LANG=([^\n]+)', '\\1') | first }}"

   - name: Parse 'LANGUAGE' from current locale and language configuration
     set_fact:
       locale_language: "{{ locale_status.stdout | regex_search('LANGUAGE=([^\n]+)', '\\1') | default([locale_lang], true) | first }}"

   - name: Configure locale to '{{ default_locale }}' and language to '{{ default_locale }}'
     become: yes
     command: localectl set-locale LANG={{ default_locale }} LANGUAGE={{ default_locale }}
     changed_when: locale_lang != default_locale or locale_language != default_locale
Use bash instead of dash

dash is more limited than bash (albeit faster) - since vdr packages and addons ship with a lot of scripts, we use bash as the default shell.

- name: use bash instead of dash
  debconf:
    name: dash
    question: dash/sh
    value: 'false'
    vtype: select
  notify: reconfigure_dash

- name: flush all handlers
  meta: flush_handlers
create user vdr and set up home directories
Disable release-upgrade notifications
SOMEDAY move from lineinfile to template
  • State “SOMEDAY” from “TODO” [2017-11-22 Mi 10:59]
- name: disable release-upgrade notifications
  lineinfile:
    path: /etc/update-manager/release-upgrades
    backrefs: yes
    state: present
    regexp: '^(Prompt=).*$'
    line: 'Prompt=never'
  ignore_errors: yes
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
      - pciutils
      - psmisc
      - python3-jmespath
      - python3-kmodpy
      - python3-requests
      - python3-ruamel.yaml
      - python3-usb
      - software-properties-common
      - ssh
      - toilet
      - wget
      - wpasupplicant
      - usbutils
      - xfsprogs
      - yavdr-i18n
    state: present
    install_recommends: no
Install additional packages (user defined)
- name: apt | install extra packages
  apt:
    name: '{{ extra_packages }}'
    state: present
    install_recommends: no

- name: apt | install plymouth-theme-yavdr-logo on x86 architectures
  apt:
    name: plymouth-theme-yavdr-logo
  when: ansible_architecture is match("x86_.*")
create directories
create sudoers drop-in for vdr
basic scripts for standby support

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

templates

apt configuration

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

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

suspend hooks

#!/usr/bin/bash
{{ ansible_managed | comment }}

case $1 in
  pre)
    # disable "_nfs.tcp" avahi announcements
    for file in /etc/avahi/services/*.service; do
        if grep -Fq "_nfs._tcp" "$file"; then
            echo "rename $file to ${file%%.*}.disabled"
            mv "$file" "${file%%.*}.disabled"
        fi
    done

    /bin/systemctl stop vdr
    /usr/bin/frontend-dbus-send shutdown_successfull
    {% if standby_reload_dvb %}
    echo "unload dvb drivers"
    /usr/local/bin/module-helper -u dvb_core
    {% endif %}
    {% if timezone_hw is defined %}/etc/init.d/hwclock.sh stop{% endif %}
    ;;
  post)
    {% if timezone_hw is defined %}/etc/init.d/hwclock.sh start{% endif %}
    # start current frontend again
    if [ -x /usr/bin/frontend-dbus-send ]
    then
        /usr/bin/frontend-dbus-send start ||:
    fi

    # reload rc-core keytables
    if [ -x /usr/bin/ir-keytable ]
    then
        for remote in $(ir-keytable 2>&1 | grep rc/rc | egrep -o "rc[0-9]{1,}")
        do
            /usr/bin/ir-keytable -a /etc/rc_maps.cfg --sysdev $remote
        done
    fi

    {% if standby_reload_dvb %}
    echo "restore dvb drivers"
    /usr/local/bin/module-helper -r
    {% endif %}

    # wait up to 10 seconds for the network
    timeout=0
    while [ -z "$(hostname --all-fqdns)" ]
    do
        echo "waiting for network..."
        sleep .5
        [ $(( timeout++ )) -ge 20 ] && break
    done

    # restore "_nfs._tcp" avahi announcements
    for file in /etc/avahi/services/*.disabled; do
        if grep -Fq "_nfs._tcp" "$file"; then
            echo "rename $file to ${file%%.*}.service"
            mv "$file" "${file%%.*}.service"
        fi
    done

    /bin/systemctl start vdr
    ;;
esac

module-helper

# {{ 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)

from dataclasses import dataclass, field
from typing import List, Set
import argparse
import pathlib
import pickle
import subprocess
import sys

PROC_MODULES = pathlib.Path('/proc/modules')


@dataclass
class Module:
    name: str
    raw_dependencies: List["Module"] = field(default_factory=list)
    dependencies: List["Module"] = field(default_factory=list)
    n_instances: int = 0

    def __hash__(self) -> int:
        return hash(self.name)

    @property
    def has_dependencies(self) -> bool:
        return len(self.dependencies) > 0 or self.n_instances > 0

    def get_all_dependencies(self) -> Set["Module"]:
        deps = set()
        deps.add(self)
        for dep in self.dependencies:
            deps |= dep.get_all_dependencies()
        return deps


def read_modules(main_module_name: str) -> Set[Module]:
    modules_with_deps = {}
    # read the proc filesystem to get the loaded modules
    for line in PROC_MODULES.read_text().splitlines():
        module, _, n_instances, dependencies, *_ = line.split()
        deps = list(
            filter(lambda x: x not in ('', '-'), dependencies.split(',')))
        modules_with_deps[module] = Module(
            name=module,
            raw_dependencies=deps,
            n_instances=int(n_instances)
        )
    # fill in dependencies
    for m in modules_with_deps.values():
        for dep in m.raw_dependencies:
            m.dependencies.append(modules_with_deps[dep])
        m.raw_dependencies.clear()

    # get a set of the modules we need to unload
    if (main_module := modules_with_deps.get(main_module_name)):
        all_dependencies = main_module.get_all_dependencies()
    else:
        all_dependencies = set()
    return all_dependencies


def run_command_on_module_list(command, module_list):
    for module in module_list:
        cmd = [command, module]
        print(*cmd)
        subprocess.run(cmd, check=True)


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


def main():
    parser = create_argparser()
    args = parser.parse_args()
    if args.unload:
        for module in args.unload:
            unloaded_modules = []
            while (all_dependencies := read_modules(module)):
                # print(all_dependencies)
                modules_without_deps = [
                    m for m in all_dependencies if not m.has_dependencies
                ]
                for m in modules_without_deps:
                    print(f"modprobe -r {m.name}")
                    subprocess.run(['modprobe', '-r', m.name], check=True)
                    unloaded_modules.append(m.name)
            print(f"modprobe -r {module}")
            subprocess.run(['modprobe', '-r', module], check=True)
            unloaded_modules.append(module)
            try:
                with open(args.temp_file, 'wb') as f:
                    pickle.dump(unloaded_modules, f)
            except Exception as err:
                sys.exit(err)
    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()


if __name__ == '__main__':
    main()

MOTD

Header

#
#    00-header - create the header of the MOTD
#    Copyright (C) 2009-2010 Canonical Ltd.
#                  2019      Alexander Grothe
#
#    Authors: Dustin Kirkland <kirkland@canonical.com>
#             Alexander Grothe <seahawk1986@gmx.de>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

toilet -S -f "smblock.tlf" --metal -t "yaVDR-ansible" 2>/dev/null || echo "yavdr-ansible"

[ -r /etc/lsb-release ] && . /etc/lsb-release

if [ -z "$DISTRIB_DESCRIPTION" ] && [ -x /usr/bin/lsb_release ]; then
	# Fall back to using the very slow lsb_release utility
	DISTRIB_DESCRIPTION=$(lsb_release -s -d)
fi

printf "%s (%s %s %s)\n" "$DISTRIB_DESCRIPTION" "$(uname -o)" "$(uname -r)" "$(uname -m)"

collect facts about the system with custom modules

variables

tasks

main.yml
- name: get information about usb and pci hardware and loaded kernel modules
  hardware_facts:
    usb: True
    pci: True
    serial: True
    modules: True
    gpus: True
    acpi_power_modes: True
  tags:
    - always

- debug:
    var: '{{ item }}'
    verbosity: 1
  loop:
    - usb
    - pci
    - serial
    - 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

vdr

streamdev_client_remote_ip: ''

# list of additional debian packages to install
vdr_plugins: []

tasks

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

Add svdrp/svdrp-disc to /etc/services

- name: add svdrp and svdrp-disc to /etc/services
  lineinfile:
    dest: /etc/services
    state: present
    line: "{{ item }}"
  loop:
    - "svdrp            6419/tcp"
    - "svdrp-disc       6419/udp"

allow dbus2vdr to use the system bus

- name: configure dbus2vdr for vdr instance id "{{ vdr.instance_id | default(0) }}" and user "{{ vdr.user }}"
  vars:
    vdr_instance_id: "{{ '' if vdr.instance_id | default(0) | int == 0 else vdr.instance_id }}"
  template:
    src: dbus2vdr/de.tvdr.vdr.conf.j2
    dest: /etc/dbus-1/system.d/de.tvdr.vdr.conf

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 vdr charset override
  template:
    src: conf.d/02-vdr-charset-override.conf.j2
    dest: /etc/vdr/conf.d/02-vdr-charset-override.conf

- name: set option to use hide-first-recording-level patch
  template:
    src: conf.d/04-vdr-hide-first-recordinglevel.conf.j2
    dest: /etc/vdr/conf.d/04-vdr-hide-first-recordinglevel.conf


- name: set instance id for vdr
  template:
    src: conf.d/05-vdr-instance.conf.j2
    dest: /etc/vdr/conf.d/05-vdr-instance.conf

- name: set option to define recordings directory
  template:
    src: conf.d/06-vdr-recordings.conf.j2
    dest: /etc/vdr/conf.d/06-vdr-recordings.conf

- 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

- name: set VDR_ID for shells in /etc/profile.d/
  template:
    src: vdr-dbus-send-vdr-id.sh
    dest: /etc/profile.d/vdr-dbus-send-vdr-id.sh
  notify: ["reboot required"]

# TODO: set user etc. in /etc/vdr/conf.d/

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 }}'
    state: present
    install_recommends: no
  notify: [ 'Restart VDR' ]

create or copy vdr configuration files

Because some files are changed by the vdr at runtime, we won’t overwrite them if the task is marked with force: no.

- 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: remote.conf
    dest: '{{ vdr.confdir }}/remote.conf'
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'
    mode: 0644
    force: no

- name: "vdr configuration | create keymacros.conf"
  template:
    src: keymacros.conf.j2
    dest: '{{ vdr.etc_confdir }}/keymacros.conf'
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'
    mode: 0644
    force: yes

- name: "vdr configuration | create scr.conf"
  template:
    src: scr.conf.j2
    dest: '{{ vdr.etc_confdir }}/scr.conf'
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'
    mode: 0644
    force: yes

- name: "vdr configuration | create diseqc.conf"
  template:
    src: diseqc.conf.j2
    dest: '{{ vdr.etc_confdir }}/diseqc.conf'
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'
    mode: 0644
    force: yes

- 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

plugin preconfiguration

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

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) }}'

- name: "disable/enable wait-for-dvb@.service for configured devices"
  systemd:
    enabled: '{{ "yes" if item in wait_for_dvb_devices|default([]) else "no" }}'
    name: 'wait-for-dvb@{{ item }}.service'
  loop: "{{ range(max_num_dvb_devices|default(16))|list }}"
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
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
streamdev-client
- name: install and configure streamdev-client
  block:
    - name: apt | install vdr-plugin-streamdev-client
      apt:
        state: '{{ "present" if streamdev_client_remote_ip else "absent" }}'
        name: "vdr-plugin-streamdev-client"
    - name: set streamdev-client.RemoteIp
      lineinfile:
        state: '{{ "present" if streamdev_client_remote_ip else "absent" }}'
        owner: "{{ vdr.user }}"
        group: "{{ vdr.group }}"
        create: yes
        path : "{{ vdr.home }}/setup.conf"
        regexp: "^streamdev-client.RemoteIp"
        line: "streamdev-client.RemoteIp = {{ streamdev_client_remote_ip }}"

    - name: set streamdev-client.RemotePort
      lineinfile:
        owner: "{{ vdr.user }}"
        group: "{{ vdr.group }}"
        create: yes
        path : "{{ vdr.home }}/setup.conf"
        regexp: "^streamdev-client.RemotePort"
        line: "streamdev-client.RemotePort = {{ streamdev_client_remote_port | default(2004) }}"

    - name: set streamdev-client.NumProvidedSystems
      lineinfile:
        owner: "{{  vdr.user  }}"
        group: "{{  vdr.group }}"
        create: yes
        path : "{{ vdr.home }}/setup.conf"
        regexp: "^streamdev-client.NumProvidedSystems"
        line: "streamdev-client.NumProvidedSystems = {{ streamdev_client_num_provided_systems | default(1) }}"

    - name: set streamdev-client.StartClient
      lineinfile:
        owner: "{{ vdr.user }}"
        group: "{{ vdr.group }}"
        create: yes
        path : "{{ vdr.home }}/setup.conf"
        regexp: "^streamdev-client.StartClient"
        line: "streamdev-client.StartClient = {{ '1' if streamdev_client_remote_ip else '0' }}"

Drop-Ins for vdr.service

- name: create directory for vdr.service systemd drop-in files
  file:
    dest: '{{ item }}'
    state: directory
  loop:
    - "/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
  notify: ["Reload Units"]

- name: systemd-drop-in | load environment file for vdr.service
  template:
    src: templates/systemd/vdr.service.d/vdr-environ.j2
    dest: /etc/systemd/system/vdr.service.d/load-environ.conf
  notify: ["Reload Units"]

create commands.conf entries

- name: create snippet with yavdr commands
  template:
    src: templates/command-hooks/commands.yavdr.conf.j2
    dest: /usr/share/vdr/command-hooks/commands.yavdr.conf

create helper scripts

- name: create helper scripts for reccmds
  copy:
    src: reccmds/{{ item }}.sh
    dest: /usr/local/bin/{{ item }}
    mode: 0755
  loop:
    - removeindex
    - removemarks
    - removeresume
    - vdr-recrepeat

create reccmds snippets

- name: create snippet for yavdr reccmds to handle marks-, resume- and index-files
  template:
    src: reccmds.removemarks.conf.j2
    dest: /usr/share/vdr/command-hooks/reccmds.removemarks.conf

- name: create snippet for yavdr reccmds to search repeats using epgsearch
  template:
    src: reccmds.searchrepeats.conf.j2
    dest: /usr/share/vdr/command-hooks/reccmds.searchrepeats.conf
  when: '"vdr-plugin-epgsearch" in vdr_plugins'

templates

vdr config snippets

{{ ansible_managed | comment }}

{% if vdr.hide_first_recording_level | bool %}
[vdr]
--hide-first-recording-level
{% endif %}
{{ ansible_managed | comment }}
{% if vdr.override_vdr_charset | default("") | length > 0 %}
[vdr]
--chartab={{ vdr.override_vdr_charset }}
{% endif %}
{{ ansible_managed | comment }}

[vdr]
--instance={{ vdr.instance_id | default(0) }}
{{ ansible_managed | comment }}

[vdr]
--video={{ vdr.recdir }}

Systemd Drop-in to start vdr.service after network-online.target and network.target

After=network-online.target is neccessary to prevent problems with vdr-plugins which expect to connect to a server during startup. After=network.target is necessary to prevent the network to be deconfigured during shutdown before the vdr process exits.

{{ansible_managed | comment }}
[Unit]
After=network-online.target network.target
Wants=network.target network-online.target

Systemd Drop-in for setting vdr environment

Set environment variables for system locale and user session on startup

{{ ansible_managed | comment }}
[Service]
EnvironmentFile=-/etc/default/locale
Environment=XDG_RUNTIME_DIR=/run/user/{{ vdr.uid }}/
EnvironmentFile=-{{ vdr.home }}/.session-env
Environment=VDR_ID={{ vdr.instance_id | default(0)  }}

dbus2vdr dbus system bus configuration

{{ ansible_managed | comment('xml') }}
<!DOCTYPE busconfig PUBLIC
 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>

  <!-- Only user {{ vdr.user }} can own the vdr-dbus-service -->
  <policy user="{{ vdr.user }}">
    <allow own="de.tvdr.vdr{{ vdr_instance_id }}"/>
  </policy>

  <!-- allow everyone to call vdr's methods -->
  <policy context="default">
    <allow send_destination="de.tvdr.vdr{{ vdr_instance_id }}"/>
    <allow receive_sender="de.tvdr.vdr{{ vdr_instance_id }}"/>
    <allow send_destination="de.tvdr.vdr{{ vdr_instance_id }}"/>
    <allow receive_sender="de.tvdr.vdr{{ vdr_instance_id }}"/>
  </policy>

</busconfig>

set VDR_ID in etc/profile.d

{{ ansible_managed | comment }}
export VDR_ID={{ vdr.instance_id | default(0) }}

/etc/default/vdr

{{ ansible_managed | comment }}
# /etc/default/vdr
#
# See also /usr/share/doc/vdr/README.Debian.gz
#

SHUTDOWNCMD="{{ vdr_shutdown_command }}"

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 %}

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 %}

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

command-hooks

{{ ansible_managed | comment }}
{{ "Update vdr recordings list" | translate }} : echo "/usr/bin/svdrpsend updr" | at now
{{ "Restart VDR" | translate }} : sudo /bin/systemctl --no-block restart vdr.service
{{ "Reboot system" | translate }} : sudo /bin/systemctl --no-block reboot
{{ "Shutdown system" | translate }} : echo "/usr/bin/lircd2uinput-send KEY_POWER2" | at now

reccmds.removemarks.conf

This is a snippet for the reccmds.conf. It adds some actions to remove marks, resume and index files.

{{ ansible_managed | comment }}
{{ "Remove marks" | translate }} : /usr/local/bin/removemarks
{{ "Remove Index-File" | translate }} : /usr/local/bin/removeindex
{{ "Remove resume file" | translate }} : /usr/local/bin/removeresume
{{ "Mark advertisements" | translate }} : /usr/bin/markad -O "$1" after

reccmds.searchrepeats.conf

This is a snippet

{{ "Search for repeat" | translate }} : /usr/local/bin/vdr-recrepeat 0
{{ "Search for repeat with subtitle (same episode)" | translate }} : /usr/local/bin/vdr-recrepeat 1

files

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

KBD.Up         00000000001B5B41
KBD.Down       00000000001B5B42
KBD.Menu       000000001B5B317E
KBD.Ok         000000000000000D
KBD.Back       000000000000007F
KBD.Left       00000000001B5B44
KBD.Right      00000000001B5B43
KBD.Red        000000001B5B5B41
KBD.Green      000000001B5B5B42
KBD.Yellow     000000001B5B5B43
KBD.Blue       000000001B5B5B44
KBD.0          0000000000000030
KBD.1          0000000000000031
KBD.2          0000000000000032
KBD.3          0000000000000033
KBD.4          0000000000000034
KBD.5          0000000000000035
KBD.6          0000000000000036
KBD.7          0000000000000037
KBD.8          0000000000000038
KBD.9          0000000000000039
KBD.Info       000000001B5B347E
KBD.Play/Pause 0000001B5B32307E
KBD.Power      00000000001B5B50
KBD.Channel+   000000001B5B357E
KBD.Channel-   000000001B5B367E
KBD.Volume+    0000001B5B32347E
KBD.Volume-    0000001B5B32337E
KBD.Mute       0000001B5B32317E
KBD.User1      000000001B5B5B45
KBD.User2      0000001B5B31377E
KBD.User3      0000001B5B31387E
KBD.User4      0000001B5B31397E
KBD.User7      000000001B5B327E
KBD.User8      000000000000002F
KBD.User9      000000000000002A

keymacros.conf

{{ ansible_managed | comment }}
# 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

{% if "vdr-plugin-osdteletext" in vdr_plugins %}
User0     @osdteletext
{% endif %}
{% if vdr_output_plugin | default("") == "vdr-plugin-xineliboutput" %}
User9     @xineliboutput Red 4   # toggle autocrop
{% elif vdr_output_plugin | default("") == "vdr-plugin-softhddevice" %}
User9     @softhddevice Blue 2 5 # toggle autocrop
{% endif %}

scr.conf

{{ ansible_managed | comment }}
# SCR (Satellite Channel Routing) configuration for VDR
#
# Format:
#
# channel frequency [pin]
#
# channel:        SCR channel index (0-7)
# frequency:      frequency of the SCR channel ("user band")
# pin:            optional pin of the SCR channel (0-255)
#
# A line containing space separated integer numbers, terminated with a ':',
# defines that any following lines apply only to the given list
# of device numbers.
#
# Examples:

# EN50494 & EN50607 ("JESS")
# 0 974
# 1 1076
# 2 1178
# 3 1280
# 4 1382
# 5 1484
# 6 1586
# 7 1688
# EN50607 ("JESS") only
# 8 1790
# 9 1892
# 10 1994
# 11 2096

diseqc.conf

{{ ansible_managed | comment }}
# DiSEqC configuration for VDR
#
# Format:
#
# satellite slof polarization lof command...
#
# satellite:      one of the 'S' codes defined in sources.conf
#                 the special value 'S360E' means that this entry uses a positioner
#                 (command 'P') that can move the dish to any requested satellite
#                 position within its range
# slof:           switch frequency of LNB; the first entry with
#                 an slof greater than the actual transponder
#                 frequency will be used
# polarization:   V = vertical, H = horizontal, L = Left circular, R = Right circular
# lof:            the local oscillator frequency to subtract from
#                 the actual transponder frequency
# command:
#   t         tone off
#   T         tone on
#   v         voltage low (13V)
#   V         voltage high (18V)
#   A         mini A
#   B         mini B
#   Pn        use positioner to move dish to satellite position n (or to the
#             satellite's orbital position, if no position number is given)
#   Sn        satellite channel routing code sequence for bank n follows
#   Wnn       wait nn milliseconds (nn may be any positive integer number)
#   [xx ...]  hex code sequence (max. 6)
#
# The 'command...' part is optional.
#
# A line containing space separated integer numbers, terminated with a ':',
# defines that any following DiSEqC sequences apply only to the given list
# of device numbers.
#
# Examples:

# Full DiSEqC sequence:

S19.2E  11700 V  9750  t v W15 [E0 10 38 F0] W15 A W15 t
S19.2E  99999 V 10600  t v W15 [E0 10 38 F1] W15 A W15 T
S19.2E  11700 H  9750  t V W15 [E0 10 38 F2] W15 A W15 t
S19.2E  99999 H 10600  t V W15 [E0 10 38 F3] W15 A W15 T

S13.0E  11700 V  9750  t v W15 [E0 10 38 F4] W15 B W15 t
S13.0E  99999 V 10600  t v W15 [E0 10 38 F5] W15 B W15 T
S13.0E  11700 H  9750  t V W15 [E0 10 38 F6] W15 B W15 t
S13.0E  99999 H 10600  t V W15 [E0 10 38 F7] W15 B W15 T

# Optimized for mini DiSEqC (aka toneburst):
#
# S19.2E  11700 V  9750  t v W15 A W15 t
# S19.2E  99999 V 10600  t v W15 A W15 T
# S19.2E  11700 H  9750  t V W15 A W15 t
# S19.2E  99999 H 10600  t V W15 A W15 T
#
# S13.0E  11700 V  9750  t v W15 B W15 t
# S13.0E  99999 V 10600  t v W15 B W15 T
# S13.0E  11700 H  9750  t V W15 B W15 t
# S13.0E  99999 H 10600  t V W15 B W15 T
#
# Optimized for full DiSEqC:
#
# S19.2E  11700 V  9750  [E0 10 38 F0]
# S19.2E  99999 V 10600  [E0 10 38 F1]
# S19.2E  11700 H  9750  [E0 10 38 F2]
# S19.2E  99999 H 10600  [E0 10 38 F3]
#
# S13.0E  11700 V  9750  [E0 10 38 F4]
# S13.0E  99999 V 10600  [E0 10 38 F5]
# S13.0E  11700 H  9750  [E0 10 38 F6]
# S13.0E  99999 H 10600  [E0 10 38 F7]
#
# DisiCon-4 Single Cable Network:
#
# horizontal: 11.704 - 12.205 GHz (1. IF: 1144 - 1645 MHz) - LOF 1 (10,56 GHz)
# horizontal: 12.676 - 12.709 GHz (1. IF: 2116 - 2148 MHz) - LOF 1 (10,56 GHz)
# vertical:   12.035 - 12.107 GHz (1. IF:  955 - 1027 MHz) - LOF 3 (11,08 GHz)
# vertical:   12.464 - 12.716 GHz (1. IF: 1744 - 1996 MHz) - LOF 2 (10,72 GHz)
#
# S19.2E  99999 H 10560 t v
# S19.2E  12110 V 11080 t v
# S19.2E  99999 V 10720 t v
#
# SCR (Satellite Channel Routing) EN50494:
#
# S19.2E  11700 V  9750  t V W10 S0 [E0 10 5A 00 00] W10 v
# S19.2E  99999 V 10600  t V W10 S1 [E0 10 5A 00 00] W10 v
# S19.2E  11700 H  9750  t V W10 S2 [E0 10 5A 00 00] W10 v
# S19.2E  99999 H 10600  t V W10 S3 [E0 10 5A 00 00] W10 v
#
# S13.0E  11700 V  9750  t V W10 S4 [E0 10 5A 00 00] W10 v
# S13.0E  99999 V 10600  t V W10 S5 [E0 10 5A 00 00] W10 v
# S13.0E  11700 H  9750  t V W10 S6 [E0 10 5A 00 00] W10 v
# S13.0E  99999 H 10600  t V W10 S7 [E0 10 5A 00 00] W10 v
#
# SCR (Satellite Channel Routing) EN50607 "JESS":
#
# S19.2E  11700 V  9750  t V W10 S0 [70 00 00 00] W10 v
# S19.2E  99999 V 10600  t V W10 S1 [70 00 00 00] W10 v
# S19.2E  11700 H  9750  t V W10 S2 [70 00 00 00] W10 v
# S19.2E  99999 H 10600  t V W10 S3 [70 00 00 00] W10 v
#
# S13.0E  11700 V  9750  t V W10 S4 [70 00 00 00] W10 v
# S13.0E  99999 V 10600  t V W10 S5 [70 00 00 00] W10 v
# S13.0E  11700 H  9750  t V W10 S6 [70 00 00 00] W10 v
# S13.0E  99999 H 10600  t V W10 S7 [70 00 00 00] W10 v
#
# S23.0E  11700 V  9750  t V W10 S8 [70 00 00 00] W10 v
# S23.0E  99999 V 10600  t V W10 S9 [70 00 00 00] W10 v
# S23.0E  11700 H  9750  t V W10 S10 [70 00 00 00] W10 v
# S23.0E  99999 H 10600  t V W10 S11 [70 00 00 00] W10 v
#
# S28.2E  11700 V  9750  t V W10 S12 [70 00 00 00] W10 v
# S28.2E  99999 V 10600  t V W10 S13 [70 00 00 00] W10 v
# S28.2E  11700 H  9750  t V W10 S14 [70 00 00 00] W10 v
# S28.2E  99999 H 10600  t V W10 S15 [70 00 00 00] W10 v
#
# Positioner for steerable dish:
#
# S360E  11700 V  9750  t V W20 P W20 t v
# S360E  99999 V 10600  t V W20 P W20 T v
# S360E  11700 H  9750  t V W20 P W20 t V
# S360E  99999 H 10600  t V W20 P W20 T V

VDR helper scripts

removeresume

This script deletes all resume and resume.* files of the given recording

find "$1" -maxdepth 1 -type f \( -name "resume" -o -name "resume.*" \) -delete
removeindex

This script deletes all index and index.vdr files of the given recording

find "$1" -maxdepth 1 \( -name "index" -o -name "index.vdr" \) -delete
removemarks

This script deletes all marks and marks.vdr files of the given recording

find "$1" -maxdepth 1 \( -name "marks" -o -name "marks.vdr" \) -delete
recrepeat

This script allows to search for repeats of a recording using epgsearch

#------------------------------------------------------------------------------
# this script allows searching for a repeat of a recording using epgsearch
# add the following lines to your reccmds.conf
#
# Search for repeat : /path_to_this_script/recrep.sh 0
# Search for repeat with subtitle (same episode): /path_to_this_script/recrep.sh 1
#
# Author: Christian Wieninger (cwieninger@gmx.de)
# Version: 1.1 - 2011-01-16
#
# changed for yavdr (steffenbpunkt@gmail.com)
#
# requirements: grep
#------------------------------------------------------------------------------

# adjust the following lines to your config

# your plugins config dir
PLUGINCONFDIR=/var/lib/vdr/plugins/epgsearch

# do not edit below this line

cat << EOM >/tmp/cmd.sh
INFOFILE="$2/info";

TITLE=\$(grep '^T ' \$INFOFILE);
#cut leading 'T '
TITLE=\${TITLE#*\$T };

EPISODE=\$(grep '^S ' \$INFOFILE)
#cut leading 'S '
EPISODE=\${EPISODE#*\$S };

SEARCHTERM=\$TITLE;

if [ "$1" -eq "1" ]; then
SEARCHTERM=\$TITLE~\$EPISODE;
fi

RCFILE=$PLUGINCONFDIR/.epgsearchrc
echo Search=\$SEARCHTERM > \$RCFILE
#search for this term as phrase
echo SearchMode=0 >> \$RCFILE
if [ "$1" -eq "0" ]; then
    echo UseSubtitle=0 >> \$RCFILE;
fi
echo UseDescr=0 >> \$RCFILE
vdr-dbus-send /Remote remote.CallPlugin string:'epgsearch'
EOM

echo ". /tmp/cmd.sh; rm /tmp/cmd.sh" | at now

yavdr-network

default variables

install_avahi: true

tasks

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

- name: apt | install packages for network services
  apt:
      name:
          - avahi-daemon
          - avahi-utils
          - autofs
          #- 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: set video directory for avahi-linker
  ini_file:
    path: /etc/avahi-linker/default.cfg
    section: "targetdirs"
    option: "vdr"
    value: "{{ vdr.recdir }}"

- name: set vdr instance id for avahi-linker
  ini_file:
    path: /etc/avahi-linker/default.cfg
    section: "options"
    option: "vdr_instance_id"
    value: "{{ vdr.instance_id | default(0) }}"

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

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

- name: ensure additional services for avahi-linker are active
  systemd:
    name: '{{ item }}'
    state: started
    enabled: yes
    masked: no
  loop:
    - vdr-net-monitor
    - vdr-update-monitor
    - prevent-umount-on-pause

vdr-plugin-menuorg

The menuorg plugin provides a more organized osd menu and allows to sort entries for plugins, vdr menu entries and custom commands using submenus. You can customize the menuorg.xml template to your liking. Please remember that the translate filter requires the localization data from the package yavdr-i18n on the device which executes the ansible playbook (this package is installed by default when running the install-yavdr.sh script).

tasks

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

- name: apt | install vdr-plugin-menuorg
  apt:
    name:
      - vdr-plugin-menuorg
    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
    owner: "{{ vdr.user }}"
    group: "{{ vdr.group }}"
    mode: 0644

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" />
    <menu name="{{ "Timer" | translate }}">
        <system name="Timers" />
        <plugin name="epgsearchonly" />
        <plugin name="quickepgsearch" />
        <plugin name="conflictcheckonly" />
    </menu>
    <menu name="{{ "Video"| translate }}">
        <system name="Recordings" />
        <plugin name="extrecmenu" />
        <plugin name="extrecmenung" />
        <plugin name="burn" />
        <plugin name="dvdswitch" />
        <plugin name="dvdselect" />
        <plugin name="dvd" />
        <plugin name="xineliboutput" />
        <plugin name="mplayer" />
        <plugin name="mpv" />
        <plugin name="markad" />
        <plugin name="recsearch" />
        <plugin name="recstatus" />
        <plugin name="webvideo" />
        <plugin name="vodcatcher" />
        <plugin name="vcd" />
        <plugin name="undelete" />
    </menu>
    <menu name="{{ "Audio" | translate }}" >
        <plugin name="mp3" />
        <plugin name="music" />
        <plugin name="lastfm" />
        <plugin name="radio" />
        <plugin name="radiolist" />
        <plugin name="cdda" />
        <plugin name="cdplayer" />
        <plugin name="podcatcher" />
        <plugin name="karaoke" />
    </menu>
    <plugin name="tvguide" />
    <plugin name="tvguideng" />
    <plugin name="desktop" title="{{ 'Applications' | translate }}" />
    <command name="Kodi" execute="frontend-dbus-send switchto kodi" />
    <menu name="{{ 'System' | translate }}">
        <system name="Commands" />
        <system name="Setup" />
        <plugin name="devstatus" />
        <plugin name="systeminfo" />
        <plugin name="femon" />
        <plugin name="filebrowser" />
        <plugin name="osd2web" />
        <plugin name="pin" />
        <plugin name="epg2vdr" />
        <plugin name="scraper2vdr" />
        <plugin name="sndctl" />
        <plugin name="pulsecontrol" />
        <plugin name="dynamite" />
        <plugin name="softhdcuvid" />
        <plugin name="softhddevice" />
        <plugin name="vaapidevice" />
        <plugin name="streamdev-client" />
        <plugin name="streamdev-server" />
        <plugin name="suspendoutput" />
    </menu>
    <plugin name="trayopenng" />
    <plugin name="undelete" />
</menus>

nfs-server

disable announcement for NFS avahi-services before stopping nfs-kernel-server, announce them again after a (re)start

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'
  loop: '{{ media_dirs|dict2items }}'

templates

/etc/exports

{{ ansible_managed | comment }}
{% set global = namespace(fsid=0) %}
/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 '' }})
{% set global.next_fsid = (loop.index + 1) %}
{% endfor %}
{% if vdr.hide_first_recording_level %}
{{ vdr.recdir }}/local  *(rw,fsid={{ global.next_fsid }},sync,no_subtree_check,all_squash,anongid={{ vdr.gid }},anonuid={{ vdr.uid }}{{ ',insecure' if nfs.insecure else '' }})
{% endif %}

avahi services

<?xml version="1.0" standalone='no'?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
{{ ansible_managed | comment('xml') }}
<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 }}{{ "/local" if (item.key == "recordings" and vdr.hide_first_recording_level | bool) else "" }}</txt-record> ## path to shared Folder
       <txt-record>subtype={{ 'vdr' if item.key == 'recordings' else item.key }}</txt-record> ## subtype
</service>
</service-group>

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.

default variables

lircd0_socket: /var/run/lirc/lircd0

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
      notify: ["Reload Units"]
  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
  loop:
    - 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


- name: install dependencies for rc-core-learn.py
  apt:
    pkg:
      - python3-evdev
    state: present

- name: expand template for rc-core-learn.py
  template:
    src:  templates/rc-core-learn.py.j2
    dest: /usr/local/bin/rc-core-learn
    mode: 0755

templates

Systemd Drop-in to start vdr with lirc support

{{ ansible_managed | comment }}
{% if frontend != 'kodi' %}
[vdr]
--lirc=/var/run/lirc/lircd
{% endif %}

Systemd Drop-in to start lircd2uinput with lircd

{{ ansible_managed | comment }}
[Service]
ExecStartPost=/usr/bin/lircd2uinput-add /var/run/lirc/lircd0
ExecStopPost=/usr/bin/lircd2uinput-remove /var/run/lirc/lircd0

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
gpio_ir_recv rc-rc6-mce         /lib/udev/rc_keymaps/rc-rc6-mce

# 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

Helper script to learn keys for rc-core receiver

{{ ansible_managed | comment }}
import functools
import contextlib
import signal
import sys
import time
from argparse import ArgumentParser
from pathlib import Path
from evdev import InputDevice, ecodes

BUTTONS = [
        "KEY_OK",
        "KEY_MENU",
        "KEY_ESC",
        "KEY_UP",
        "KEY_DOWN",
        "KEY_LEFT",
        "KEY_RIGHT",
        "KEY_RED",
        "KEY_GREEN",
        "KEY_YELLOW",
        "KEY_BLUE",
        "KEY_0",
        "KEY_1",
        "KEY_2",
        "KEY_3",
        "KEY_4",
        "KEY_5",
        "KEY_6",
        "KEY_7",
        "KEY_8",
        "KEY_9",
        "KEY_INFO",
        "KEY_PLAY",
        "KEY_PAUSE",
        "KEY_STOP",
        "KEY_RECORD",
        "KEY_FASTFORWARD",
        "KEY_REWIND",
        "KEY_NEXT",
        "KEY_BACK",
        "KEY_POWER2",
        "KEY_CHANNELUP",
        "KEY_CHANNELDOWN",
        "KEY_PREVIOUS",
        "KEY_VOLUMEUP",
        "KEY_VOLUMEDOWN",
        "KEY_MUTE",
        "KEY_SUBTITLE",
        "KEY_EPG",
        "KEY_CHANNEL",
        "KEY_FAVORITES",
        "KEY_MODE",
        "KEY_TIME",
        "KEY_PVR",
        "KEY_SETUP",
        "KEY_TEXT",
        "KEY_PROG1",
        "KEY_PROG2",
        "KEY_PROG3",
        "KEY_PROG4",
        "KEY_AUDIO",
        "KEY_VIDEO",
        "KEY_IMAGES",
        "KEY_FN",
        "KEY_SCREEN",
        ]

RC_SYS_DEVICES = Path('/sys/class/rc/')
DEVPATH = Path('/dev')
debug = functools.partial(print, file=sys.stderr)


def print_keymap(keytable, protocol, keys, output):
    print(f"\n# table: {keytable}, type: {protocol}", file=output)
    for keyname, scancode in keys.items():
        print(f"{scancode:#06x} {keyname}", file=output)


def read_from_device(dev):
    def btn_generator():
        for btn in BUTTONS:
            yield btn
        while True:
            yield None

    btn_gen = btn_generator()

    keymap = {}
    last_scancode = None
    last_ts = float('inf')

    button = next(btn_gen)
    first_btn = button
    with contextlib.closing(InputDevice(dev)) as dev:
        debug(f"Please press a button for {button}")

        for ev in dev.read_loop():
            advance_button = False
            if ev.type == ecodes.EV_MSC:
                ts = ev.timestamp()
                if (
                    ev.value == keymap.get(first_btn)
                    and last_scancode == keymap.get(first_btn)
                    and ts - last_ts > .5
                ):

                    # skip button because user pressed KEY_OK
                    # and it's not an unwanted repeat
                    advance_button = True
                    debug(f"skipped learning button {button}")
                elif last_scancode == ev.value:
                    # repeated key
                    pass
                elif ev.value != keymap.get(first_btn):
                    if ev.value < 0:
                        ev.value &= 0xffffffff
                    debug(f"Got scancode {ev.value:#06x} for {button}.")
                    advance_button = True
                    keymap[button] = ev.value
                    last_scancode = ev.value

                last_ts = ts
                last_scancode = ev.value

            if advance_button:
                button = next(btn_gen)
                if button:
                    debug(f"Please press a button for {button} (press KEY_OK to skip)")
                else:
                    break
        return keymap


def list_devices():
    devices = []
    for rc_device in RC_SYS_DEVICES.glob('rc*'):
        device = {}
        device["path"] = rc_device
        device["sys"] = rc_device.name

        with open(next(rc_device.glob('input*/event*/uevent'))) as f:
            # read DEVNAME attribute (and others)
            for line in f:
                key, value = line.rstrip().split('=')
                device[key] = value

        with open(rc_device.joinpath('uevent')) as f:
            for line in f:
                key, value = line.rstrip().split('=')
                device[key] = value

        with open(rc_device.joinpath('protocols')) as f:
            protocols = f.read().rstrip().split()
            active_protocols = [p[1:-1] for p in protocols if (
                p.startswith('[') and p != "[lirc]")]
            inactive_protocols = [p for p in protocols if not p.startswith('[')]
            device["protocols"] = protocols
            device["inactive_protocols"] = inactive_protocols
            device["active_protocols"] = active_protocols
            devices.append(device)
    return devices


if __name__ == '__main__':
    parser = ArgumentParser(description="create keymaps for rc-core devices")
    parser.add_argument('-p', '--protocol', metavar='PROTOCOL',
                        help='set ir-protocol')
    parser.add_argument('-d', '--device', metavar='DEVICE', default=None,
                        help='ir device (e.g. rc0)')
    parser.add_argument('-o', '--output', metavar='KEYMAP', default=None,
                        help='write the keymap to this file instead of printing to stdout')
    args = parser.parse_args()

    devices = list_devices()
    if not devices:
        sys.exit("No rc-core devices found. Exiting.")
    elif len(devices) == 1:
        print(f"Using device {devices[0]['NAME']}", file=sys.stderr)
        dev = devices[0]['path']
        input_dev = devices[0]['DEVNAME']
        keytable = devices[0]['NAME']
        protocol = ",".join(devices[0]['active_protocols'])
    else:
        if args.device:
            for d in devices:
                if (d['sys'] == args.device
                        or d['path'] == RC_SYS_DEVICES.joinpath(args.device)):
                    dev = RC_SYS_DEVICES.joinpath(args.device)
                    input_dev = d['DEVNAME']
                    keytable = d['NAME']
                    protocol = ",".join(d['active_protocols'])
        else:
            for i, d in enumerate(devices, start=1):
                if not args.protocol:
                    print(f"{i}) {d['DEV_NAME']} ({','.join(d.get('active_protocols'))})")
                else:
                    print(f"{i}) {d['DEV_NAME']}")
            try:
                dev_num = int(input("\tUse device numer: "))
                if dev_num < 0 or dev_num > len(devices):
                    raise ValueError
                d = devices[dev_num - 1]
            except (ValueError, IndexError):
                sys.exit("invalid device number")
            input_dev = d['DEVNAME']
            keytable = d['NAME']
            protocol = ",".join(d['active_protocols'])
            dev = d['path']

    # set ir-protocol(s)
    if args.protocol:
        try:
            with open(dev.joinpath('protocols'), 'w') as f:
                f.write(args.protocol)
        except PermissionError as e:
            sys.exit(f"Error: insifficient permissions to change protocol - are you root?")
        except IOError as e:
            sys.exit(f"Error: could not set protocol(s) to {args.protocol}: {e}")
        protocol = args.protocol

    input_dev = DEVPATH.joinpath(input_dev)
    debug("using device: ", input_dev)

    def signal_handler(signum, frame):
        sys.exit()

    for signame in {'SIGINT', 'SIGTERM'}:
        signal.signal(
            getattr(signal, signame),
            signal_handler)
    try:
        keymap = read_from_device(input_dev)
    except PermissionError as e:
        sys.exit(f"Error: can't open {input_dev} - are you root?")
    else:
        if args.output:
            with open(args.output, 'w') as output:
                print_keymap(keytable, protocol, keymap, output)
        else:
            print_keymap(keytable, protocol, keymap, None)

pulseaudio

tasks

---

- name: apt | install pulseaudio, pavucontrol and vdr-plugin-pulsecontrol
  apt:
    name:
      - alsa-base
      - alsa-utils
      - pulseaudio
      - pavucontrol
      - vdr-plugin-pulsecontrol
      - python3-pulsectl
      - python3-dasbus
    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

- name: "create /etc/dbus-1/system.d/org.yavdr.dbus_pulse_ctl.conf to configure access to dbus_pulsectl"
  template:
    src: "dbus_pulsectl/org.yavdr.dbus_pulsectl.conf.j2"
    dest: "/etc/dbus-1/system.d/org.yavdr.dbus_pulse_ctl.conf"

- name: ensure ~/bin exists for vdr user
  file:
    path: "{{ vdr.home }}/bin"
    owner: "{{ vdr.user }}"
    group: "{{ vdr.group}}"
    state: directory
    mode: 0755

- name: create dbus_pulsectl script
  template:
    src: "dbus_pulsectl/dbus_pulsectl.py.j2"
    dest: "{{ vdr.home }}/bin/dbus_pulsectl"
    mode: 0755

- name: ensure ~/.config/systemd/user exists for vdr user
  file:
    path: "{{ vdr.home }}/.config/systemd/user"
    state: directory
    owner: "{{ vdr.user }}"
    group: "{{ vdr.group }}"
    mode: 0755

- name: create dbus service for dbus_pulsectl
  template:
    src: dbus_pulsectl/dbus-pulsectl.service.j2
    dest: "{{ vdr.home }}/.config/systemd/user/dbus-pulsectl.service"
    owner: "{{ vdr.user }}"
    group: "{{ vdr.group }}"

    # TODO: better way to enable unit than in openbox autostart set by yavdr-desktop?
    

templates

{{ ansible_managed | comment }}
# 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"
}

dbus_pulsectl

This script allows to change the pulseaudio output device via dbus.

<!DOCTYPE busconfig PUBLIC
 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <!-- Only user vdr can own the dbus-service -->
  <policy user="{{ vdr.user }}">
    <allow own="org.yavdr.PulseDBusCtl"/>
  </policy>

  <!-- allow everyone to call to call the dbus methods -->
  <policy context="default">
    <allow send_destination="org.yavdr.PulseDBusCtl"/>
    <allow receive_sender="org.yavdr.PulseDBusCtl"/>
  </policy>

</busconfig>
#!/usr/bin/env python3
from dasbus.connection import SystemMessageBus
from dasbus.loop import EventLoop
import pulsectl

pulse = pulsectl.Pulse('pulse_dbus_ctl')
loop = EventLoop()
bus = SystemMessageBus()

class PulseDBusCtl(object):
    __dbus_xml__ = """
    <node>
        <interface name="org.yavdr.PulseDBusCtl">
            <method name="ListSinks">
                <!--
                    Returns an array of
                    structs containing data for each sink:
                      name: string
                      description: string
                      index: int32
                      muted: bool
                      number of channels: int32
                      volume_values: array of doubles
                      port_active: string one of ["yes", "no", "unknown"]
                    -->
                <arg direction="out" name="output_sinks" type="a(ssibiads)" />
            </method>
            <method name="SetDefaultSink">
                <!--
                    set the default sink by a given sink name, e.g.
                    'alsa_output.pci-0000_01_00.1.hdmi-stereo'
                -->
                <arg direction="in" name="sink_name" type="s" />
                <arg direction="out" name="success" type="b" />
            </method>
        </interface>
    </node>
    """

    def ListSinks(self):
        return [
            (
                s.name,
                s.description,
                s.index,
                s.mute,
                s.channel_count,
                s.volume.values,
                s.port_active.available_state._value
            ) for s in pulse.sink_list()
        ]

    def SetDefaultSink(self, sink_name: str) -> bool:
        try:
            target_sink = pulse.get_sink_by_name(sink_name)
        except:
            print("could not get target sink")
            return False
        try:
            pulse.sink_default_set(target_sink)
        except Exception as e:
            print(e)
            return False

        # move all streams to the new default sink
        for stream in pulse.sink_input_list():
            pulse.sink_input_move(stream.index, target_sink.index)
        return True


bus.publish_object("/org/yavdr/PulseDBusCtl", PulseDBusCtl())
bus.register_service("org.yavdr.PulseDBusCtl")
try:
    loop.run()
except KeyboardInterrupt:
    loop.quit()
    pulse.disconnect()
{{ ansible_managed | comment }}
[Unit]
Description=Control pulseaudio output via DBus

[Service]
Type=simple
ExecStart={{ vdr.home }}/bin/dbus_pulsectl
KillSignal=SIGINT

[Install]
WantedBy=yavdr-desktop.target

session-common

This role prepares a basic systemd user session

tasks

---
- name: install required packages
  apt:
    name:
      - tmux

- name: create ~/.config/systemd/user
  file:
    path: "{{ vdr.home }}/.config/systemd/user"
    state: directory
    owner: "{{ vdr.user }}"
    group: "{{ vdr.group }}"
    mode: 0755

- name: make vdr.user owner of ~/.config
  file:
    path: "{{ vdr.home }}/.config"
    state: directory
    recurse: true
    owner: "{{ vdr.user }}"
    group: "{{ vdr.group }}"
    mode: 0755

- 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 }}'

templates

tmux.service

[Unit]
Description=Start tmux in detached session

[Service]
EnvironmentFile=-%h/.session-env
Type=forking
WorkingDirectory=%h
ExecStart=/usr/bin/tmux new-session -s %u -d
ExecStop=/usr/bin/tmux kill-session -t %u
Restart=always

[Install]
WantedBy=default.target

headless-session

About the headless session

This configures a headless login session for the VDR user, so we can run services like udiskie in the context of a systemd user session.

default variables

enable_headless_session: true

dependencies

---
dependencies:
    - role: session-common
    - role: udiskie

Tasks

main.yml

- name: create ~/.config
  file:
    path: "{{ vdr.home }}/.config"
    state: directory
    owner: "{{ vdr.user }}"
    group: "{{ vdr.group }}"
    mode: 0755

- name: expand template for ~/.config/headless-session
  template:
    src: templates/config/headless-session.j2
    dest: "{{ vdr.home }}/.config/headless-session"
    owner: "{{ vdr.user }}"
    group: "{{ vdr.group }}"
    mode: 0755

- name: expand template for headless-session@.service
  template:
    src: templates/systemd/headless-session@.service.j2
    dest: /etc/systemd/system/headless-session@.service

- name: start headless-session@.service for vdr user
  systemd:
    name: "headless-session@{{ vdr.user }}.service"
    state: started
    enabled: true
    masked: no

- name: enable services for the user session
  systemd:
    name: "{{ item }}"
    state: started
    masked: no
    enabled: yes
    scope: user
  become: yes
  become_user: vdr
  loop:
    - tmux.service
    - udiskie.service

- name: disable services for the user session
  systemd:
    name: "{{ item }}"
    state: stopped
    masked: yes
    enabled: no
    scope: user
  become: yes
  become_user: vdr
  loop:
    - detect-second-display.service
    - openbox-second.service
    - osd2web.service
    - pulseaudio.socket
    - pulseaudio.service
    - yavdr-frontend.service
    - wm-exit.service
  ignore_errors: yes

templates

Systemd Service

[Unit]
Description=headless login for user %i
After=systemd-user-sessions.service
Conflicts=getty@tty7.service

[Service]
User=%i
WorkingDirectory=~
TTYPath=/dev/tty7
PAMName=login
ExecStart=/bin/bash -l .config/headless-session

[Install]
WantedBy=graphical.target

Configuration file

while :
do
    sleep infinity
done

udiskie

This role installs and preconfigures udiskie

tasks

- name: install udiskie
  apt:
    name:
      - policykit-1
      - udiskie
    state: present

- name: create ~/.config/udiskie
  file:
    name: '{{ vdr.home }}/.config/udiskie'
    state: directory
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'

- name: expand template for udiskie's config.yml
  template:
    src: templates/config.yml.j2
    dest: '{{ vdr.home }}/.config/udiskie/config.yml'
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'


- name: create ~/.config/systemd/user
  file:
    name: '{{ vdr.home }}/.config/systemd/user'
    state: directory
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'

- name: expand template for udiskie's systemd unit
  template:
    src: templates/udiskie.service.j2
    dest: '{{ vdr.home }}/.config/systemd/user/udiskie.service'
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'

- name: create ~/bin
  file:
    name: '{{ vdr.home }}/bin'
    state: directory
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'

- name: expand template for mount helper script
  template:
    src: templates/udiskie_vdr_mount_helper.j2
    dest: '{{ vdr.home }}/bin/udiskie_vdr_mount_helper'
    mode: 0755
    owner: "{{ vdr.user }}"
    group: "{{ vdr.group }}"

- name: expand template for udiskie vdr commands
  template:
    src: templates/umount_all.j2
    dest: /usr/share/vdr/command-hooks/commands.udiskie.conf

templates

config.yml

{{ ansible_managed | comment }}

program_options:
  tray:             false    # [bool] Enable the tray icon. "auto"
  menu:             flat    # ["flat" | "nested"] Set the
  automount:        true   # [bool] Enable automatic mounting.
  notify:           true    # [bool] Enable notifications.
  password_cache:   false      # [int] Password cache in minutes. Caching is

  file_manager:     ""
  notify_command: "{{ vdr.home }}/bin/udiskie_vdr_mount_helper '{event}' '{device_presentation}' '{mount_path}'"

device_config:
  - is_loop: true
    ignore: true

  - is_external: false
    ignore: true

notifications:
  device_mounted:   5       # mount notification
  device_unmounted: true   # unmount notification
  device_added:     true   # device has appeared
  device_removed:   true   # device has disappeared

mount helper script

{{ ansible_managed | comment }}

videodir="{{ vdr.recdir }}"
event="$1"
device_node="$2"
mount_path="$3"


logger -t "mount-notification" "event: $event, device: $device_node, mount_path: $mount_path"
case "$event" in
    'device_mounted')
        [[ "$mount_path" =~ ^/media/{{ vdr.user }}/ ]] || {
            logger -t "skip mount event" "ignoring mounts outside /media/{{ vdr.user }}/"
            exit 0
        }
        target="${videodir}/$(basename "${mount_path}")"
        ln -s -T "$mount_path" "$target" || {
            logger -t "vdr recordings found" "mountpoint already exists, aborting"
            exit
        }
        # check if we got a vdr recording on the mountpoint
        if [ -n $(find "$mount_path" -name "*.rec" -print -quit 2>/dev/null) ]
        then
            vdr-dbus-send /Skin skin.QueueMessage string:"$mount_path mounted (with recordings)"
            svdrpsend updr
        else
            vdr-dbus-send /Skin skin.QueueMessage string:"$mount_path' mounted"
        fi
        ;;
    'device_unmounted')
        removed_symlinks=($(find "$videodir" -xtype l -delete -print))
        logger -t "device umounted" "remove unneeded symlinks: $(paste -d " " <<< "${removed_symlinks[@]}")"
        vdr-dbus-send /Skin skin.QueueMessage string:"$device_node umounted"
        svdrpsend updr
        ;;
    'device_removed')
        removed_symlinks=($(find "$videodir" -xtype l -delete -print))
        [ -z "$device_node" ] && exit
        logger -t "device removed" "remove unneeded symlinks: $(paste -d " " <<< "${removed_symlinks[@]}")"
        vdr-dbus-send /Skin skin.QueueMessage string:"$device_node removed"
        svdrpsend updr
        ;;
    'job_failed')
        if [ -n "$mount_path" ]
        then
            logger -t "umount failed" "could not unmount $mount_path"
        else
            logger -t "operation failed" 'could not mount(?) '"$device_node"
        fi
        ;;
esac

udiskie snippet for commands.conf

{{ ansible_managed | comment }}

{{ "Safely remove usb mass storage" | translate }} : echo 'svdrpsend mesg "$(udiskie-umount -a 2>&1 | grep -o "Error unmounting.*")"' | at now

udiskie.service

[Unit]
Description=dynamic mounts for the user session

[Service]
ExecStart=/usr/bin/udiskie

[Install]
WantedBy=default.target

yavdr-xorg

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).

STARTED automatic X-server configuration

  • State “STARTED” from “DONE” [2019-03-27 Mi 15:23]
  • State “DONE” from “TODO” [2019-03-27 Mi 15:23]
  • [X] detect connected display
  • [X] read EDID from displays

create a xorg.conf for

  • [X] nvidia
  • [X] intel
  • [X] amd gpus
  • [ ] allow overscan compensation in xorg.conf or via xrandr on startup

Missing steps:

  • [X] X-Server configuration for AMD graphics cards
  • [X] load edids by setting kernel boot arguments

AMD-GPUs

  • State “DONE” from “TODO” [2020-09-23 Mi 21:09]
Example configuration
Section "ServerLayout"
	Identifier     "X.org Configured"
	Screen      0  "Screen0" 0 0
	Screen      1  "Screen1" RightOf "Screen0"
EndSection

Section "Monitor"
	Identifier   "Monitor0"
	Modeline "1920x1080_60"  148.50 1920 2008 2052 2200 1080 1084 1089 1125 +hsync +vsync
	Modeline "3840x2160_60"  533.25 3840 3888 3920 4000 2160 2163 2168 2222 +hsync -vsync
	Modeline "1920x1080_60"  148.500 1920 2008 2052 2200 1080 1084 1089 1125 +hsync +vsync
	Modeline "1280x720_60"  74.250 1280 1390 1420 1650 720 725 730 750 +hsync +vsync
	Modeline "720x480_60"  27.027 720 736 798 858 480 489 495 525 -hsync -vsync
	Modeline "720x480_60"  27.027 720 736 798 858 480 489 495 525 -hsync -vsync
	Modeline "1440x480_60"  54.054 1440 1472 1596 1716 480 489 495 525 -hsync -vsync
	Modeline "1920x1080_50"  148.500 1920 2448 2492 2640 1080 1084 1089 1125 +hsync +vsync
	Modeline "1920x1080_25"  74.250 1920 2448 2492 2640 1080 1084 1089 1125 +hsync +vsync
	Modeline "1920x1080_25"  74.250 1920 2448 2492 2640 1080 1082 1089 1125 +hsync +vsync interlace
	Modeline "1280x720_50"  74.250 1280 1720 1760 1980 720 725 730 750 +hsync +vsync
	Modeline "720x576_50"  27.000 720 732 796 864 576 581 586 625 -hsync -vsync
	Modeline "1440x576_25"  27.000 1440 1464 1590 1728 576 578 581 625 -hsync -vsync interlace
	Modeline "640x480_60"  25.200 640 656 752 800 480 490 492 525 -hsync -vsync
	Modeline "2560x1440_60"  241.50 2560 2608 2640 2720 1440 1443 1448 1481 +hsync -vsync
EndSection

Section "Monitor"
	Identifier   "Monitor1"
	Option "Ignore" "true"
EndSection

Section "Device"
        ### Available Driver options are:-
        ### Values: <i>: integer, <f>: float, <bool>: "True"/"False",
        ### <string>: "String", <freq>: "<f> Hz/kHz/MHz",
        ### <percent>: "<f>%"
        ### [arg]: arg optional
        #Option     "Accel"              	# [<bool>]
        #Option     "SWcursor"           	# [<bool>]
        #Option     "EnablePageFlip"     	# [<bool>]
        #Option     "SubPixelOrder"      	# [<str>]
        #Option     "ZaphodHeads"        	# <str>
        #Option     "AccelMethod"        	# <str>
        #Option     "DRI3"               	# [<bool>]
        #Option     "DRI"                	# <i>
        #Option     "ShadowPrimary"      	# [<bool>]
        #Option     "TearFree"           	# [<bool>]
        #Option     "DeleteUnusedDP12Displays" 	# [<bool>]
        #Option     "VariableRefresh"    	# [<bool>]
	Identifier  "Card0"
	Driver      "amdgpu"
	Option	    "TearFree" "true"
	Option      "DRI3" "true"
	Option      "DRI" "3"
	Option      "VariableRefresh" "true"
	BusID       "PCI:10:0:0"
EndSection

Section "Device"
        ### Available Driver options are:-
        ### Values: <i>: integer, <f>: float, <bool>: "True"/"False",
        ### <string>: "String", <freq>: "<f> Hz/kHz/MHz",
        ### <percent>: "<f>%"
        ### [arg]: arg optional
        #Option     "Accel"              	# [<bool>]
        #Option     "SWcursor"           	# [<bool>]
        #Option     "EnablePageFlip"     	# [<bool>]
        #Option     "SubPixelOrder"      	# [<str>]
        #Option     "ZaphodHeads"        	# <str>
        #Option     "AccelMethod"        	# <str>
        #Option     "DRI3"               	# [<bool>]
        #Option     "DRI"                	# <i>
        #Option     "ShadowPrimary"      	# [<bool>]
        #Option     "TearFree"           	# [<bool>]
        #Option     "DeleteUnusedDP12Displays" 	# [<bool>]
        #Option     "VariableRefresh"    	# [<bool>]
	Identifier  "Card1"
	Driver      "amdgpu"
        Option      "TearFree" "true"
	Option      "DRI3" "true"        
	Option      "DRI" "3"
        Option      "VariableRefresh" "true
	BusID       "PCI:10:0:1"
EndSection

Section "Screen"
	Identifier "Screen0"
	Device     "Card0"
	Monitor    "Monitor0"
	SubSection "Display"
		Viewport   0 0
		Depth     24
		Modes     "3840x2160_60"
		#Modes     "1920x1080_60"
	EndSubSection
EndSection

Section "Screen"
	Identifier "Screen1"
	Device     "Card1"
	Monitor    "Monitor1"
	SubSection "Display"
		Viewport   0 0
		Depth     24
		#Modes     "3840x2160_60"
	EndSubSection
EndSection

Nvidia-GPUs:read EDID:

  • State “DONE” from “HOLD” [2019-03-27 Mi 15:22]
$ nvidia-xconfig --extract-edids-from-file=/var/log/Xorg.0.log --extract-edids-output-file=/tmp/edid.bin.0

Found 2 EDIDs in "/var/log/Xorg.0.log".
  Wrote EDID for "DELL 2407WFP (CRT-1)" to "/tmp/edid.bin.0.0" (128 bytes).
  Wrote EDID for "ADI A715 (DFP-1)" to "/tmp/edid.bin.0.1" (128 bytes).

$ xrandr -q
Screen 0: minimum 8 x 8, current 3200 x 1200, maximum 8192 x 8192
DVI-I-0 disconnected primary (normal left inverted right x axis y axis)
VGA-0 connected 1920x1200+1280+0 (normal left inverted right x axis y axis) 519mm x 324mm
   1920x1200     59.95*+
   1680x1050     59.95
   1280x1024     75.02    60.02
   1152x864      75.00
   1024x768      75.03    60.00
   800x600       75.00    60.32
   640x480       75.00    59.94
DVI-I-1 disconnected (normal left inverted right x axis y axis)
HDMI-0 connected 1280x1024+0+0 (normal left inverted right x axis y axis) 338mm x 270mm
   1280x1024     60.02*+
   1024x768      60.00
   800x600       60.32
   640x480       59.95    59.94

$ parse-edid < /tmp/edid.bin.0.1
Checksum Correct

Section "Monitor"
    Identifier "ADI A715"
    ModelName "ADI A715"
    VendorName "ADI"
    # Monitor Manufactured week 15 of 2003
    # EDID version 1.3
    # Digital Display
    DisplaySize 330 270
    Gamma 2.20
    Option "DPMS" "true"
    #Not giving standard mode: 640x480, 60Hz
    #Not giving standard mode: 800x600, 60Hz
    #Not giving standard mode: 1024x768, 60Hz
    #Not giving standard mode: 1280x1024, 60Hz
    Modeline    "Mode 0" 108.00 1280 1328 1440 1688 1024 1025 1028 1066 +hsync +vsync
    Modeline    "Mode 1" 40.00 800 840 968 1056 600 601 605 628 +hsync +vsync
EndSection

$ parse-edid < /tmp/edid.bin.0.0
Checksum Correct

Section "Monitor"
    Identifier "DELL 2407WFP"
    ModelName "DELL 2407WFP"
    VendorName "DEL"
    # Monitor Manufactured week 24 of 2007
    # EDID version 1.3
    # Analog Display
    Option "SyncOnGreen" "true"
    DisplaySize 520 330
    Gamma 2.20
    Option "DPMS" "true"
    Horizsync 30-83
    VertRefresh 56-76
    # Maximum pixel clock is 170MHz
    #Not giving standard mode: 1280x1024, 60Hz
    #Not giving standard mode: 1600x1200, 60Hz
    #Not giving standard mode: 1152x864, 75Hz
    #Not giving standard mode: 1680x1050, 60Hz
    Modeline    "Mode 0" 154.00 1920 1968 2000 2080 1200 1203 1209 1235 +hsync -vsync
EndSection

Start X-server with debug-output

# /etc/systemd/system/x-debug@.service
[Unit]
Description=X with verbose logging on %I
Wants=graphical.target
Before=graphical.target
Conflicts=xlogin@vdr.service x@vt7.service

[Service]
Type=forking
ExecStart=/usr/bin/x-daemon -logverbose 6 -noreset %I -config xdiscover.conf
# /etc/X11/xdiscover.conf
Section "Device"
        Identifier "nvidia"
        Driver  "nvidia"
        Option  "NoLogo"              "true"
        Option  "DynamicTwinView"     "true"
        Option  "NoFlip"              "false"
#        Option  "FlatPanelProperties" "Scaling = Native"
#        Option  "ModeValidation"      "NoVesaModes, NoXServerModes"
#        Option  "ModeDebug"           "true"
#        Option  "HWCursor"            "false"
EndSection

Section "Screen"
        Identifier      "screen"
        Device          "nvidia"
EndSection

Section "Extensions"
        Option  "Composite"           "false"
EndSection

python-script for parsing xrandr –verbose output

Example output
$ xrandr --verbose
Screen 0: minimum 8 x 8, current 1280 x 720, maximum 8192 x 8192
VGA-0 disconnected primary (normal left inverted right x axis y axis)
    Identifier: 0x1c4
    Timestamp:  18571
    Subpixel:   unknown
    Clones:
    CRTCs:      0 1
    Transform:  1.000000 0.000000 0.000000
                0.000000 1.000000 0.000000
                0.000000 0.000000 1.000000
               filter:
    BorderDimensions: 4
        supported: 4
    Border: 0 0 0 0
        range: (0, 65535)
    SignalFormat: VGA
        supported: VGA
    ConnectorType: VGA
    ConnectorNumber: 0
    _ConnectorLocation: 1
HDMI-0 connected 1280x720+0+0 (0x1cb) normal (normal left inverted right x axis y axis) 885mm x 498mm
    Identifier: 0x1c5
    Timestamp:  18571
    Subpixel:   unknown
    Gamma:      1.0:1.0:1.0
    Brightness: 1.0
    Clones:
    CRTC:       0
    CRTCs:      0 1fg
    Transform:  1.000000 0.000000 0.000000
                0.000000 1.000000 0.000000
                0.000000 0.000000 1.000000
               filter:
    EDID:
        00ffffffffffff004c2d800100000000
        2c0e01038059328c0ae2bda15b4a9824
        15474a20000001010101010101010101
        010101010101011d007251d01e206e28
        550075f23100001e011d00bc52d01e20
        b828554075f23100001e000000fd0032
        3d0f2e08000a202020202020000000fc
        0053414d53554e470a20202020200181
        02031971468413051403122309070783
        01000065030c001000011d8018711c16
        20582c250075f23100009e011d80d072
        1c1620102c258075f23100009e8c0ad0
        8a20e02d10103e960075f2310000188c
        0ad090204031200c40550075f2310000
        18000000000000000000000000000000
        000000000000000000000000000000ca
    BorderDimensions: 4
        supported: 4
    Border: 39 24 41 21
        range: (0, 65535)
    SignalFormat: TMDS
        supported: TMDS
    ConnectorType: HDMI
    ConnectorNumber: 1
    _ConnectorLocation: 2
  1280x720 (0x1c6)   74.2MHz +HSync +VSync +preferred
        h: width  1280 start 1390 end 1430 total 1650 skew    0 clock   45.0KHz
        v: height  720 start  725 end  730 total  750           clock   60.0Hz
  1920x1080 (0x1c7)   74.2MHz +HSync +VSync Interlace
        h: width  1920 start 2008 e#nd 2052 total 2200 skew    0 clock   33.8KHz
        v: height 1080 start 1084 end 1094 total 1124           clock   60.1Hz
  1920x1080 (0x1c8)   74.2MHz +HSync +VSync Interlace
        h: width  1920 start 2008 end 2052 total 2200 skew    0 clock   33.7KHz
        v: height 1080 start 1084 end 1094 total 1124           clock   60.0Hz
  1920x1080 (0x1c9)   74.2MHz +HSync +VSync Interlace
        h: width  1920 start 2448 end 2492 total 2640 skew    0 clock   28.1KHz
        v: height 1080 start 1084 end 1094 total 1124           clock   50.0Hz
  1280x720 (0x1ca)   74.2MHz +HSync +VSync
        h: width  1280 start 1390 end 1430 total 1650 skew    0 clock   45.0KHz
        v: height  720 start  725 end  730 total  750           clock   59.9Hz
  1280x720 (0x1cb)   74.2MHz +HSync +VSync *current
        h: width  1280 start 1720 end 1760 total 1980 skew    0 clock   37.5KHz
        v: height  720 start  725 end  730 total  750           clock   50.0Hz
  800x600 (0x1cc)   40.0MHz +HSync +VSync
        h: width   800 start  840 end  968 total 1056 skew    0 clock   37.9KHz
        v: height  600 start  601 end  605 total  628           clock   60.3Hz
  800x600 (0x1cd)   36.0MHz +HSync +VSync
        h: width   800 start  824 end  896 total 1024 skew    0 clock   35.2KHz
        v: height  600 start  601 end  603 total  625           clock   56.2Hz
  720x576 (0x1ce)   27.0MHz -HSync -VSync
        h: width   720 start  732 end  796 total  864 skew    0 clock   31.2KHz
        v: height  576 start  581 end  586 total  625           clock   50.0Hz
  720x480 (0x1cf)   27.0MHz -HSync -VSync
        h: width   720 start  736 end  798 total  858 skew    0 clock   31.5KHz
        v: height  480 start  489 end  495 total  525           clock   59.9Hz
  640x480 (0x1d0)   25.2MHz -HSync -VSync
        h: width   640 start  656 end  752 total  800 skew    0 clock   31.5KHz
        v: height  480 start  490 end  492 total  525           clock   59.9Hz
  320x240 (0x1d1)   12.6MHz -HSync -VSync DoubleScan
        h: width   320 start  328 end  376 total  400 skew    0 clock   31.5KHz
        v: height  240 start  245 end  246 total  262           clock   60.1Hz
$ xrandr --verbose
Screen 0: minimum 8 x 8, current 3200 x 1200, maximum 8192 x 8192
DVI-I-0 disconnected primary (normal left inverted right x axis y axis)
    Identifier: 0x1c4
    Timestamp:  641679
    Subpixel:   unknown
    Clones:
    CRTCs:      0 1
    Transform:  1.000000 0.000000 0.000000
                0.000000 1.000000 0.000000
                0.000000 0.000000 1.000000
               filter:
    BorderDimensions: 4
        supported: 4
    Border: 0 0 0 0
        range: (0, 65535)
    SignalFormat: VGA
        supported: VGA
    ConnectorType: DVI-I
    ConnectorNumber: 0
    _ConnectorLocation: 0
VGA-0 connected 1920x1200+1280+0 (0x1c6) normal (normal left inverted right x axis y axis) 519mm x 324mm
    Identifier: 0x1c5
    Timestamp:  641679
    Subpixel:   unknown
    Gamma:      1.0:1.0:1.0
    Brightness: 1.0
    Clones:
    CRTC:       1
    CRTCs:      0 1
    Transform:  1.000000 0.000000 0.000000
                0.000000 1.000000 0.000000
                0.000000 0.000000 1.000000
               filter:
    EDID:
        00ffffffffffff0010ac16a0534b4431
        181101030e342178eeee91a3544c9926
        0f5054a54b008180a940714fb3000101
        010101010101283c80a070b023403020
        360007442100001a000000ff00555935
        343537364531444b5320000000fc0044
        454c4c20323430375746500a000000fd
        00384c1e5311000a20202020202000f1
    BorderDimensions: 4
        supported: 4
    Border: 0 0 0 0
        range: (0, 65535)
    SignalFormat: VGA
        supported: VGA
    ConnectorType: VGA
    ConnectorNumber: 2
    _ConnectorLocation: 2
  1920x1200 (0x1c6) 154.000MHz +HSync -VSync *current +preferred
        h: width  1920 start 1968 end 2000 total 2080 skew    0 clock  74.04KHz
        v: height 1200 start 1203 end 1209 total 1235           clock  59.95Hz
  1680x1050 (0x1c7) 146.250MHz -HSync +VSync
        h: width  1680 start 1784 end 1960 total 2240 skew    0 clock  65.29KHz
        v: height 1050 start 1053 end 1059 total 1089           clock  59.95Hz
  1280x1024 (0x1c8) 135.000MHz +HSync +VSync
        h: width  1280 start 1296 end 1440 total 1688 skew    0 clock  79.98KHz
        v: height 1024 start 1025 end 1028 total 1066           clock  75.02Hz
  1280x1024 (0x1c9) 108.000MHz +HSync +VSync
        h: width  1280 start 1328 end 1440 total 1688 skew    0 clock  63.98KHz
        v: height 1024 start 1025 end 1028 total 1066           clock  60.02Hz
  1152x864 (0x1ca) 108.000MHz +HSync +VSync
        h: width  1152 start 1216 end 1344 total 1600 skew    0 clock  67.50KHz
        v: height  864 start  865 end  868 total  900           clock  75.00Hz
  1024x768 (0x1cb) 78.750MHz +HSync +VSync
        h: width  1024 start 1040 end 1136 total 1312 skew    0 clock  60.02KHz
        v: height  768 start  769 end  772 total  800           clock  75.03Hz
  1024x768 (0x1cc) 65.000MHz -HSync -VSync
        h: width  1024 start 1048 end 1184 total 1344 skew    0 clock  48.36KHz
        v: height  768 start  771 end  777 total  806           clock  60.00Hz
  800x600 (0x1cd) 49.500MHz +HSync +VSync
        h: width   800 start  816 end  896 total 1056 skew    0 clock  46.88KHz
        v: height  600 start  601 end  604 total  625           clock  75.00Hz
  800x600 (0x1ce) 40.000MHz +HSync +VSync
        h: width   800 start  840 end  968 total 1056 skew    0 clock  37.88KHz
        v: height  600 start  601 end  605 total  628           clock  60.32Hz
  640x480 (0x1cf) 31.500MHz -HSync -VSync
        h: width   640 start  656 end  720 total  840 skew    0 clock  37.50KHz
        v: height  480 start  481 end  484 total  500           clock  75.00Hz
  640x480 (0x1d0) 25.175MHz -HSync -VSync
        h: width   640 start  656 end  752 total  800 skew    0 clock  31.47KHz
        v: height  480 start  490 end  492 total  525           clock  59.94Hz
DVI-I-1 disconnected (normal left inverted right x axis y axis)
    Identifier: 0x1d1
    Timestamp:  641679
    Subpixel:   unknown
    Clones:
    CRTCs:      0 1
    Transform:  1.000000 0.000000 0.000000
                0.000000 1.000000 0.000000
                0.000000 0.000000 1.000000
               filter:
    BorderDimensions: 4
        supported: 4
    Border: 0 0 0 0
        range: (0, 65535)
    SignalFormat: TMDS
        supported: TMDS
    ConnectorType: DVI-I
    ConnectorNumber: 0
    _ConnectorLocation: 0
HDMI-0 connected 1280x1024+0+0 (0x1c9) normal (normal left inverted right x axis y axis) 338mm x 270mm
    Identifier: 0x1d2
    Timestamp:  641679
    Subpixel:   unknown
    Gamma:      1.0:1.0:1.0
    Brightness: 1.0
    Clones:
    CRTC:       0
    CRTCs:      0 1
    Transform:  1.000000 0.000000 0.000000
                0.000000 1.000000 0.000000
                0.000000 0.000000 1.000000
               filter:
    EDID:
        00ffffffffffff0004895d2320090000
        0f0d0103e0211b782ac5c6a3574a9c23
        124f5421080031404540614081800101
        010101010101302a009851002a403070
        1300520e1100001ea00f200031581c20
        28801400520e1100001e000000ff0033
        31355430324530323333360a000000fc
        0041444920413731350a20202020002b
    BorderDimensions: 4
        supported: 4
    Border: 0 0 0 0
        range: (0, 65535)
    SignalFormat: TMDS
        supported: TMDS
    ConnectorType: HDMI
    ConnectorNumber: 1
    _ConnectorLocation: 1
  1280x1024 (0x1c9) 108.000MHz +HSync +VSync *current +preferred
        h: width  1280 start 1328 end 1440 total 1688 skew    0 clock  63.98KHz
        v: height 1024 start 1025 end 1028 total 1066           clock  60.02Hz
  1024x768 (0x1cc) 65.000MHz -HSync -VSync
        h: width  1024 start 1048 end 1184 total 1344 skew    0 clock  48.36KHz
        v: height  768 start  771 end  777 total  806           clock  60.00Hz
  800x600 (0x1ce) 40.000MHz +HSync +VSync
        h: width   800 start  840 end  968 total 1056 skew    0 clock  37.88KHz
        v: height  600 start  601 end  605 total  628           clock  60.32Hz
  640x480 (0x1d3) 25.180MHz -HSync -VSync
        h: width   640 start  648 end  744 total  800 skew    0 clock  31.48KHz
        v: height  480 start  482 end  484 total  525           clock  59.95Hz
  640x480 (0x1d0) 25.175MHz -HSync -VSync
        h: width   640 start  656 end  752 total  800 skew    0 clock  31.47KHz
        v: height  480 start  490 end  492 total  525           clock  59.94Hz
parse hex-strings
>>> import binascii
>>> s = "deadbeef"
>>> binascii.a2b_hex(s)
b'\xde\xad\xbe\xef'

dependencies

---
dependencies:
    - role: collect-facts
    - role: stop-vdr
    - role: stop-xorg

default variables

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

intel_boot_options: ""
intel_set_boot_edid: false
intel_driver: "intel"  # choose one of "intel" and "modesetting"
softhddevice_intel_output_method: "va-api"  # choose one of va-api, va-api-glx, va-api-egl
nvidia_force_dpi: 0

yavdr_frontend:
    attach_on_startup: auto  # choose one of auto, always or never

tasks

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]
- import_tasks: intel.yml
  tags: [xorg.detect,install]
  when: intel_detected | bool and not nvidia_detected | bool

setup-xorg.yml

---
# file: roles/yavdr-xorg/tasks/setup-xorg.yml
- name: create directories for systemd drop-in files
  file:
    dest: '{{ item }}'
    state: directory
  loop:
    - "/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
  notify: ["Reload Units"]

- 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
  notify: ["Reload Units"]

- 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
  notify: ["Reload Units"]

- 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'
  notify: ["Reload Units"]

- name: vdr-config | create softhddevice.conf if it doesn't exist yet
  lineinfile:
    path: /etc/vdr/conf.avail/softhddevice.conf
    create: yes
    state: present
    line: '[softhddevice]'

- name: vdr-config | start softhddevice detached and set audio to pulseaudio
  lineinfile:
    path: /etc/vdr/conf.avail/softhddevice.conf
    line: '{{ item }}'
  loop:
    - '-D'
    - '-w alsa-driver-broken'
    # - '-a pulse' # do we need this with our existing asound.conf?

- name: vdr-config | set intel video output method for softhddevice
  lineinfile:
    path: /etc/vdr/conf.avail/softhddevice.conf
    state: present
    regexp: "^-v .*"
    line: "-v {{ softhddevice_intel_output_method | default('va-api') }}"
  when: (intel_detected and not nvidia_detected)

- 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:
      - yavdr-xorg
      - edid-decode
      - python3-dbus2vdr
      - python3-yavdrfrontend
      - tmux
      - vdr-plugin-desktop
      - feh
      - xdg-utils
    state: present

- name: apt | install intel xorg driver
  apt:
    name:
      - xserver-xorg-video-intel
    state: present
  when: intel_driver == 'intel'

- name: set the vdr instance id for yavdr-frontend
  yaml_edit:
    path: /etc/yavdr-frontend/config.yml
    key: vdr.id
    int_value: "{{ vdr.instance_id | default(0) }}"

- name: yavdr-frontend | set attach on startup to "{{ yavdr_frontend.attach_on_startup }}"
  yaml_edit:
    path: /etc/yavdr-frontend/config.yml
    key: vdr.attach_on_startup
    str_value: "{{ yavdr_frontend.attach_on_startup }}"

- name: apt | install packages for Intel IGP
  apt:
    name:
      - i965-va-driver-shaders
      - intel-media-va-driver-non-free
    state: present
  when: intel_detected | bool and not nvidia_detected | bool

- name: add ppa:oibaf/graphics-drivers for up to date mesa drivers
  apt_repository:
    repo: ppa:oibaf/graphics-drivers
  when: amd_detected | bool and not nvidia_detected | bool
  register: oibaf_ppa

- name: update package lists and all packages after adding ppa:oibaf/graphics-drivers
  apt:
    upgrade: dist
    update_cache: yes
  when: oibaf_ppa.changed

- name: apt | install packages for AMD GPUs
  apt:
    name:
      - mesa-vdpau-drivers
      - mesa-va-drivers
    state: present
  when: amd_detected | bool and not nvidia_detected | bool

- 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

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"
  notify: ["Reload Units"]

- 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: remove amd snippet
  file:
    path: /etc/X11/xorg.conf.d/20-amd.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 /etc/yavdr/autoinstalled if a nvidia driver has been installed
  lineinfile:
    name: /etc/yavdr/autoinstalled
    regexp: '.*nvidia.*'
    state: absent
  check_mode: yes
  register: nvidia_driver_detected

- name: set_fact | nvidia_driver_installed
  set_fact:
    nvidia_driver_installed: '{{ nvidia_driver_detected.changed | bool }}'

- 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
      rmmod:
        name: '{{ item }}'
      loop:
        - nouveau
        - ttm
        - drm_kms_helper
        - drm

    - 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
      ignore_errors: yes
  when:
    - nvidia_detected | bool
    - nvidia_driver_installed | bool
    - '"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 }}'

- name: show parsed xrandr data
  debug:
    var: xrandr

- name: show primary display configuration
  debug:
    var: xorg.primary

- name: show secondary display configuration
  debug:
    var: xorg.secondary
  when: xorg.secondary is defined

- name: show drm data (emtpy for nvidia)
  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 | bool

- name: intel xorg config snippet
  template:
    src: templates/20-intel.conf.j2
    dest: /etc/X11/xorg.conf.d/20-intel.conf
  when:
    - intel_detected | bool and not nvidia_detected | bool

- name: amd xorg config snippet
  template:
    src: templates/20-amd.conf.j2
    dest: /etc/X11/xorg.conf.d/20-amd.conf
  when:
    - amd_detected | bool and not nvidia_detected | bool

- 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

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.

TODO: create clean-edids script/role/Playbook to undo kernel boot options for edid loading

- name: "create initramfs hook to copy EDID files"
  template:
    src: templates/include-edid-data.sh.j2
    dest: '/etc/initramfs-tools/hooks/include-edid-data'
    mode: 0755
    force: yes

- name: "create /lib/firmware/edid"
  file:
    state: directory
    dest: /lib/firmware/edid

- name: "set intel_boot_options variable"
  set_fact:
    intel_boot_options: "video={{ drm.primary.drm_connector }}:D {% if drm.secondary is defined and drm.secondary.drm_connector is defined %}video={{ drm.secondary.drm_connector }}:D{% endif %} drm.edid_firmware={{ drm.primary.drm_connector }}:edid/{{ drm.primary.edid }}{% if drm.secondary is defined and drm.secondary.drm_connector is defined and drm.secondary.edid is defined %},{{ drm.secondary.drm_connector }}:edid/{{ drm.secondary.edid }}{% endif %}{% for ignored_c in drm.ignored_outputs %} video={{ ignored_c }}:d{% endfor %}"
  when: intel_set_boot_edid | bool
  notify: ['Update Initramfs', 'Update GRUB']

templates

xorg

x-verbose@.service
{{ ansible_managed | comment }}
[Unit]
Description=X with verbose logging on %I
Wants=graphical.target
Before=graphical.target

[Service]
Type=forking
ExecStartPre=/usr/bin/bash -c "printf '\0' > /sys/module/drm/parameters/edid_firmware"
ExecStartPre=-/usr/bin/bash -c 'shopt -s nullglob; for e in /sys/kernel/debug/dri/[0-9]/*/edid_override; do echo -n reset > "$$e"; done'
ExecStartPre=/usr/bin/bash -c 'shopt -s nullglob; for e in /sys/class/drm/card[0-9]*/status; do echo -n detect > "$$e"; done'
ExecStart=/usr/bin/x-daemon -logverbose 6 -noreset %I -config /etc/X11/xorg-verbose.conf
Drop-in snippet for x@.service
{{ ansible_managed | comment }}
[Unit]
After=plymouth.service
[Unit]
Before=xlogin@{{ vdr.user }}.service
{{ ansible_managed | comment }}
[Unit]
Wants=x@vt7.service
After=x@vt7.service

[Service]
TimeoutStopSec=20
KillMode=mixed
Drop-in snippet for vdr.service
{{ ansible_managed | comment }}
[Unit]
After=x@vt7.service xlogin@{{ vdr.user }}.service yavdr-xorg.service
Wants=x@vt7.service xlogin@{{ vdr.user }}.service yavdr-xorg.service
#BindsTo=x@vt7.service
xorg.conf snippets
{{ ansible_managed | comment }}

Section "InputClass"
    Identifier "exclude eventlircd devices"
    MatchTag   "eventlircd"
    Option     "Ignore"    "True"
EndSection
intel graphics xorg.conf snippet
AMD graphics xorg.conf snippet

This seems pretty straight forward - but I need someone with an AMD GPU to test this…

{{ 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      "amdgpu"
  Option      "TearFree" "true"
  Option      "VariableRefresh" "true"
{% if xorg.secondary is defined %}
  Option      "ZaphodHeads" "{{ primary_output }}"
  Screen      0  
{% endif %}
EndSection

{% if xorg.secondary is defined %}
Section "Device"
  Identifier  "Device1"
  Driver      "amdgpu"
  Option      "TearFree" "true"
  Option      "VariableRefresh" "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 xrandr["Screen 0:"][xorg.primary.connector].modelines %}
  {{ xrandr["Screen 0:"][xorg.primary.connector].modelines[xorg.primary.mode]|default("") }}
{% elif 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|bool or
    connector == xorg.primary.connector or
    (xorg.secondary is defined and connector == xorg.secondary.connector))
 %}
Section "Monitor"
  Identifier "{{ connector|replace("-","") }}"
  Option "Ignore" "true"
EndSection

{% endfor %}
xorg.conf
{{ ansible_managed | comment }}

{% if nvidia_legacy | default(False) %}
Section "ServerFlags"
    Option "IgnoreABI" "true"
EndSection
{% endif %}

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

{% if nvidia_legacy | default(False) %}
Section "ServerFlags"
    Option "IgnoreABI" "true"
EndSection
{% endif %}

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 %}
{% if nvidia_force_dpi | int(0) > 0  %}
    Option         "UseEdidDpi" "FALSE"
    Option         "DPI" "{{ nvidia_force_dpi }} x {{ nvidia_force_dpi }}"
{% 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

{ % if nvidia_legacy | default(False) % }
Section "ServerFlags"
    Options "IgnoreABI" "true"
EndSection
{ % endif % }

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 %}
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

initramfs EDID hook

{{ 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

mkdir -p /lib/firmware/edid/
rm -fr /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

yavdr-desktop

dependencies

---
dependencies:
    - role: session-common
    - role: udiskie

Tasks

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 }}'
    recurse: yes
  loop:
    - '{{ 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 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 irexec configuration for the session
  block:
    - name: expand template for irexec.service
      template:
        src: templates/systemd/user/irexec.service.j2
        dest: '{{ vdr.home }}/.config/systemd/user/irexec.service'
        mode: 0644
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
    - name: expand template for .lircrc
      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

- name: enable tmux.service
  systemd:
    name: tmux.service
    state: started
    masked: no
    enabled: yes
    scope: user
  become: yes
  become_user: vdr

- name: enable and start udiskie.service
  systemd:
    name: 'udiskie.service'
    state: started
    masked: no
    enabled: yes
    scope: user
  become: yes
  become_user: vdr

Templates

xorg configuration

.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
.xinitrc
{{ ansible_managed | comment }}
exec openbox-session

openbox

autostart
# forward environment variables to an environment file and the systemd user session
export ENV
exec bash -s << "EOF"
env | grep "DISPLAY\|DBUS_SESSION_BUS_ADDRESS\|XDG_RUNTIME_DIR\|XDG_DATA_DIRS" > ~/.session-env
systemctl --user import-environment DISPLAY XAUTHORITY XDG_RUNTIME_DIR XDG_DATA_DIRS DBUS_SESSION_BUS_ADDRESS

feh --no-fehbg  --bg-center "/usr/share/yavdr/images/yavdr_logo.png"

enabled_services=(
    "detect-second-display.service"
    "openbox-second.service"
    "yavdr-frontend.service" "pulseaudio.socket" "dbus-pulsectl.service"
    "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 XDG_DATA_DIRS
fi

# start systemd units for the yavdr user session
systemctl --user isolate yavdr-desktop.target
EOF
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>14</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>14</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>16</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>16</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>16</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>16</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>
    <!-- the openglosd window shouldn't gain focus on creation,
            not appear in the task bar or when switching between windows
            and stay below the other windows -->
    <application title="openglosd">
      <decor>no</decor>
      <maximized>no</maximized>
      <focus>no</focus>
      <layer>below</layer>
      <skip_pager>yes</skip_pager>
      <skip_taskbar>yes</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>
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>14</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>14</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>16</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>16</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>16</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>16</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>
    <!-- the openglosd window shouldn't gain focus on creation,
            not appear in the task bar or when switching between windows
            and stay below the other windows -->
    <application title="openglosd">
      <decor>no</decor>
      <maximized>no</maximized>
      <focus>no</focus>
      <layer>below</layer>
      <skip_pager>yes</skip_pager>
      <skip_taskbar>yes</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>

Systemd User Session

yavdr-desktop.target
[Unit]
Description=yaVDR Desktop
Requires=default.target
After=default.target pulseaudio.service
Wants=dbus.service pulseaudio.service
AllowIsolate=yes
detect-second-display
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
switch-displays

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

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
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=/usr/bin/feh --no-fehbg --bg-center "/usr/share/yavdr/images/yavdr_logo.png"

[Install]
WantedBy=yavdr-desktop.target
wm-exit.service
[Unit]
Description=exit window manager gracefully

[Service]
ExecStart=/bin/true
ExecStop=/usr/bin/openbox --exit
RemainAfterExit=True

[Install]
WantedBy=default.target

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

osd2web

This role installs and configures osd2web to be displayed on a second display (if one exists).

meta

---
dependencies:
    - { role: yavdr-desktop}

tasks

---
- name: apt | install vdr-plugin-osd2web
  apt:
    name: vdr-plugin-osd2web
    state: present

- name: expand osd2web.service from template 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: activate and start systemd unit osd2web.service
  systemd:
    name: 'osd2web.service'
    daemon_reload: yes
    enabled: yes
    masked: no
    scope: user
    state: started
  become: yes
  become_user: "{{ vdr.user }}"

templates

osd2web.service
{{ ansible_managed | comment }}
[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" if nvidia_detected | bool else "firefox --no-remote --kiosk --private-window"  }}
EnvironmentFile=-%h/.config/osd2web/config
ExecStart=/usr/bin/on_vdr -o -c '${browser} "${url}"'
KillSignal=SIGINT

[Install]
WantedBy=yavdr-desktop.target

samba-install

tasks

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

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

samba-config

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' ]

templates

smb.conf

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 %}
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 %}
include custom samba exports
include = /etc/samba/smb.conf.custom

autoinstall-drivers

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

sundtek for Sundtek devices (local or network connection)

  • State “STARTED” from “TODO” [2019-06-11 Di 08:54]

For now we plan to 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)

planned packages

  • [X] python3-sundtek (Python C-extension for sundtek API)
  • [ ] yavdr-hardware-sundtek (basic configuration files and scripts, sundtek-avahi-mounter)
  • [ ] yavdr-backend-sundtek (WFE)

drivers for TT S2-6400 FF

https://github.com/s-moch/linux-saa716x - how to build this as a dkms package?

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. Please note that you need to disable this role if you are using nvidia drivers which are not in the official ubuntu package repositories.

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 install
      register: ubuntu_drivers_install
      changed_when: '"All the available drivers are already installed." not in ubuntu_drivers_install.stdout_lines[-1]'

  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

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 X11 guest additions
  block:
    - name: install packages
      apt:
        state: present
        name:
          - virtualbox-guest-x11
  when:
    - ansible_virtualization_type == "virtualbox"
    - ansible_virtualization_role == "guest"
    - ansible_distribution == "Ubuntu"

- name: install virtualbox guest dkms package
  block:
    - name: install packages
      apt:
        state: present
        name:
          - dkms
          - virtualbox-guest-dkms
  when:
    - ansible_virtualization_type == "virtualbox"
    - ansible_virtualization_role == "guest"
    - ansible_distribution == "Ubuntu"
    - ansible_distribution_major_version == "20"
# TODO: replace by a role to allow proper frontend selection

- name: set xineliboutput as frontend
  block:
    - name: set xineliboutput as preferred frontend
      set_fact:
        preferred_frontend: "xineliboutput"
    - name: install xinelibouput and vdr-sxfe
      apt:
        name:
          - vdr-plugin-xineliboutput
          - xineliboutput-sxfe
        state: present
    - name: set vdr_output_plugin variable
      set_fact:
        automatically_installed_output_plugin: vdr-plugin-xineliboutput
  when:
    - ansible_virtualization_type in ["virtualbox", "VirtualPC", "VMware"]
    - ansible_virtualization_role == "guest"

set-output-plugin

This role sets the active output plugin and disables the other output plugins.

defaults

known_output_plugins:
  - pvr350
  - rpihddevice
  - softhdcuvid
  - softhddevice
  - softhddrm
  - softhdvaapi
  - vaapidevice
  - xine
  - xineliboutput

# must be one of the above values if the default doesn't fit
selected_frontend: '{{ preferred_frontend | default("") }}'

# name of the VDR output plugin deb-package to install,
# overwrite if you want something else than the default
# it must be one of:
#  - vdr-plugin-rpihddevice
#  - vdr-plugin-softhddevice
#  - vdr-plugin-softhddevice-cuvid
#  - vdr-plugin-softhddevice-openglosd
#  - vdr-plugin-softhdcuvid
#  - vdr-plugin-softhddrm
#  - vdr-plugin-softhdvaapi
#  - vdr-plugin-vaapidevice
#  - vdr-plugin-xine
#  - vdr-plugin-xineliboutput
vdr_output_plugin: '{{ automatically_installed_output_plugin | default("vdr-plugin-rpihddevice" if ansible_architecture == "armv7l" else "vdr-plugin-softhddevice") }}'

tasks

- name: show vdr_output_plugin
  debug:
    var: vdr_output_plugin
    verbosity: 1

- name: show automatically_installed_output_plugin
  debug:
    var: automatically_installed_output_plugin
    verbosity: 1

- name: apt | install the output plugin
  apt:
    state: present
    name: '{{ vdr_output_plugin }}'

- name: show selected_frontend
  debug:
    var: selected_frontend
    verbosity: 1

- name: show preferred_frontend
  debug:
    var: preferred_frontend
    verbosity: 1

- name: set active output plugin
  set_fact:
    active_output_plugin: '{{ selected_frontend }}'

- name: show active_output_plugin
  debug:
    msg: 'active_output_plugin is: {{ active_output_plugin }}'
    verbosity: 1

- name: set disabled output plugins
  set_fact:
    disabled_output_plugins: '{{ known_output_plugins | difference([active_output_plugin]) | list }}'
  when: active_output_plugin | length > 0

- name: show disabled_output_plugins
  debug:
    var: q('list', disabled_output_plugins)
    verbosity: 1

- name: disable unused output plugins
  command: "vdrctl disable {{ item }}"
  loop: '{{ disabled_output_plugins }}'
  #ignore_errors: yes
  register: vdrctl
  failed_when:
    - vdrctl.rc != 0 and 'is unknown or already disabled' not in vdrctl.stderr
  when: disabled_output_plugins is defined

- name: enable chosen output plugin
  command: vdrctl enable {{ active_output_plugin }}
  #ignore_errors: yes
  register: vdrctl
  failed_when:
    - vdrctl.rc != 0 and 'is already linked to' not in vdrctl.stderr
  when: active_output_plugin | length > 0

#known_frontends:
#  xineliboutput_sxfe:
#    plugin_name: xineliboutput
#    plugin_options: ""
#    packages:
#      - vdr-plugin-xineliboutput
#      - xineliboutput-sxfe
#  softhddevice_cuvid:
#    plugin_name: softhddevice
#    plugin_options: |
#      -D
#      -a pulse 
#      -w alsa-driver-broken
#      -v cuvid
#    packages:
#      - vdr-plugin-softhddevice-cuvid
#  softhddevice:
#    plugin_name: softhddevice
#    plugin_options: |
#      -D
#      -a pulse
#      -w alsa-driver-broken
#    packages:
#      - vdr-plugin-softhddevice

autoinstall-atric-usb

dependencies

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

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: run udevadm trigger to force udev rule to create symlinks
      shell: udevadm trigger -c add -s tty -w /dev/ttyACM*

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

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

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

autoinstall-yausbir

dependencies

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

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
      loop:
        - eventlircd.service
        - lircd2uinput.service
        - lircd.service
      tags:
        - systemd

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

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

autoinstall-satip

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

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 | length > 0
  notify: [ 'Restart VDR' ]

autoinstall-targavfd

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' ]

autoinstall-imonlcd

This role handles IMON LCDs with USB-IDs 15c2:0038 and 15c2:ffdc. According to http://www.vdr-wiki.de/wiki/index.php/Imonlcd-plugin#Beschreibung there is a IMON VFD with USB-ID 15c2:ffdc, which doesn’t work with vdr-plugin-imonlcd. In this case please disable this role and adapt the role autoinstall-imonvfd.

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' ]

autoinstall-imonvfd

This role handles IMON VFD devices with the USB-IDs 15c2:0036 and 15c2:0044.

tasks

---
# file roles/autoinstall-imonvfd/tasks/main

- name: Set up IMON VFD
  block:
    - name: check which display type we got
      set_fact:
        imon_vfd_device: '{{ "imon_0044" if "15c2:0044" in usb else "imon_0036" }}'
    - name: apt | install lcdproc and vdr-plugin-lcdproc for IMON VFD
      apt:
        name:  vdr-plugin-lcdproc

    - name: copy udev rule for IMON VFD
      template:
        src: templates/92-imonvfd.rules.j2
        dest: /etc/udev/rules.d/92-imonvfd.rules
        mode: 0644
        force: yes
      notify: [ 'reboot required' ]

    - name: expand template for /etc/LCDd.conf
      template:
        src: templates/LCDd.conf.j2
        dest: /etc/LCDd.conf
        owner: root
        group: root
        mode: 0644
        force: yes

    - name: enable service LCDd.service and ensure it is not masked
      systemd:
        name: LCDd.service
        state: started
        enabled: yes
        masked: no
        daemon_reload: yes

  when: '"15c2:0036" in usb or "15c2:0044" in usb'

templates

udev rules

{{ ansible_managed | comment }}

ACTION=="add", KERNEL=="lcd*", SUBSYSTEMS=="usb", ATTRS{idVendor}=="15c2", ATTRS{idProduct}=="*", MODE="0660", GROUP="vdr"
ACTION=="add", KERNEL=="lcd*", SUBSYSTEMS=="usb", ATTRS{idVendor}=="15c2", ATTRS{idProduct}=="0036", MODE="0660", GROUP="vdr", SYMLINK+="imon_0036"
ACTION=="add", KERNEL=="lcd*", SUBSYSTEMS=="usb", ATTRS{idVendor}=="15c2", ATTRS{idProduct}=="0044", MODE="0660", GROUP="vdr", SYMLINK+="imon_0044"

lcdproc.conf

{{ ansible_managed | comment }}

## This file was originally created by cme command.
## You can run 'cme edit lcdproc' to modify this file.
## You may also modify the content of this file with your favorite editor.

[server]
DriverPath = /usr/lib/x86_64-linux-gnu/lcdproc/
NextScreenKey = Right
PrevScreenKey = Left
ReportToSyslog = yes
ToggleRotateKey = Enter

Driver=imon
Bind=127.0.0.1
Port=13666
User=nobody
Hello=" "
Hello=" "
GoodBye=" "
GoodBye=" "
WaitTime=5
ServerScreen=no
Heartbeat=off

[menu]
DownKey = Down
EnterKey = Enter
MenuKey = Escape
UpKey = Up

[imon]
Device=/dev/{{imon_vfd_device}}
Size=16x2

autoinstall-libcecdaemon

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'

autoinstall-dvb-firmware

This tasks installs the dvb-firmwares collected by LibreElec

tasks

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

- name: install LibreElec dvb-firmware files
  block:
    - name: clone git repository
      git:
        repo: "https://github.com/LibreElec/dvb-firmware.git"
        dest: /var/cache/dvb-firmware
        clone: yes
        update: yes
        depth: 1
        force: true
      register: git_dvb_firmware

    - name: copy firwmware files if needed
      block:
        - name: get firmware files to copy
          command: |
            find /var/cache/dvb-firmware/firmware  -type f -regex '.*/*\.\(fw\|bin\)$' -printf '%P\n'
          register: firmware_to_copy
          changed_when: false

        - name: create directories for firmware files
          file:
            state: directory
            path: "/var/cache/dvb-firmware/firmware/{{ item }}"
          loop: "{{ firmware_to_copy.stdout_lines | map('dirname') | unique | list }}"

        - name: copy firmware files to /lib/firmware
          copy:
            src: "/var/cache/dvb-firmware/firmware/{{ item }}"
            dest: /lib/firmware/
            owner: root
            group: root
            mode: "0644"
          loop: "{{ firmware_to_copy.stdout_lines }}"
      when: git_dvb_firmware.changed or always_update_dvb_firmware | default (False)

autoinstall-pvr350

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' ]

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' ]

autoinstall-firmware

dependencies

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

defaults

hauppauge_wintv_hd_usb_ids:
  - "2013:025f" # PCTV292e
  - "2040:0264" # soloHD
  - "2040:8268" # soloHD
  - "2040:0265" # dualHD in lsoc mode
  - "2040:8265" # dualHD in bulk mode

hauppauge_wintv_hd_pci_ids:
  - "14f1:8880" # HCR-5525
  - "14f1:8852" # WinTV Quad HD

ds3000_pci_ids:
  - "d464:9022"
  - "14f1:8802"
  - "d470:9022"
  - "d471:9022"

ds3000_usb_ids:
  - "9022:d660"
  - "0572:6831"

m88rs6000_pci_ids:
  - "1ade:8038" # S952 V3
  - "14f1:8880" # Hauppauge WinTV HVR-5525

m88ds3103_pci_ids:
  - "14f1:8852" # S952 V2

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/LibreELEC/dvb-firmware/raw/master/firmware/dvb-demod-si2168-b40-01.fw
    checksum: sha256:d25c7deb9f69dca232ce25ab108da8ff5013d6d39088e0ec3475d97ded8af718
    dest: /lib/firmware/dvb-demod-si2168-b40-01.fw
  when: '(hauppauge_wintv_hd_usb_ids | intersect(usb)) | length > 0 or (hauppauge_wintv_hd_pci_ids | intersect(pci)) | length > 0'
  notify: ['reboot required']

- name: Firmware dvb-demod-si2168-02.fw for Hauppauge WinTV quadHD
  get_url:
    url: https://github.com/LibreELEC/dvb-firmware/raw/master/firmware/dvb-demod-si2168-02.fw
    checksum: sha256:5bbcfee4a0dbd55ea9d88d6b7d618afed5937a1f02962f14cdf707e108895cf7
    dest: /lib/firmware/dvb-demod-si2168-02.fw
  when: '"14f1:8852" in pci'
  notify: ['reboot required']

- name: Firmware dvb-fe-xc5000-1.6.114.fw for Hauppauge WinTV-HVR-930C
  get_url:
    url: https://github.com/LibreELEC/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'
  notify: ['reboot required']

- name: Firmware dvb-demod-m88rs6000.fw for Hauppauge WinTV HVR-5525, DVBSky S952 V3
  get_url:
    url: https://github.com/LibreELEC/dvb-firmware/raw/master/firmware/dvb-demod-m88rs6000.fw
    checksum: sha256:9ac84583d83a4222909cb568236b7786e436f27dc050e60a31df404bb1be19dc
    dest: /lib/firmware/dvb-demod-m88rs6000.fw
  when: '(m88rs6000_pci_ids | intersect(pci)) | length > 0'

- name: Firmware dvb-demod-m88ds3103.fw for DVBSky S952 V2
  get_url:
    url: https://github.com/LibreELEC/dvb-firmware/raw/master/firmware/dvb-demod-m88ds3103.fw
    checksum: sha256:4767ab80ceba4a66315cbec2a07ae1f7ebbd19c5758fd098b932e02c9108eff9
    dest: /lib/firmware/dvb-demod-m88ds3103.fw
  when: '(m88ds3103_pci_ids | intersect(pci)) | length > 0'
  notify: ['reboot required']

- name: Firmware ngenge_18.fw for ngene cards
  get_url:
    url: https://linux4media.de/official_downloads/drivers/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/LibreELEC/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']

- name: Firmware for Hauppauge Win-TV HVR-4000. NOVA-HD-S2 and Tevii S460
  get_url:
    url: https://github.com/LibreELEC/dvb-firmware/raw/master/firmware/dvb-fe-cx24116.fw
    checksum: sha256:8fa49be12cf332b4c9b0379ef997be9ab6e193ae03aae55b39e033ae31e35da0
    dest: /lib/firmware/dvb-fe-cx24116.fw
  when: '"14f1:8802" in pci or "d460:9022" in pci'
  notify: ['reboot required']

- name: Firmware for Tevii S464/470/471/660
  get_url:
    url: https://github.com/LibreELEC/dvb-firmware/raw/master/firmware/dvb-fe-ds3000.fw
    checksum: sha256:ad8c23bfb51642f48d31fe4f797182352bb13a4d4b7247b25aea18e208e0e882
    dest: /lib/firmware/dvb-fe-ds3000.fw
  when: '(ds3000_pci_ids | intersect(pci)) | length > 0 or (ds3000_usb_ids | intersect(usb)) | length > 0'
  notify: ['reboot required']

autoinstall-dvbsky-firmware

dependencies

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

defaults

tasks

autoinstall-dvbhddevice

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

defaults

dvbhddevice_firmware:
  - { src: 'http://www.aregel.de/file_download/26/dvb-ttpremium-fpga-01_v1_10.zip', 'checksum': 'sha256:d5f0ecd1c687549d71a21269c25791554895d8c03ff580a9694ef24041952f69', 'name': 'dvb-ttpremium-fpga-01_v1_10.zip' }
  - { src: 'http://www.aregel.de/file_download/7/dvb-ttpremium-loader-01_v1_03.zip', 'checksum': 'sha256:99969d4740ca26332439590e6b6e86711c89be69cf94c3732267b8278c0da763', 'name': 'dvb-ttpremium-loader-01_v1_03.zip' }
  - { src: 'http://aregel.de/file_download/28/dvb-ttpremium-st7109-01_v0_5_1.zip',  'checksum': 'sha256:cf336310fdd0c6998e6efa59d17b39d1fdb682daea51b60faee66444545986d4', 'name': 'dvb-ttpremium-st7109-01_v0_5_1.zip' }

dependencies

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

tasks

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

- name: Download Firmware for TT-6400 FF and install vdr-plugin-dvbhddevice
  block:
    - name: apt | install vdr-plugin-dvbhddevice and unzip
      apt:
        name:
          - vdr-plugin-dvbhddevice
          - unzip
        state: present
      notify: [ 'Restart VDR' ]

    - name: ensure /var/cache/firmware exists
      file:
        state: directory
        path: '/var/cache/firmware'
    - name: download TT-6400 FF firmware files
      get_url:
        url: '{{ item.src }}'
        dest: '/var/cache/firmware/{{ item.name }}'
        checksum: '{{ item.checksum }}'
      loop: "{{ dvbhddevice_firmware }}"

    - name: extract firmware files
      unarchive:
        src: '/var/cache/firmware/{{ item.name }}'
        dest: '/lib/firmware/'
        remote_src: yes
      loop: "{{ dvbhddevice_firmware }}"
      notify: [ 'reboot required' ]
  when: '"13c2:300a" in pci or "13c2:300b" in pci or force_dvbhddevice|default(False)|bool'

autoinstall-dvbsddevice

defaults

dvbsddevice_firmware:
  - { src: 'https://linuxtv.org/downloads/firmware/dvb-ttpci-01.fw-2622', 'checksum': 'sha256:482e49f8aac3fa5ea464230ea3f0cf27e298b62680c33636cb1bf442ad6fb067', 'name': 'dvb-ttpci-01.fw' }

dependencies

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

tasks

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

# TODO: install firmware

- name: Install firmware and output plugin for FF sd cards
  block:
    - name: download firmware files
      get_url:
        url: '{{ item.src }}'
        dest: '/lib/firmware/{{ item.name }}'
        checksum: '{{ item.checksum }}'
      loop: "{{ dvbsddevice_firmware }}"
      notify: ['reboot required']

    - name: apt | install vdr-plugin-dvbsddevice if module is loaded
      apt:
        name: vdr-plugin-dvbsddevice
      notify: [ 'Restart VDR' ]
  when: '"dvb_ttpci" in modules or force_dvbsddevice|default(False)|bool'

autoinstall-hardware-irmp

dependencies

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

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'

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.

defaults

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

serial_ir_device: ''

serial_ir_data:
  ttyS0:
    irq: 4
    io: '0x3f8'
  ttyS1:
    irq: 3
    io: '0x2f8'
  ttyS2:
    irq: 3
    io: '0x3e8'
  ttyS3:
    irq: 4
    io: '0x2e8'

tasks

---
# file: roles/serial-ir/tasks/main.yml
- name: check if serial_ir_device is not empty and does not match devices defined in serial_ir_data
  fail:
    msg: "The serial device  {{ serial_ir_device }} is not defined in serial_ir_data"
  when: serial_ir_device|length > 0 and serial_ir_device not in serial_ir_data

- 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

templates

load serial_ir on startup

{{ ansible_managed | comment }}

{% if serial_ir_device in serial_ir_data %}
serial_ir
{% endif %}

serial_ir module options

{{ ansible_managed | comment }}

{% if serial_ir_device in serial_ir_data %}
options serial_ir irq={{ serial_ir_data[serial_ir_device].irq }} io={{ serial_ir_data[serial_ir_device].io }}
install serial_ir setserial /dev/{{ serial_ir_device }} uart none; /sbin/modprobe --ignore-install serial_ir
{% endif %}

setserial options

{{ ansible_managed | comment }}

{% if serial_ir_device in serial_ir_data %}
/dev/{{serial_ir_device}} uart none
{% endif %}

kodi

defaults

kodi_as_flatpak: False
kodi_config_dir: '{{ vdr.home }}/{{ kodi_as_flatpak | ternary(".var/app/tv.kodi.Kodi/data", ".kodi") }}'
flatpak_kodi_allowed_dirs:
  - /media
  - /net
  - /srv/audio
  - /srv/files
  - /srv/picture
  - /srv/video
  - /home/vdr/Downloads
  - /var/lib/vdr/Downloads

tasks

Install KODI

main.yml
install-kodi.yml
configure-kodi.yml

templates

kodi.service

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

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.

files

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

Lircmap.xml

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

remote.xml

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

dvd

tasks

install libdvd-pkg, allow programs to eject optical media

vdr-epg-daemon

This role installs the vdr-epg-daemon

defaults

tasks

templates

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

template for /root/.ssh/mysqlpasswd so epgd-tool is happy

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

template for /etc/epgd/epgd.conf

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

install-sundtek

This role checks for USB IDs 2659:* and ab1a:5*. If you want to force installation of sundtek packages (e.g. for accessing network tuners), set sundtek.force_install to True.

There are two ways to configure the VDR to use sundtek devices:

  • delay starting the VDR until the sundtek mediasrv has initialized all local devices -> set sundtek.wait_for_devices to True (default)
  • use the vdr plugins vdr-plugin-dynamite and vdr-plugin-sundtek to allow the VDR to add (and remove) those tuners at runtime -> set sundtek.wait_for_devices to False

If you need to statically mount network tuners via /etc/sundtek.conf, set sundtek.wait_for_network to True. Please note that in this case the server has to be always avaible while the client is running.

Each program accessing sundtek tuners over device nodes in /dev/dvb needs to be started with the environment variable LD_PRELOAD=/opt/lib/libmediaclient.so. E.g. when using w_scan:

sudo LD_PRELOAD=/opt/lib/libmediaclient.so w_scan -f c -c de > new_channels.conf

additional services

  • [ ] avahi-sundtek-mounter
  • [ ] yavdr-backend-sundtek (for WFE)

dependencies

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

defaults

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

sundtek:
  force_install: false
  wait_for_devices: true
  wait_for_network: false

tasks

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

- name: check for connected sundtek devices
  set_fact:
    install_sundtek: "{{ (usb | select('match', '2659:') | list | length | bool) or (usb | select('match', 'eb1a:5') | list | length | bool) or (sundtek.force_install | bool) }}"

- name: Preconfigure sundtek drivers
  block:
    - name: override udev rule installed by dvb-driver-sundtek
      template:
        src: templates/udev/80-mediasrv-sundtek.rules.j2
        dest: /etc/udev/rules.d/80-mediasrv-sundtek.rules

    - name: expand template for custom sundtek.service
      template:
        src: templates/systemd/sundtek.service.j2
        dest: /etc/systemd/system/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: ['Reload Units', 'Restart VDR']

  when: install_sundtek | bool

- name: apt | install sundtek dvb driver
  apt:
    name: dvb-driver-sundtek
    state: '{{ "present" if install_sundtek | bool 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 or not install_sundtek else "present" }}'

- name: install and run sundtek.service
  block:
    - name: check if sundtek.service is running
      systemd:
        name: sundtek.service
        state: started
      check_mode: yes
      register: sundtek_service_state

    - name: get mediasrv pids
      command: pgrep mediasrv
      register: mediasrv_pids

    - name: systemd | start sundtek.service
      systemd:
        name: sundtek.service
        state: '{{ "started" if (mediasrv_pids.stdout_lines and not sundtek_service_state.changed) or not mediasrv_pids.stdout_lines else "stopped" }}'
        enabled: yes
        masked: no
        daemon-reload: yes
  when: install_sundtek | bool

# 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

# TODO: install yavdr-hardware-sundtek
#- name: apt | install yavdr-hardware-sundtek
#  apt:
#    name: yavdr-hardware-sundtek

# 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

templates

udev rule to force sundtek mediaclient to start the mediasrv using systemd

{{ ansible_managed | comment }}

{% if install_sundtek %}
#SUBSYSTEM=="usb", ACTION=="add", RUN+="/opt/bin/mediaclient --start=5 --systemdcheck"
{% endif %}
I wonder if this rule can to better than running mediaclient on every usb device

LD_PRELOAD for vdr.service

{{ ansible_managed | comment }}

{% if install_sundtek %}
[Service]
Environment=LD_PRELOAD=/opt/lib/libmediaclient.so
{% endif %}

/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

/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 %}

channel logos

This role loads the channel logos from https://github.com/Jasmeet181/mediaportal-de-logos and links them to /var/lib/vdr/channellogos. Please note, that all files in this directory will be replaced by this role.

default

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

channellogo_languages: []

tasks

- name: apt | install dependencies
  apt:
    name:
      - git
      - python3-lxml
    state: present

- name: copy channel_linker script
  template:
    src: roles/channellogos/templates/channel_linker.py.j2
    dest: /usr/local/bin/channel_linker
    mode: 0755

- name: get current mediaportal channel logos
  git:
    repo: "https://github.com/Jasmeet181/mediaportal-{{ item }}-logos.git"
    dest: "/usr/local/lib/mediaportal-{{ item }}-logos"
    clone: yes
    depth: 1
    update: yes
  loop: "{{ channellogo_languages }}"

- name: update channel logo links
  command: /usr/local/bin/channel_linker {{ channellogo_languages | map('quote') | join(' ') }}

templaces

{{ ansible_managed | comment }}
import shutil
import sys
from collections import defaultdict
from pathlib import Path
from lxml import etree


target = Path("/var/lib/vdr/channellogos")

logo_dir = {
    "au": ".Light",
    "be": ".Light",
    "cz": ".Light",
    "de": ".Light",
    "es": ".Light",
    "ie": ".Light",
    "il": ".Light",
    "it": ".Light",
    "nordic": ".Light",
    "nz": ".Light",
    "ru": ".Light",
    "uk": ".Light",
    "us": ".Light",
}

languages = []
for lang in sys.argv[1:]:
    if lang in logo_dir:
        languages.append(lang)
    else:
        print(f"language {lang} is not supported")

if languages:
    shutil.rmtree(target, ignore_errors=True)

target.mkdir(parents=True, exist_ok=True)

for language in languages:
    origin = Path(f"/usr/local/lib/mediaportal-{language}-logos")
    tree = etree.parse(str(origin / "LogoMapping.xml"))
    channels = defaultdict(set)
    for t in ("TV", "Radio"):
        base_path = Path(origin) / Path(t) / logo_dir[language]
        for channel in tree.xpath(f'{t}/Channel'):
            for channel_item in channel.findall("Item"):
                channel_name = channel_item.get("Name")
                channel_file_node = channel.find("File")
                if channel_file_node is None:
                    continue
                channel_file = base_path / Path(channel_file_node.text)
                suffix = channel_file.suffix
                channels[channel_file].add(target / (f"{channel_name}{suffix}".lower()))

    for channel_logo, channel_names in channels.items():
        for channel_name in channel_names:
            try:
                channel_name.parent.mkdir(parents=True, exist_ok=True)
                channel_name.symlink_to(channel_logo)
            except IOError as err:
                if err.errno == 17:
                    pass
                else:
                    print(err)

arm64-output

This role installs vdr-plugin-softhdevice-drm-gles and configures the output to use tty7. A systemd user session allows the use of udiskie and other programs within a systemd user session. Please note that kodi is no supported at the moment due to limitation with the dri master.

---
# this role is used to configure the output for arm64 boards like the Raspberry Pi 4 and 5

- name: ensure /etc/systemd/system/vdr.service.d/ exists
  file:
    path: /etc/systemd/system/vdr.service.d/
    state: directory

- name: let vdr run on tty7
  template:
    src: templates/systemd/console-output.conf.j2
    dest: /etc/systemd/system/vdr.service.d/99-console-output.conf
  notify: ['Reload Units', 'Restart VDR']
  tags:
    - config

- name: install vdr-plugin-softhddevice-drm-gles
  apt:
    name:
      - vdr-plugin-softhddevice-drm-gles
    state: present
  tags:
    - packages

- name: set preferred_frontend to softhddevice-drm-gles
  set_fact:
    preferred_frontend: softhddevice-drm-gles
    automatically_installed_output_plugin: vdr-plugin-softhddevice-drm-gles
{{ ansible_managed | comment }}
[Unit]
Conflicts=getty@tty7.service

[Service]
StandardInput=tty-force
StandardOutput=journal
TTYPath=/dev/tty7
TTYReset=no
TTYVHangup=yes
ExecStartPre=/usr/bin/chvt 7

rpi

This role installs vdr-plugin-rpihddevice and configures the output to use tty7. A Systemd User session allows to use udiskie and other programs within a systemd user session. Please note that kodi is not supported at the moment on the Raspberry Pi platform since it requires a special build.

tasks

---
# this role is used to configure the output for a raspberry pi 2 or 3

- name: ensure /etc/systemd/system/vdr.service.d/ exists
  file:
    path: /etc/systemd/system/vdr.service.d/
    state: directory

- name: let vdr run on tty7
  template:
    src: templates/rpi.conf.j2
    dest: /etc/systemd/system/vdr.service.d/99-rpi.conf
  notify: ['Reload Units', 'Restart VDR']
  tags:
    - config

- name: install vdr-plugin-rpihddevice
  apt:
    name:
      - libraspberrypi-dev
      - vdr-plugin-rpihddevice
    state: present
  tags:
    - packages

- name: set preferred_frontend to rpihddevice
  set_fact:
    preferred_frontend: rpihddevice
    automatically_installed_output_plugin: vdr-plugin-rpihddevice

# Note: this needs to be in config.txt, because the early firmware loader ingores include directives
- name: set gpu_mem firmware option
  lineinfile:
    path: /boot/firmware/config.txt
    regexp: '^gpu_mem='
    line: "gpu_mem={{ rpi_gpu_mem | default(256) | int if 16 <= rpi_gpu_mem | default(256) | int <= 512 else 256 }}"
    state: present

- name: set firmware options for decoder keys, rpi ir receiver
  template:
    src: templates/usercfg.txt.j2
    dest: /boot/firmware/usercfg.txt
  notify: ['reboot required']

templates

{{ ansible_managed | comment }}
[Unit]
Conflicts=getty@tty7.service

[Service]
StandardInput=tty-force
StandardOutput=journal
TTYPath=/dev/tty7
TTYReset=no
TTYVHangup=yes
ExecStartPre=/usr/bin/chvt 7
{{ansible_managed | comment }}
# Place "config.txt" changes (dtparam, dtoverlay, disable_overscan, etc.) in
# this file. Please refer to the README file for a description of the various
# configuration files on the boot partition.
{% if rpi_decode_mpg2 is defined %}
decode_MPG2={{ rpi_decode_mpg2 }}
{% endif %}
{% if rpi_decode_wvc1 is defined %}
decode_WVC1={{ rpi_decode_wvc1 }}
{% endif %}
{% if rpi_ir_gpio_pin is defined %}
dtoverlay=gpio-ir,gpio_pin={{ rpi_ir_gpio_pin }}{% if rpi_gpio_in_pullup|default(False)|bool %},gpio_in_pullup=up{% endif %}
{% endif %}
{% if rpi_ir_gpio_pin_tx is defined %}
dtoverlay=gpio-ir-tx,gpio_pin={{ rpi_ir_gpio_pin_tx }}
{% endif %}

channelpedia upload

This role requires three variables to be set (otherwise it won’t do anything):

channelpedia_url
the url the data will be uploaded to
channelpedia_username
the username required for authentication
channelpedia_password
the password required for authentication

If you are a channelpedia contributor, you should already know the required url (as of 07/2019 https is supported) and authentication data.

tasks

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

- name: upload channels.conf to channelpedia once a day
  block:
    - name: ensure curl is installed
      apt:
        name: curl
        state: present

    - name: expand template for channelpedia.service
      template:
        src: templates/channelpedia.service.j2
        dest: /etc/systemd/system/channelpedia.service

    - name: expand template for channelpedia.timer
      template:
        src: templates/channelpedia.timer.j2
        dest: /etc/systemd/system/channelpedia.timer

    - name: create credentials file
      template:
        src: templates/channelpedia_credentials.j2
        dest: /root/.channelpedia_credentials
        mode: 0700

    - name: start channelpedia timer and service
      systemd:
        name: '{{ item }}'
        state: started
        enabled: yes
        masked: no
      loop:
        - channelpedia.service
        - channelpedia.timer

  when:
    - channelpedia_url is defined
    - channelpedia_username is defined
    - channelpedia_password is defined

templates

channelpedia.service

[Unit]
Description=Upload channels.conf to channelpedia.yavdr.com
After=network-online.target
Requires=network-online.target

[Service]
EnvironmentFile=/root/.channelpedia_credentials
Type=oneshot
DynamicUser=true
ExecStart=/usr/bin/curl  -F "channels=@/var/lib/vdr/channels.conf;type=text/plain" -F "user=${user}" -F "password=${password}" "${url}"

channelpedia.timer

[Unit]
Description=Upload channels.conf to channelpedia.yavdr.com

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

.channelpedia_credentials

url={{ channelpedia_url }}
user={{ channelpedia_username }}
password={{ channelpedia_password }}

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"

wakeup

defaults

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

# Method can be 'acpiwakeup' or 'stm32wakeup'
wakeup_method: "acpiwakeup"
# How many minutes to start the VDR ahead?
wakeup_start_ahead: 5

# Days of the week for regular wake-ups (empty=Disabled, 1=Monday...7=Sunday)
wakeup_days: ""
# Time of each day for regular wake-ups (<24-hours>:<minutes>, e.g. "01:00")
wakeup_time: ""

variables

# vars file for wakeup role

# name of add-on package(s) to install (it can be made a list of packages)
wakeup_addon_packages:
  acpiwakeup: vdr-addon-acpiwakeup
  stm32wakeup: vdr-addon-stm32irmp-wakeup

# path to configuration file, corresponding template is called like the
# file's basename with .j2
wakeup_addon_confs:
  acpiwakeup: /etc/vdr/vdr-addon-acpiwakeup.conf
  stm32wakeup: /etc/vdr/vdr-addon-stm32irmp-wakeup.conf

tasks

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

- name: install package {{ wakeup_addon_packages[wakeup_method] }}
  apt:
    name: "{{ wakeup_addon_packages[wakeup_method] }}"
    state: present

# we expand all templates so that any add-on can be disabled if needed
- name: expand template for each wakeup add-on config file
  template:
    src: "{{ item.value | basename }}.j2"
    dest: "{{ item.value }}"
  loop: "{{ wakeup_addon_confs | dict2items }}"

templates

acpiwakeup

{{ 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={{ wakeup_start_ahead }}

# 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)
{% if wakeup_days %}
ACPI_REGULAR_DAYS="{{ wakeup_days }}"
{% else %}
# ACPI_REGULAR_DAYS="1 2 3 4 5 6 7"
{% endif %}

# Wakeup time
{% if wakeup_time %}
ACPI_REGULAR_TIME="{{ wakeup_time }}"
{% else %}
# ACPI_REGULAR_TIME=01:00  # HH:MM
{% endif %}

st32wakeup

{{ ansible_managed | comment }}

# Activate/deactivate STM32Wakeup with yes/no:
WAKEUP_ENABLED="{% if wakeup_method == 'stm32wakeup' %}yes{% else %}no{% endif %}"

# How many minutes should the machine wake up before the timer starts:
WAKEUP_START_AHEAD={{ wakeup_start_ahead }}

# 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)
{% if wakeup_days %}
WAKEUP_REGULAR_DAYS="{{ wakeup_days }}"
{% else %}
# WAKEUP_REGULAR_DAYS="1 2 3 4 5 6 7"
{% endif %}

# Wakeup time
{% if wakeup_time %}
WAKEUP_REGULAR_TIME="{{ wakeup_time }}"
{% else %}
# WAKEUP_REGULAR_TIME=01:00  # HH:MM
{% endif %}

grub-config

default variables

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

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 }} {{ intel_boot_options }}"'
      backrefs: yes
  notify: [ 'Update GRUB' ]

- name: set GRUB_TIMEOUT
  lineinfile:
      dest: /etc/default/grub
      state: present
      regexp: '^(GRUB_TIMEOUT=)'
      line: 'GRUB_TIMEOUT={{ grub.timeout }}'
      backrefs: yes
  notify: [ 'Update GRUB' ]

- name: set GRUB_TIMEOUT_STYLE
  lineinfile:
      dest: /etc/default/grub
      state: present
      regexp: '^(GRUB_TIMEOUT_STYLE=)'
      line: '\1{{ "hidden" if grub.timeout == 0 else "menu" }}'
      backrefs: yes
  notify: [ 'Update GRUB' ]

templates

{{ 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

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

Modules

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

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)

    serial:
        required: False
        default: True
        description:
          - return a list of serial connections which provide an address and an interrupt

    acpi_power_modes:
        required: False
        default: True
        description:
          - return a list of supported acpi power saving modes
notes:
   - requires python3-pyusb and python3-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
    serial: True
    modules: True
    acpi_power_modes: True
- debug:
    var: usb
- debug:
    var: pci
- debug:
    var: modules
- debug:
    var: serial
- debug:
    var: gpus
- debug:
    var: acpi_power_modes
'''

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 get_serial_data(name):
    """
    get the I/O and IRQ numbers for the serial port
    using the /sys/class/tty/ttyS<X>/ device nodes
    """
    with open(os.path.join(name, 'irq')) as f:
        irq = int(f.read().rstrip())
    with open(os.path.join(name, 'port')) as f:
        port = int(f.read().rstrip(), base=16)
    return port, irq

def get_serial_devices():
    devices = {}
    p = '/sys/class/tty/'
    for d in sorted(glob.glob(os.path.join(p, 'ttyS[0-9]*'))):
        port, irq = get_serial_data(d)
        if port and irq:
            # TODO: check if a serial receiver is attached
            devices[os.path.basename(d)] = {'port': '0x{:x}'.format(port), 'irq': irq}
    return devices


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),
    'serial': 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_serial = module.params['serial']
    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
    serial_devices = []
    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_serial:
        serial_devices = get_serial_devices()

    if collect_acpi_power_modes:
        acpi_power_modes = list_acpi_power_modes()

    data = {'usb': usb_devices,
            'pci': pci_devices,
            'modules': modules,
            'gpus': gpus,
            'serial': serial_devices,
            '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()

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()

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")).encode('utf-8')

@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).decode()
            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()

xrandr_facts.py

  • [ ] support multiple screens (-d :0.0 .. :0.n)

rmmod.py

Handlers

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

- name: Trigger Udev
  command: 'udevadm trigger '

- name: Reload Units
  systemd:
    daemon_reload: yes

- name: Restart Samba
  systemd:
    name: smbd.service
    state: restarted
    enabled: yes
    #masked: no
  register: samba_reload

- name: Restart vdr-addon-lifeguard-ng
  systemd:
    name: vdr-addon-lifeguard-ng.service
    state: restarted
    enabled: yes
  register: lifeguard_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
    daemon_reload: 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
    daemon_reload: 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.

- name: rerun required
  debug:
    msg: PLEASE REBOOT YOUR SYSTEM AND RUN THIS PLAYBOOK AGAIN.

Settings for Export

\newcommand*\justify{%
  \fontdimen2\font=0.4em% interword space
  \fontdimen3\font=0.2em% interword stretch
  \fontdimen4\font=0.1em% interword shrink
  \fontdimen7\font=0.1em% extra space
  \hyphenchar\font=`\-% allowing hyphenation
}

\renewcommand{\texttt}[1]{%
  \begingroup
  \ttfamily
  \begingroup\lccode`~=`/\lowercase{\endgroup\def~}{/\discretionary{}{}{}}%
  \begingroup\lccode`~=`[\lowercase{\endgroup\def~}{[\discretionary{}{}{}}%
  \begingroup\lccode`~=`.\lowercase{\endgroup\def~}{.\discretionary{}{}{}}%
  \catcode`/=\active\catcode`[=\active\catcode`.=\active
  \justify\scantokens{#1\noexpand}%
  \endgroup
}