yavdr-ansible/Manual.org

104 KiB
Raw Blame History

Ansible Playbooks for yaVDR 0.7

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

Using vdr, pulseaudio and xorg together

Notes

:PROPERTIES: :export nil

:END:

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

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

STARTED BusID für (nvidia) Grafikkarten aus lspci auslesen

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?

NVIDIA-Dokumentation zur 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🚌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)

TODO [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-vpp
amd
softhddevice-vpp
nvidia
softhddevice-openglosd (if no HEVC channels are needed)

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 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
sudo ./install-yavdr.sh

Install script for local usage

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

apt-get -y install software-properties-common
# Add repository for ansible
add-apt-repository -y ppa:ansible/ansible
# update packages
apt-get update
# install required packages
apt-get -y install ansible

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

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
  roles:
     - yavdr-common             # install and configure the basic system
     - autoinstall-ubuntu-drivers # use ubuntu-drivers to install proprietary dirvers (e.g. nvidia, virtualbox)
     - vdr                      # install vdr and related packages
     - yavdr-network            # enable network client capabilities
     - samba-install            # install samba server
     - samba-config             # configure samba server
     - nfs-server               # install nfs server
     - yavdr-xorg               # graphical session
     - yavdr-remote             # remote configuration files, services and scripts
     - 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-pv350        # install vdr-plugin-pvr350 if a matching card is detected
     #- autoinstall-dvbsddevice  # install vdr-plugin-dvbsddevice if a matching card is detected
     - kodi
     - dvd
     - grub-config          # configure grub

  handlers:
    - include: 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
  roles:
    - yavdr-common
    - vdr
    - yavdr-network
    - samba-server
    - samba-config
    - nfs-server
    - grub-config
    - autoinstall-satip
  handlers:
    - include: 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:

[localhost]
localhost connection=local

Group Variables

default text for templates used by ansible

# file: group_vars/all

# this is the standard text to put in templates
ansible_managed_file: "*** YAVDR: ANSIBLE MANAGED FILE ***"

PPAs

branch: unstable
ppa_owner: 'ppa:yavdr'
# a list of all package repositories to be added to the installation
repositories:
  - '{{ ppa_owner }}/main'
  - '{{ ppa_owner }}/unstable-main'
  - '{{ ppa_owner }}/{{branch}}-vdr'
  - '{{ ppa_owner }}/{{branch}}-yavdr'
  - '{{ ppa_owner }}/{{branch}}-kodi'

VDR user, directories, special configuration and plugins

# properties of the user vdr and vdr-related options
vdr:
  user: vdr
  group: vdr
  uid: 666
  gid: 666
  home: /var/lib/vdr
  recdir: /srv/vdr/video
  hide_first_recording_level: false
  safe_dirnames: true  # escape characters (useful for windows clients and FAT/NTFS file systems)
  override_vdr_charset: false

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

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/audio
  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

Additional packages

# additional packages you want to install
extra_packages:
  - vim
  - tree
  - w-scan
  - bpython3

System pre-configuration

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

Roles

install-dependencies

tasks

main.yml
---

- name: apt | install packages
  apt:
    name: '{{ item }}'
    state: present
    install_recommends: no
  with_items:
    '{{ packages }}'

nvidia-381 drivers

install nvidia-381 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

- name: install nvidia-381 and other required packages
  apt:
    name: '{{ item }}'
    state: present
    install_recommends: no
  with_items:
    - nvidia-381
    - nvidia-settings

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: unstable
repositories:
  - 'ppa:yavdr/main'
  - 'ppa:yavdr/unstable-main'
  - 'ppa:yavdr/{{branch}}-vdr'
  - 'ppa:yavdr/{{branch}}-kodi'
  - 'ppa:yavdr/{{branch}}-yavdr'
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: /var/lib/vdr
    recdir: /srv/vdr/video
    hide_first_recording_level: false
    safe_dirnames: true
    override_vdr_charset: false

tasks

yavdr-common executes the following tasks:

main.yml
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
Use bash instead of dash
- name: use bash instead of dash
  shell: |
    echo "set dash/sh false" | debconf-communicate
    dpkg-reconfigure -f noninteractive dash    
create user vdr
Disable release-upgrade notifications
- name: disable release-upgrade notifications
  lineinfile:
    dest: /etc/update-manager/release-upgrades
    backrefs: yes
    state: present
    regexp: '^(Prompt=).*$'
    line: '\1never'
Set up package repositories
- name: add yaVDR PPAs
 apt_repository:
     repo: '{{ item }}'
     state: present
     update_cache: yes
 with_items: '{{ repositories }}'

- name: upgrade existing packages
 apt:
     upgrade: dist
     update_cache: yes
Install essential packages
- name: apt | install basic packages
 apt:
   name: '{{ item }}'
   state: present
   install_recommends: no
 with_items:
   - anacron
   - at
   - bash-completion
   - biosdevname
   - debconf-utils
   - linux-firmware
   - psmisc
   - python-kmodpy
   - python-usb
   - python3-usb
   - software-properties-common
   - ssh
   - wget
   - wpasupplicant
   - usbutils
   - xfsprogs
Install additional packages (user defined)
- name: apt | install extra packages
  apt:
    name: '{{ item }}'
    state: present
    install_recommends: no
  with_items:
      '{{ extra_packages }}'
Gather facts with custom modules
- name: get information about usb and pci hardware and loaded kernel modules
  hardware_facts:
    usb: True
    pci: True
    modules: True
    gpus: True
- debug:
    var: usb
    verbosity: 1
- debug:
    var: pci
    verbosity: 1
- debug:
    var: modules
    verbosity: 1
- debug:
    var: gpus
    verbosity: 1
create media directories

templates

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

vdr

tasks

install the basic vdr packages
---
# file: roles/vdr/tasks/main.yml

- name: apt | install basic vdr packages
  apt:
    name: '{{ item }}'
    state: present
    install_recommends: no
  with_items:
    - vdr
    - vdrctl
    - vdr-plugin-dbus2vdr
Add svdrp/svdrp-disc to /etc/services
- name: add svdrp to /etc/services
  lineinfile:
    dest: /etc/services
    state: present
    line: "svdrp        6419/tcp"

- name: add svdrp-disc to /etc/services
  lineinfile:
    dest: /etc/services
    state: present
    line: "svdrp-disc       6419/udp"
Set up the recording directory for the vdr user
- name: create vdr recdir
  file:
    state: directory
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'
    mode: 0775
    dest: '{{ vdr.recdir }}'

- name: set option to use hide-first-recording-level patch
  blockinfile:
    dest: /etc/vdr/conf.d/04-vdr-hide-first-recordinglevel.conf
    create: true
    block: |
      [vdr]
      --hide-first-recording-level      
  when:
    vdr.hide_first_recording_level

- name: create local dir in recdir
  file:
    state: directory
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'
    mode: '0775'
    dest: '{{ vdr.recdir }}/local'
  when:
    vdr.hide_first_recording_level

# TODO: set recdir, user etc. in /etc/vdr/conf.d/
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: '{{ item }}'
    state: present
    install_recommends: no
  with_items:
    '{{ vdr_plugins | default({}) }}'
  notify: [ 'Restart VDR' ]

STARTED 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: '{{ item }}'
      state: present
      install_recommends: no
  with_items:
      - avahi-daemon
      - avahi-utils
      - biosdevname
      - ethtool
      - nfs-common
      - vdr-addon-avahi-linker
      - wakeonlan

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

nfs-server

tasks

- name: install nfs server packages
  apt:
    name: '{{ item }}'
    state: present
    install_recommends: no
  with_items:
    - nfs-kernel-server
    - nfs-common

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

templates

  /srv *(rw,fsid=0,sync,no_subtree_check,all_squash,anongid={{ vdr.gid }},anonuid={{ vdr.uid }})
  {% for name, path in media_dirs.iteritems() %}
  {{ path }} *(rw,fsid={{ loop.index }},sync,no_subtree_check,all_squash,anongid={{ vdr.gid }},anonuid={{ vdr.uid }}{{ ',insecure' if nfs.insecure else '' }})
  {% endfor %}

TODO yavdr-remote

default variables

tasks

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

- name: install yavdr-remote
  apt:
    name: yavdr-remote
    state: present

- name: install lirc
  apt:
    name: lircd
    state: present
  when:
    - install_lircd is defined and install_lircd

- name: install eventlircd
  apt:
    name: eventlircd
    state: present
  when:
      install_eventlircd is defined and install_eventlircd

# TODO: upload lircd2uinput package to PPA
#- name: install lircd2uinput
#  tag: install
#  apt:
#      name: lircd2uinput
#      state: present
#  when:
#      install_eventlircd is defined and install_eventlircd

templates

files

yavdr-xorg

TODO automatic X-server configuration

  • detect connected display
  • read EDID from displays
  • create a xorg.conf for nvidia/intel/amd gpus
HOLD Nvidia-GPUs:read EDID:
$ 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
DONE 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
DONE 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'

default variables

tasks

---
# file: roles/yavdr-xorg/tasks/main.yml


# TODO: use hooks or requirements instead
- name: Stop VDR
  systemd:
    name: vdr.service
    state: stopped
    enabled: yes
  notify: ['Start VDR']

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

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

- name: install packages for xorg
  apt:
    name: '{{ item }}'
    state: present
  with_items:
    - xorg
    - xserver-xorg-video-all
    - xserver-xorg-input-all
    - xlogin
    - xterm
    - openbox
    #- yavdr-xorg

- name: "stop x@vt7.service"
  systemd:
    name: "x@vt7.service"
    state: stopped

- name: "expand template for x-verbose@.service"
  template:
    src: "templates/x-verbose@.service.j2"
    dest: "/etc/systemd/system/x-verbose@.service"

- name: "start x-verbose@.service"
  systemd:
    name: "x-verbose@vt7.service"
    state: started
    enabled: false
    masked: false
    daemon_reload: true

- name: "wait a little bit, so X has some time to start up (needed?)"
  wait_for:
    timeout: 3

- name: "detect xorg configuration"
  action: xrandr_facts

- name: "stop x-verbose@vt7.service"
  systemd:
    name: "x-verbose@vt7.service"
    state: stopped
    enabled: false
    masked: true

### TODO: Create xorg configuration

- name: create folders for user session
  file:
    state: directory
    dest: '{{ item }}'
    mode: '0775'
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'
  with_items:
    - '{{ vdr.home }}/.config/systemd/user'
    - '{{ vdr.home }}/.config/openbox/'

### TODO: move to yavdr-xorg package? ###
- name: create folder for customizations of vdr.service
  file:
    state: directory
    dest: /etc/systemd/system/vdr.service.d
    mode: '0775'

- name: create folder for systemd user configuration
  file:
    state: directory
    dest: '{{ vdr.home }}/.config/systemd/user'
    mode: '0775'
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'

- name: create folder for openbox configuration
  file:
    state: directory
    dest: '{{ vdr.home }}/.config/openbox'
    mode: '0775'
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'

- name: add dependency to X-server for vdr.service using a drop-in
  template:
    src: templates/vdr-xorg.conf
    dest: /etc/systemd/system/vdr.service.d/
### END TODO ###

- name: create .xinitrc for vdr user
  template:
      src: 'templates/.xinitrc.j2'
      dest: '{{ vdr.home }}/.xinitrc'
      mode: 0755
      owner: '{{ vdr.user }}'
      group: '{{ vdr.group }}'

- name: populate autostart for openbox
  template:
      src: 'templates/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: 0755
      owner: '{{ vdr.user }}'
      group: '{{ vdr.group }}'

- name: create yavdr.target for the user session
  template:
      src: 'templates/yavdr-desktop.target.j2'
      dest: '{{ vdr.home }}/.config/systemd/user/yavdr-desktop.target'
      mode: 0755
      owner: '{{ vdr.user }}'
      group: '{{ vdr.group }}'

- name: set a login shell for the user vdr
  user:
      name: '{{ vdr.user }}'
      shell: '/bin/bash'
      state: present
      uid: '{{ vdr.uid }}'
      groups: '{{ vdr.group }}'
      append: yes

# TODO: run xorg-debug and parse xrandr output
# TODO: expand template for xorg.conf (or snippets)
#       with respect for the available graphics card driver
#       nvidia, noveau, intel, radeon

- name: enable and start xlogin for the user vdr
  systemd:
    daemon_reload: yes
    name: 'xlogin@{{ vdr.user }}'
    enabled: yes
    state: started

templates

xorg
[Unit]
Description=X with verbose logging on %I
Wants=graphical.target
Before=graphical.target

[Service]
Type=forking
ExecStart=/usr/bin/x-daemon -logverbose 6 -noreset %I
# file: roles/yavdr-xorg/templates/vdr-xorg.conf
# {{ ansible_managed_file }}

[Unit]
After=x@vt7.service
Wants=x@vt7.service
#BindsTo=x@vt7.service
#!/bin/bash
# {{ ansible_managed_file }}
exec openbox-session
[Unit]
Description=yaVDR Desktop
Requires=default.target
After=default.target pulseaudio.service
Wants=dbus.service pulseaudio.service
AllowIsolate=yes
openbox
autostart
env | grep "DISPLAY\|DBUS_SESSION_BUS_ADDRESS\|XDG_RUNTIME_DIR" > ~/.session-env
systemctl --user import-environment DISPLAY XAUTHORITY XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS

if which dbus-update-activation-environment >/dev/null 2>&1; then
        dbus-update-activation-environment DISPLAY XAUTHORITY XDG_RUNTIME_DIR
fi

# start systemd units for the yavdr user session
systemctl --user isolate yavdr-desktop.target
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>yes</keepBorder>
    <animateIconify>yes</animateIconify>
    <font place="ActiveWindow">
      <name>sans</name>
      <size>8</size>
      <!-- font size in points -->
      <weight>bold</weight>
      <!-- 'bold' or 'normal' -->
      <slant>normal</slant>
      <!-- 'italic' or 'normal' -->
    </font>
    <font place="InactiveWindow">
      <name>sans</name>
      <size>8</size>
      <!-- font size in points -->
      <weight>bold</weight>
      <!-- 'bold' or 'normal' -->
      <slant>normal</slant>
      <!-- 'italic' or 'normal' -->
    </font>
    <font place="MenuHeader">
      <name>sans</name>
      <size>9</size>
      <!-- font size in points -->
      <weight>normal</weight>
      <!-- 'bold' or 'normal' -->
      <slant>normal</slant>
      <!-- 'italic' or 'normal' -->
    </font>
    <font place="MenuItem">
      <name>sans</name>
      <size>9</size>
      <!-- font size in points -->
      <weight>normal</weight>
      <!-- 'bold' or 'normal' -->
      <slant>normal</slant>
      <!-- 'italic' or 'normal' -->
    </font>
    <font place="ActiveOnScreenDisplay">
      <name>sans</name>
      <size>9</size>
      <!-- font size in points -->
      <weight>bold</weight>
      <!-- 'bold' or 'normal' -->
      <slant>normal</slant>
      <!-- 'italic' or 'normal' -->
    </font>
    <font place="InactiveOnScreenDisplay">
      <name>sans</name>
      <size>9</size>
      <!-- font size in points -->
      <weight>bold</weight>
      <!-- 'bold' or 'normal' -->
      <slant>normal</slant>
      <!-- 'italic' or 'normal' -->
    </font>
  </theme>
  <desktops>
    <!-- this stuff is only used at startup, pagers allow you to change them
       during a session

       these are default values to use when other ones are not already set
       by other applications, or saved in your session

       use obconf if you want to change these without having to log out
       and back in -->
    <number>2</number>
    <firstdesk>1</firstdesk>
    <names>
      <!-- set names up here if you want to, like this:
    <name>desktop 1</name>
    <name>desktop 2</name>
    -->
    </names>
    <popupTime>875</popupTime>
    <!-- The number of milliseconds to show the popup for when switching
       desktops.  Set this to 0 to disable the popup. -->
  </desktops>
  <resize>
    <drawContents>yes</drawContents>
    <popupShow>Nonpixel</popupShow>
    <!-- 'Always', 'Never', or 'Nonpixel' (xterms and such) -->
    <popupPosition>Center</popupPosition>
    <!-- 'Center', 'Top', or 'Fixed' -->
    <popupFixedPosition>
      <!-- these are used if popupPosition is set to 'Fixed' -->
      <x>10</x>
      <!-- positive number for distance from left edge, negative number for
         distance from right edge, or 'Center' -->
      <y>10</y>
      <!-- positive number for distance from top edge, negative number for
         distance from bottom edge, or 'Center' -->
    </popupFixedPosition>
  </resize>
  <!-- You can reserve a portion of your screen where windows will not cover when
     they are maximized, or when they are initially placed.
     Many programs reserve space automatically, but you can use this in other
     cases. -->
  <margins>
    <top>0</top>
    <bottom>0</bottom>
    <left>0</left>
    <right>0</right>
  </margins>
  <dock>
    <position>TopLeft</position>
    <!-- (Top|Bottom)(Left|Right|)|Top|Bottom|Left|Right|Floating -->
    <floatingX>0</floatingX>
    <floatingY>0</floatingY>
    <noStrut>no</noStrut>
    <stacking>Above</stacking>
    <!-- 'Above', 'Normal', or 'Below' -->
    <direction>Vertical</direction>
    <!-- 'Vertical' or 'Horizontal' -->
    <autoHide>no</autoHide>
    <hideDelay>300</hideDelay>
    <!-- in milliseconds (1000 = 1 second) -->
    <showDelay>300</showDelay>
    <!-- in milliseconds (1000 = 1 second) -->
    <moveButton>Middle</moveButton>
    <!-- 'Left', 'Middle', 'Right' -->
  </dock>
  <keyboard>
    <chainQuitKey>C-g</chainQuitKey>
    <!-- Keybindings for desktop switching -->
    <keybind key="C-A-Left">
      <action name="GoToDesktop">
        <to>left</to>
        <wrap>no</wrap>
      </action>
    </keybind>
    <keybind key="C-A-Right">
      <action name="GoToDesktop">
        <to>right</to>
        <wrap>no</wrap>
      </action>
    </keybind>
    <keybind key="C-A-Up">
      <action name="GoToDesktop">
        <to>up</to>
        <wrap>no</wrap>
      </action>
    </keybind>
    <keybind key="C-A-Down">
      <action name="GoToDesktop">
        <to>down</to>
        <wrap>no</wrap>
      </action>
    </keybind>
    <keybind key="S-A-Left">
      <action name="SendToDesktop">
        <to>left</to>
        <wrap>no</wrap>
      </action>
    </keybind>
    <keybind key="S-A-Right">
      <action name="SendToDesktop">
        <to>right</to>
        <wrap>no</wrap>
      </action>
    </keybind>
    <keybind key="S-A-Up">
      <action name="SendToDesktop">
        <to>up</to>
        <wrap>no</wrap>
      </action>
    </keybind>
    <keybind key="S-A-Down">
      <action name="SendToDesktop">
        <to>down</to>
        <wrap>no</wrap>
      </action>
    </keybind>
    <keybind key="W-F1">
      <action name="GoToDesktop">
        <to>1</to>
      </action>
    </keybind>
    <keybind key="W-F2">
      <action name="GoToDesktop">
        <to>2</to>
      </action>
    </keybind>
    <keybind key="W-F3">
      <action name="GoToDesktop">
        <to>3</to>
      </action>
    </keybind>
    <keybind key="W-F4">
      <action name="GoToDesktop">
        <to>4</to>
      </action>
    </keybind>
    <keybind key="W-d">
      <action name="ToggleShowDesktop"/>
    </keybind>
    <!-- Keybindings for windows -->
    <keybind key="A-F4">
      <action name="Close"/>
    </keybind>
    <keybind key="A-Escape">
      <action name="Lower"/>
      <action name="FocusToBottom"/>
      <action name="Unfocus"/>
    </keybind>
    <keybind key="A-space">
      <action name="ShowMenu">
        <menu>client-menu</menu>
      </action>
    </keybind>
    <!-- Take a screenshot of the current window with scrot when Alt+Print are pressed -->
    <keybind key="A-Print">
      <action name="Execute">
        <command>scrot -s</command>
      </action>
    </keybind>
    <!-- Keybindings for window switching -->
    <keybind key="A-Tab">
      <action name="NextWindow">
        <finalactions>
          <action name="Focus"/>
          <action name="Raise"/>
          <action name="Unshade"/>
        </finalactions>
      </action>
    </keybind>
    <keybind key="A-S-Tab">
      <action name="PreviousWindow">
        <finalactions>
          <action name="Focus"/>
          <action name="Raise"/>
          <action name="Unshade"/>
        </finalactions>
      </action>
    </keybind>
    <keybind key="C-A-Tab">
      <action name="NextWindow">
        <panels>yes</panels>
        <desktop>yes</desktop>
        <finalactions>
          <action name="Focus"/>
          <action name="Raise"/>
          <action name="Unshade"/>
        </finalactions>
      </action>
    </keybind>
    <!-- Keybindings for window switching with the arrow keys -->
    <keybind key="W-S-Right">
      <action name="DirectionalCycleWindows">
        <direction>right</direction>
      </action>
    </keybind>
    <keybind key="W-S-Left">
      <action name="DirectionalCycleWindows">
        <direction>left</direction>
      </action>
    </keybind>
    <keybind key="W-S-Up">
      <action name="DirectionalCycleWindows">
        <direction>up</direction>
      </action>
    </keybind>
    <keybind key="W-S-Down">
      <action name="DirectionalCycleWindows">
        <direction>down</direction>
      </action>
    </keybind>
    <!-- Keybindings for running applications -->
    <keybind key="W-e">
      <action name="Execute">
        <startupnotify>
          <enabled>true</enabled>
          <name>Konqueror</name>
        </startupnotify>
        <command>kfmclient openProfile filemanagement</command>
      </action>
    </keybind>
    <!-- Launch scrot when Print is pressed -->
    <keybind key="Print">
      <action name="Execute">
        <command>scrot</command>
      </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>true</maximized>
      <!--<skip_pager>yes</skip_pager>-->
      <skip_taskbar>yes</skip_taskbar>
    </application>
    <application title="browser">
      <decor>no</decor>
      <maximized>true</maximized>
      <!--<skip_pager>yes</skip_pager>-->
      <skip_taskbar>yes</skip_taskbar>
    </application>
    <application title="osd2Web">
      <decor>no</decor>
      <maximized>true</maximized>
      <!--<skip_pager>yes</skip_pager>-->
      <skip_taskbar>yes</skip_taskbar>
    </application>
  </applications>
</openbox_config>

samba-install

tasks

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

- name: install samba server
  apt:
    name: '{{ item }}'
    state: present
    install_recommends: no
  with_items:
    - samba
    - samba-common
    - samba-common-bin
    - tdb-tools

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_file | 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 user

{% 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.iteritems() %}
[{{ 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

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

Vendor-IDs:

  • eb1a:5[1b2] (alte Generation)
  • 2659:* (neuere Sticks)

dddvb-dkms if only newer DD cards are detected

media-build-experimental (up to kernel 4.8) for "old" cards like TT S2-6400 FF

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.

tasks

---
# file roles/autoinstall-ubuntu-drivers/tasks/main.yml
- name: apt | install ubuntu-drivers-common
  apt:
    name: ubuntu-drivers-common
    state: present

- name: ensure /etc/yavdr exists
  file:
    path: /etc/yavdr
    state: directory
    mode: 0755

- name: let ubuntu-drivers automatically install additional drivers
  command: ubuntu-drivers --package-list /etc/yavdr/autoinstalled autoinstall

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_detected
    verbosity: 1

- name: apt | install vdr-plugin-satip if a Sat>IP server has been detected
  apt:
    name: vdr-plugin-satip
  when: satip_detected
  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

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'
    - '"15c2:ffdc" in usb'
  notify: [ 'Restart VDR' ]

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

TODO autoinstall-dvbhddevice

Problem: woher kommt der Treiber (AFAIK noch nicht im Kernel)? Die Firmware sollte in yavdr-firmware stecken

tasks

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

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

autoinstall-dvbsddevice

tasks

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

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

dvd

tasks

install libdvd-pkg
---
# file: roles/dvd/tasks/main.yml

- name: preconfigure libdvd-pkg
  shell: |
    echo 'libdvd-pkg libdvd-pkg/post-invoke_hook-install boolean true' | debconf-set-selections
    echo 'libdvd-pkg libdvd-pkg/build boolean true' | debconf-set-selections    

- name: apt | install libdvd-pkg
  apt:
    name: '{{ item }}'
    state: present
    install_recommends: no
  with_items:
    - libdvd-pkg

kodi

tasks

Install KODI
---

- name: change udev rule to allow KODI to eject optical disks
  shell: sed 's/--lock-media //' /lib/udev/rules.d/60-cdrom_id.rules > /etc/udev/rules.d/60-cdrom_id.rules
  args:
    creates: /etc/udev/rules.d/60-cdrom_id.rules

- name: apt | install kodi packages
  apt:
    name: '{{ item }}'
    state: present
    install_recommends: no
  with_items:
    - kodi
    - kodi-pvr-vdr-vnsi
    - kodi-eventclients-xbmc-send  

- name: create kodi.service for the user session
  template:
      src: 'templates/kodi.service.j2'
      dest: '{{ vdr.home }}/.config/systemd/user/kodi.service'
      mode: 0755
      owner: '{{ vdr.user }}'
      group: '{{ vdr.group }}'

templates

kodi.service
[Unit]
Description=Start kodi in user session

[Service]
Type=simple
PassEnvironment=DISPLAY XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS
ExecStartPre=-/usr/bin/set-kodi-display
ExecStart=/usr/bin/kodi
ExecStop=/bin/bash -c "/usr/bin/kodi-send --action=QUIT; while /usr/bin/pgrep kodi; do sleep 1;  done; sleep 1"
TimeoutStopSec=10
SuccessExitStatus=0 127
Restart=on-failure

template-test

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

- name: test templates
  template:
    src: templates/test.j2
    dest: /tmp/test.txt
{{ ansible_managed_file | 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 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 %}

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

grub-config

default variables

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

tasks

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

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

templates

#!/bin/sh
exec tail -n +3 $0

# This file is configured by the ansible configuration for yaVDR

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

#!/usr/bin/env python

# 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)
notes:
   - requres python-pyusb and python-kmodpy
requirements: [ ]
author: "Alexander Grothe <seahawk1986@gmx.de>"
'''

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

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

import kmodpy
from ansible.module_utils.basic import *


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

def get_pci_devices():
    for device in glob.glob('/sys/devices/pci*/*:*:*/'):
        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)

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 ("{:04x}:{:04x}".format(d.idVendor, d.idProduct))
    return [entry for entry in get_entries(iterator)]

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


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']
    if collect_usb:
        usb_devices = format_device_list(usb.core.find(find_all=True))
    else:
        usb_device = []
    if collect_pci:
        pci_devices = format_device_list(get_pci_devices())
    else:
        pci_devices = []
    if collect_modules:
        k = kmodpy.Kmod()
        modules = [m[0] for m in k.loaded()]
    else:
        modules = []
    if collect_gpus:
        gpus = format_gpu_device_list(get_pci_devices())
    else:
        gpus = []
    data = {'usb': usb_devices, 'pci': pci_devices, 'modules': modules, 'gpus': gpus}
    module.exit_json(changed=False, ansible_facts=data, msg=data)


if __name__ == '__main__':  
    main()

satip_facts.py

#!/usr/bin/env python2

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 the boolean variable 'satip_detected'
'''
EXAMPLES = '''
- name: "detect SAT>IP Server on the network"
  action: satip_facts

- debug:
    var: satip_detected
'''

import json
import socket
import sys
import time

from ansible.module_utils.basic import *

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

def main():
    module = AnsibleModule(argument_spec={}, supports_check_mode=True,)
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # according to Sat>IP Specification 1.2.2, p. 20
    # a client should send three requests within 100 ms with a ttl of 2
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
    sock.settimeout(SSDP_MX + 0.5)
    for _ in range(3):
        sock.sendto(ssdpRequest, (SSDP_ADDR, SSDP_PORT))
        time.sleep(0.03)
    try:
        response = sock.recv(1000)
        if response and "SERVER:" in response:
            got_response = True
        else:
            raise ValueError('No satip server detected')
    except (socket.timeout, ValueError):
        got_response = False

    module.exit_json(changed=False, ansible_facts={'satip_detected': got_response})

if __name__ == '__main__':  
    main()

xrandr_facts.py

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

from ansible.module_utils.basic import *

DOCUMENTATION = '''
---
module: xrandr_facts
short_description: "gather facts about connected monitors and available modelines"
description:
     - This module needs a running x-server on a given display in order to successfully call xrandr.
       Returns the dictionary "xrandr", wich contains all screens with output states, connected displays,
       EDID info and their modes and a recommendation for the best fitting tv mode.
options:
    display:
        required: False
        default: ":0"
        description:
          - the DISPLAY variable to use when calling xrandr
    multi_display:
        required: False
        default: "False"
        description:
          - check additional screens (:0.0 .. :0.n) until xrandr fails to collect information
    preferred_outpus:
        required: False
        default: ["HDMI", "DP", "DVI", "VGA", "TV": 0]
        description:
          - ranking of the preferred display connectors
    preferred_refreshrates:
        required: False
        default: ["50", "60", "75", "30", "25"]
        description:
          - ranking of the preferred display refreshrate
    preferred_resolutions:
        required: False
        default: ["7680x4320", "3840x2160", "1920x1080", "1280x720", "720x576"]
        description:
           - ranking of the preferred display resolutions
    write_edids:
        required: False
        default: True
        description:
           - write edid data to /etc/X11/edid.{connector}.bin
'''
EXAMPLES = '''
- name: "collect facts for connected displays"
  action: xserver_facts
    display: ":0"

- debug:
    var: xrandr
'''

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

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

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

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

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

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

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

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

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

def output_data(data, write_edids=True):
    if data:
        modes = []
        for _, screen_data in data.items():
            for connector, connection_data in screen_data.items():
                if connection_data.get('EDID') and write_edids:
                    with open('/etc/X11/edid.{}.bin'.format(connector), 'wb') as edid:
                        edid.write(binascii.a2b_hex(connection_data['EDID']))
                for resolution, refreshrates in connection_data['modes'].items():
                    for refreshrate in refreshrates:
                        modes.append(Mode(connector, resolution, refreshrate))
        if modes:
            best_mode = max(modes, key=sort_mode)
            data['best_tv_mode'] = best_mode

    #print(json.dumps(data, sort_keys=True, indent=4))
    module.exit_json(changed=True if write_edids else False, ansible_facts={'xrandr': data})

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

Handlers

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

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

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

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

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

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

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