Add support for drm subsystem to xrandr_facts.py
This commit is contained in:
parent
387daa0134
commit
58859dd8d7
1623
Manual.html
1623
Manual.html
File diff suppressed because it is too large
Load Diff
23
Manual.org
23
Manual.org
@ -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 &> /dev/null" />
|
||||
<command name="{{ "Update vdr recordings list" | translate }}" execute="/usr/bin/vdr-dbus-send /Recordings recording.Update &> /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" {
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user