Add support for drm subsystem to xrandr_facts.py

This commit is contained in:
Alexander Grothe 2019-01-07 10:33:27 +01:00
parent 387daa0134
commit 58859dd8d7
4 changed files with 1085 additions and 719 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1369,8 +1369,8 @@ description of the config file format.
<system name="Timers" />
<system name="Recordings" />
<plugin name="tvguide" />
<plugin name="desktop" />
<command name="Kodi" execute="frontend-dbus-send switchto kodi" />
<plugin name="desktop" />
<menu name="{{ 'VDR Plugins' | translate }}">
<plugin name="epgsearchonly" />
<plugin name="quickepgsearch" />
@ -1382,7 +1382,7 @@ description of the config file format.
<plugin name="osd2web" />
</menu>
<menu name="{{ 'System' | translate }}">
<menu name="Befehle">
<menu name="{{ 'Commands' | translate }}">
<command name="{{ "Safely remove usb mass storage" | translate }}" confirm="yes" execute="/usr/bin/vdr-mounter --unmount-all &amp;> /dev/null" />
<command name="{{ "Update vdr recordings list" | translate }}" execute="/usr/bin/vdr-dbus-send /Recordings recording.Update &amp;> /dev/null " />
<command name="{{ "Restart VDR" |translate }}" confirm="yes" execute="sudo /sbin/initctl restart vdr" />
@ -2499,6 +2499,10 @@ preferred_refreshrates:
var: xorg.secondary
when: xorg.secondary is defined
- debug:
var: drm
when: drm is defined
- name: "stop x-verbose@vt7.service"
systemd:
name: "x-verbose@vt7.service"
@ -2526,9 +2530,15 @@ preferred_refreshrates:
copy:
content: '{{ {"xrandr": xrandr} | to_nice_json }}'
dest: /etc/ansible/facts.d/xrandr.fact
- name: write drm variable as local fact
copy:
content: '{{ {"drm": drm} | to_nice_json }}'
dest: /etc/ansible/facts.d/drm.fact
when:
- xrandr is defined
- xorg is defined
- drm is defined
- name: update xorg and xrandr variable with values from local facts if needed
set_fact:
@ -2598,8 +2608,9 @@ template:
:ID: eb4e27ae-937f-4893-91c2-cf3d731536db
:END:
#+BEGIN_SRC conf :tangle roles/yavdr-xorg/templates/grub.d/intel.j2 :mkdirp yes :padline no
{{ ansible_managed | comment }}
{% set output_flag = 'D' if ("HDMI" in xorg.primary.connector or "DVI" in xorg.primary.connector or "DP" in xorg.primary.connector) else 'e' %}
GRUB_CMDLINE_LINUX+=" video={{ xorg.primary.drm_connector }}:{{ xorg.primary.mode|replace('_', '@') }}{{ output_flag }} drm.edid_firmware={{ xorg.primary.drm_connector }}:edid/edid.bin"
GRUB_CMDLINE_LINUX+=" video={{ drm.primary.drm_connector }}:{{ xorg.primary.mode|replace('_', '@') }}{{ output_flag }} drm.edid_firmware={{ drm.primary.drm_connector }}:edid/{{ drm.primary.edid }} {% for ignored_c in drm.ignored_outputs %}video={{ ignored_c }}:d{% endfor %}"
# TODO: configure additional monitors (primary monitor on, all others off)
#+END_SRC
**** xorg
@ -6530,11 +6541,11 @@ grub:
:PROPERTIES:
:ID: b6b30fe9-27b4-4713-8a0d-56d4ef1919d4
:END:
#+BEGIN_SRC shell :tangle roles/grub-config/templates/50_custom.j2 :mkdirp yes :padline no
#+BEGIN_SRC jinja2 :tangle roles/grub-config/templates/50_custom.j2 :mkdirp yes :padline no
#!/bin/sh
exec tail -n +3 $0
{{ ansible_managed | comment }}
# This file is configured by the ansible configuration for yaVDR
exec tail -n +3 $0
{% if system.shutdown is defined and system.shutdown == 'reboot' %}
menuentry "PowerOff" {

View File

@ -3,20 +3,31 @@ from __future__ import print_function
import ast
import binascii
import csv
import os
import re
import subprocess
from collections import namedtuple
from glob import glob
from ansible.module_utils.basic import *
from ansible.module_utils.basic import AnsibleModule
DOCUMENTATION = '''
---
module: xrandr_facts
short_description: "gather facts about connected monitors and available modelines"
description:
- This module needs a running x-server on a given display in order to successfully call xrandr.
Returns the dictionary "xrandr", wich contains all screens with output states, connected displays,
EDID info and their modes and a recommendation for the best fitting tv mode.
- This module needs a running x-server on a given display in
order to successfully call xrandr. Returns the dictionary
"xrandr", wich contains all screens with output states,
connected displays, EDID info and their modes and a
recommendation for the best fitting tv mode, the dictionary
"xorg" with a recommendation for the primary and secondary
output and a "drm" dictionary whose "primary" key associates
the primary device name of the drm subsystem with the one from
the xrandr output by comparing the edid data and a list of
"ignored_devices". Note that the proprietary nvidia driver
doesn't support KMS/drm, so in this case the dictionary is
always empty.
options:
display:
required: False
@ -25,7 +36,7 @@ options:
- the DISPLAY variable to use when calling xrandr
preferred_outputs:
required: False
default: ["HDMI", "DP", "DVI", "VGA", "TV", "Virtual"]
default: ["HDMI", "DP", "eDP", "DVI", "VGA", "TV", "Virtual"]
description:
- ranking of the preferred display connectors
preferred_refreshrates:
@ -43,6 +54,7 @@ options:
default: True
description:
- write edid data to /etc/X11/edid.{connector}.bin
- the dictionary "drm" can only be filled with data if write_edids is enabled
'''
EXAMPLES = '''
- name: "collect facts for connected displays"
@ -54,12 +66,15 @@ EXAMPLES = '''
- debug:
var: xorg
- debug:
var: drm
'''
ARG_SPECS = {
'display': dict(default=":0", type='str', required=False),
'preferred_outputs': dict(
default=["HDMI", "DP", "DVI", "VGA", "TV", "Virtual"], type='list', required=False),
default=["HDMI", "DP", "eDP", "DVI", "VGA", "TV", "Virtual"], type='list', required=False),
'preferred_refreshrates': dict(
default=[50, 60, 75, 30, 25], type='list', required=False),
'preferred_resolutions': dict(
@ -67,21 +82,23 @@ ARG_SPECS = {
"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+:)(?:.*)")
SCREEN_REGEX = re.compile(r"^(?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,}).*")
r"^(?P<connector>.*-?\d+)\s(?P<connection_state>connected|disconnected)\s(?P<primary>primary)?")
MODE_REGEX = re.compile(r"^\s+(?P<resolution>\d{3,}x\d{3,}).*")
Mode = namedtuple('Mode', ['connection', 'resolution', 'refreshrate'])
def check_for_screen(line):
"""check line for screen information"""
match = re.match(SCREEN_REGEX, line)
if match:
return match.groupdict()['screen']
def check_for_connection(line):
"""check line for connection name and state"""
match = re.match(CONNECTOR_REGEX, line)
@ -93,10 +110,12 @@ def check_for_connection(line):
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
@ -112,12 +131,13 @@ def sort_mode(mode):
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'))
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 = {}
@ -135,7 +155,7 @@ def parse_xrandr_verbose(iterator):
'preferred': '',
'current': '',
'auto': '',
}
}
elif is_connected and 'EDID:' in line:
edid_str = ""
outer_indentation = get_indentation(line)
@ -146,7 +166,7 @@ def parse_xrandr_verbose(iterator):
else:
break
xorg[screen][connector]['EDID'] = edid_str
elif is_connected and "MHz" in line and not "Interlace" in line:
elif is_connected and "MHz" in line and "Interlace" not in line:
match = re.match(MODE_REGEX, line)
if match:
match = match.groupdict()
@ -155,7 +175,7 @@ def parse_xrandr_verbose(iterator):
while True:
line = next(iterator)
if line.strip().startswith('v:'):
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:
@ -171,6 +191,7 @@ def parse_xrandr_verbose(iterator):
break
return xorg
def parse_edid_data(edid):
vendor = "Unknown"
model = "Unknown"
@ -187,19 +208,20 @@ def parse_edid_data(edid):
model = line.strip().split('"')[1]
return vendor, model
def collect_nvidia_data():
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]+)'
))
r'(?P<domain>[0-9a-fA-F]+)'
r':'
r'(?P<bus>[0-9a-fA-F]+)'
r':'
r'(?P<device>[0-9a-fA-F]+)'
r'\.'
r'(?P<function>[0-9a-fA-F]+)'
))
try:
data = subprocess.check_output(["nvidia-smi", "--query-gpu=name,pci.bus_id", "--format=csv", "-i0"],
universal_newlines=True)
data = subprocess.check_output(["nvidia-smi", "--query-gpu=name,pci.bus_id",
"--format=csv", "-i0"], universal_newlines=True)
except subprocess.CalledProcessError:
pass
except OSError:
@ -218,8 +240,68 @@ def collect_nvidia_data():
raise ValueError
Connector = namedtuple('Connector', "name xrandr_edid")
def find_drm_connectors(primary):
"""
takes a namedtuple Connector as the only argument.
returns a dict with the following schema:
{
'primary': {
'edid': 'edid.HDMI-1.bin',
'drm_connector': 'HDMI-A-1',
'xrandr_connector': 'HDMI-1',
},
'ignored_outputs': ['HDMI-A-2', 'DP-1']
}
"""
STATUS_GLOB = '/sys/class/drm/card0*/status'
CONNECTOR_RE = re.compile('card0-(?P<connector>[^/]+)/status')
try:
with open(primary.xrandr_edid, 'rb') as f:
xrandr_edid_bytes = f.read()
except IOError:
xrandr_edid_bytes = b''
drm = {'primary': {}, 'ignored_outputs': []}
for status_p in glob(STATUS_GLOB):
match = re.search(CONNECTOR_RE, status_p)
if match:
drm_connector = match.group('connector')
else:
continue
try:
with open(status_p) as f:
connected = f.read().strip() == 'connected'
except IOError:
continue
if connected and xrandr_edid_bytes:
drm_edid = os.path.join(os.path.dirname(status_p), 'edid')
try:
with open(drm_edid, 'rb') as f:
is_primary = f.read() == xrandr_edid_bytes
except IOError:
continue
else:
if is_primary:
drm['primary'] = {
'edid': os.path.basename(primary.xrandr_edid),
'drm_connector': drm_connector,
'xrandr_connector': primary.name,
}
continue
drm['ignored_outputs'].append(drm_connector)
return drm
def output_data(data, write_edids=True):
result = {}
drm = {}
if data:
modes = []
for _, screen_data in data.items():
@ -253,22 +335,30 @@ def output_data(data, write_edids=True):
connector_0, resolution_0, refreshrate_0 = max(modes, key=sort_mode)[:3]
vendor_0, model_0 = parse_edid_data('/etc/X11/edid.{}.bin'.format(connector_0))
create_entry(result, 'primary', connector_0, resolution_0, refreshrate_0, vendor_0, model_0)
create_entry(result, 'primary', connector_0, resolution_0,
refreshrate_0, vendor_0, model_0)
if write_edids:
drm = find_drm_connectors(Connector(connector_0,
'/etc/X11/edid.{}.bin'.format(connector_0)))
# check if additional monitors exist
other_modes = [mode for mode in modes if mode[0] != connector_0]
if other_modes:
connector_1, resolution_1, refreshrate_1 = max(other_modes, key=sort_mode)[:3]
vendor_1, model_1 = parse_edid_data('/etc/X11/edid.{}.bin'.format(connector_1))
create_entry(result, 'secondary', connector_1, resolution_1, refreshrate_1, vendor_1, model_1)
create_entry(result, 'secondary', connector_1, resolution_1,
refreshrate_1, vendor_1, model_1)
module.exit_json(changed=True if write_edids else False,
ansible_facts={'xrandr': data, 'xorg': result, 'drm': drm})
#print(json.dumps(data, sort_keys=True, indent=4))
module.exit_json(changed=True if write_edids else False, ansible_facts={'xrandr': data, 'xorg': result})
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()
d = subprocess.check_output(['xrandr', '-d', module.params['display'], '--verbose'],
universal_newlines=True).splitlines()
except subprocess.CalledProcessError:
xorg_data = {}
else:

View File

@ -98,6 +98,10 @@
var: xorg.secondary
when: xorg.secondary is defined
- debug:
var: drm
when: drm is defined
- name: "stop x-verbose@vt7.service"
systemd:
name: "x-verbose@vt7.service"
@ -125,9 +129,15 @@
copy:
content: '{{ {"xrandr": xrandr} | to_nice_json }}'
dest: /etc/ansible/facts.d/xrandr.fact
- name: write drm variable as local fact
copy:
content: '{{ {"drm": drm} | to_nice_json }}'
dest: /etc/ansible/facts.d/drm.fact
when:
- xrandr is defined
- xorg is defined
- drm is defined
- name: update xorg and xrandr variable with values from local facts if needed
set_fact: