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="Timers" />
<system name="Recordings" /> <system name="Recordings" />
<plugin name="tvguide" /> <plugin name="tvguide" />
<plugin name="desktop" />
<command name="Kodi" execute="frontend-dbus-send switchto kodi" /> <command name="Kodi" execute="frontend-dbus-send switchto kodi" />
<plugin name="desktop" />
<menu name="{{ 'VDR Plugins' | translate }}"> <menu name="{{ 'VDR Plugins' | translate }}">
<plugin name="epgsearchonly" /> <plugin name="epgsearchonly" />
<plugin name="quickepgsearch" /> <plugin name="quickepgsearch" />
@ -1382,7 +1382,7 @@ description of the config file format.
<plugin name="osd2web" /> <plugin name="osd2web" />
</menu> </menu>
<menu name="{{ 'System' | translate }}"> <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="{{ "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="{{ "Update vdr recordings list" | translate }}" execute="/usr/bin/vdr-dbus-send /Recordings recording.Update &amp;> /dev/null " />
<command name="{{ "Restart VDR" |translate }}" confirm="yes" execute="sudo /sbin/initctl restart vdr" /> <command name="{{ "Restart VDR" |translate }}" confirm="yes" execute="sudo /sbin/initctl restart vdr" />
@ -2499,6 +2499,10 @@ preferred_refreshrates:
var: xorg.secondary var: xorg.secondary
when: xorg.secondary is defined when: xorg.secondary is defined
- debug:
var: drm
when: drm is defined
- name: "stop x-verbose@vt7.service" - name: "stop x-verbose@vt7.service"
systemd: systemd:
name: "x-verbose@vt7.service" name: "x-verbose@vt7.service"
@ -2526,9 +2530,15 @@ preferred_refreshrates:
copy: copy:
content: '{{ {"xrandr": xrandr} | to_nice_json }}' content: '{{ {"xrandr": xrandr} | to_nice_json }}'
dest: /etc/ansible/facts.d/xrandr.fact 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: when:
- xrandr is defined - xrandr is defined
- xorg is defined - xorg is defined
- drm is defined
- name: update xorg and xrandr variable with values from local facts if needed - name: update xorg and xrandr variable with values from local facts if needed
set_fact: set_fact:
@ -2598,8 +2608,9 @@ template:
:ID: eb4e27ae-937f-4893-91c2-cf3d731536db :ID: eb4e27ae-937f-4893-91c2-cf3d731536db
:END: :END:
#+BEGIN_SRC conf :tangle roles/yavdr-xorg/templates/grub.d/intel.j2 :mkdirp yes :padline no #+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' %} {% 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) # TODO: configure additional monitors (primary monitor on, all others off)
#+END_SRC #+END_SRC
**** xorg **** xorg
@ -6530,11 +6541,11 @@ grub:
:PROPERTIES: :PROPERTIES:
:ID: b6b30fe9-27b4-4713-8a0d-56d4ef1919d4 :ID: b6b30fe9-27b4-4713-8a0d-56d4ef1919d4
:END: :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 #!/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' %} {% if system.shutdown is defined and system.shutdown == 'reboot' %}
menuentry "PowerOff" { menuentry "PowerOff" {

View File

@ -3,20 +3,31 @@ from __future__ import print_function
import ast import ast
import binascii import binascii
import csv import csv
import os
import re import re
import subprocess import subprocess
from collections import namedtuple from collections import namedtuple
from glob import glob
from ansible.module_utils.basic import * from ansible.module_utils.basic import AnsibleModule
DOCUMENTATION = ''' DOCUMENTATION = '''
--- ---
module: xrandr_facts module: xrandr_facts
short_description: "gather facts about connected monitors and available modelines" short_description: "gather facts about connected monitors and available modelines"
description: description:
- This module needs a running x-server on a given display in order to successfully call xrandr. - This module needs a running x-server on a given display in
Returns the dictionary "xrandr", wich contains all screens with output states, connected displays, order to successfully call xrandr. Returns the dictionary
EDID info and their modes and a recommendation for the best fitting tv mode. "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: options:
display: display:
required: False required: False
@ -25,7 +36,7 @@ options:
- the DISPLAY variable to use when calling xrandr - the DISPLAY variable to use when calling xrandr
preferred_outputs: preferred_outputs:
required: False required: False
default: ["HDMI", "DP", "DVI", "VGA", "TV", "Virtual"] default: ["HDMI", "DP", "eDP", "DVI", "VGA", "TV", "Virtual"]
description: description:
- ranking of the preferred display connectors - ranking of the preferred display connectors
preferred_refreshrates: preferred_refreshrates:
@ -43,6 +54,7 @@ options:
default: True default: True
description: description:
- write edid data to /etc/X11/edid.{connector}.bin - write edid data to /etc/X11/edid.{connector}.bin
- the dictionary "drm" can only be filled with data if write_edids is enabled
''' '''
EXAMPLES = ''' EXAMPLES = '''
- name: "collect facts for connected displays" - name: "collect facts for connected displays"
@ -54,12 +66,15 @@ EXAMPLES = '''
- debug: - debug:
var: xorg var: xorg
- debug:
var: drm
''' '''
ARG_SPECS = { ARG_SPECS = {
'display': dict(default=":0", type='str', required=False), 'display': dict(default=":0", type='str', required=False),
'preferred_outputs': dict( '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( 'preferred_refreshrates': dict(
default=[50, 60, 75, 30, 25], type='list', required=False), default=[50, 60, 75, 30, 25], type='list', required=False),
'preferred_resolutions': dict( 'preferred_resolutions': dict(
@ -67,21 +82,23 @@ ARG_SPECS = {
"7680x4320", "3840x2160", "1920x1080", "1280x720", "720x576"], "7680x4320", "3840x2160", "1920x1080", "1280x720", "720x576"],
type='list', required=False), type='list', required=False),
'write_edids': dict(default=True, type='bool', 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( CONNECTOR_REGEX = re.compile(
"^(?P<connector>.*-?\d+)\s(?P<connection_state>connected|disconnected)\s(?P<primary>primary)?") r"^(?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_REGEX = re.compile(r"^\s+(?P<resolution>\d{3,}x\d{3,}).*")
Mode = namedtuple('Mode', ['connection', 'resolution', 'refreshrate']) Mode = namedtuple('Mode', ['connection', 'resolution', 'refreshrate'])
def check_for_screen(line): def check_for_screen(line):
"""check line for screen information""" """check line for screen information"""
match = re.match(SCREEN_REGEX, line) match = re.match(SCREEN_REGEX, line)
if match: if match:
return match.groupdict()['screen'] return match.groupdict()['screen']
def check_for_connection(line): def check_for_connection(line):
"""check line for connection name and state""" """check line for connection name and state"""
match = re.match(CONNECTOR_REGEX, line) 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 is_connected = True if match['connection_state'] == 'connected' else False
return connector, is_connected return connector, is_connected
def get_indentation(line): def get_indentation(line):
"""return the number of leading whitespace characters""" """return the number of leading whitespace characters"""
return len(line) - len(line.lstrip()) return len(line) - len(line.lstrip())
def sort_mode(mode): def sort_mode(mode):
"""rate modes by several criteria""" """rate modes by several criteria"""
connection_score = 0 connection_score = 0
@ -118,6 +137,7 @@ def sort_mode(mode):
connection_score = len(preferred_outputs) - preferred_outputs.index(connection) connection_score = len(preferred_outputs) - preferred_outputs.index(connection)
return (rrate_score, resolution_score, x_resolution, y_resolution, connection_score) return (rrate_score, resolution_score, x_resolution, y_resolution, connection_score)
def parse_xrandr_verbose(iterator): def parse_xrandr_verbose(iterator):
"""parse the output of xrandr --verbose using an iterator delivering single lines""" """parse the output of xrandr --verbose using an iterator delivering single lines"""
xorg = {} xorg = {}
@ -146,7 +166,7 @@ def parse_xrandr_verbose(iterator):
else: else:
break break
xorg[screen][connector]['EDID'] = edid_str 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) match = re.match(MODE_REGEX, line)
if match: if match:
match = match.groupdict() match = match.groupdict()
@ -171,6 +191,7 @@ def parse_xrandr_verbose(iterator):
break break
return xorg return xorg
def parse_edid_data(edid): def parse_edid_data(edid):
vendor = "Unknown" vendor = "Unknown"
model = "Unknown" model = "Unknown"
@ -187,19 +208,20 @@ def parse_edid_data(edid):
model = line.strip().split('"')[1] model = line.strip().split('"')[1]
return vendor, model return vendor, model
def collect_nvidia_data(): def collect_nvidia_data():
BusID_RE = re.compile(( BusID_RE = re.compile((
'(?P<domain>[0-9a-fA-F]+)' r'(?P<domain>[0-9a-fA-F]+)'
':' r':'
'(?P<bus>[0-9a-fA-F]+)' r'(?P<bus>[0-9a-fA-F]+)'
':' r':'
'(?P<device>[0-9a-fA-F]+)' r'(?P<device>[0-9a-fA-F]+)'
'\.' r'\.'
'(?P<function>[0-9a-fA-F]+)' r'(?P<function>[0-9a-fA-F]+)'
)) ))
try: try:
data = subprocess.check_output(["nvidia-smi", "--query-gpu=name,pci.bus_id", "--format=csv", "-i0"], data = subprocess.check_output(["nvidia-smi", "--query-gpu=name,pci.bus_id",
universal_newlines=True) "--format=csv", "-i0"], universal_newlines=True)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
pass pass
except OSError: except OSError:
@ -218,8 +240,68 @@ def collect_nvidia_data():
raise ValueError 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): def output_data(data, write_edids=True):
result = {} result = {}
drm = {}
if data: if data:
modes = [] modes = []
for _, screen_data in data.items(): 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] 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)) 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 # check if additional monitors exist
other_modes = [mode for mode in modes if mode[0] != connector_0] other_modes = [mode for mode in modes if mode[0] != connector_0]
if other_modes: if other_modes:
connector_1, resolution_1, refreshrate_1 = max(other_modes, key=sort_mode)[:3] 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)) 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__': if __name__ == '__main__':
module = AnsibleModule(argument_spec=ARG_SPECS, supports_check_mode=False,) module = AnsibleModule(argument_spec=ARG_SPECS, supports_check_mode=False,)
try: 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: except subprocess.CalledProcessError:
xorg_data = {} xorg_data = {}
else: else:

View File

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