diff --git a/monitorDaemon.py b/monitorDaemon.py --- a/monitorDaemon.py +++ b/monitorDaemon.py @@ -1,295 +1,310 @@ #!/usr/bin/env python import subprocess import os import hashlib import binascii import re import sys import configparser from Xlib import X, display, Xutil from Xlib.ext import randr +# TODO: We could move away from outputs completely +# and make configuration EDID-based only + class xrandrInterface: def __init__(self): self.display = display.Display() self.screen = self.display.screen() - self.window = self.screen.root.create_window(50, 50, 300, 200, 2, + self.window = self.screen.root.create_window(0, 0, 1, 1, 1, self.screen.root_depth, event_mask = (X.ExposureMask | X.StructureNotifyMask | X.ButtonPressMask | X.ButtonReleaseMask | X.Button1MotionMask) ) #self.window.xrandr_select_input( # randr.RRScreenChangeNotifyMask # | randr.RRCrtcChangeNotifyMask # | randr.RROutputChangeNotifyMask # | randr.RROutputPropertyNotifyMask # ) self.outputStatus = {} self.crtcStatus = {} self.getScreenInformation() def getScreenInformation(self): resources = self.window.xrandr_get_screen_resources()._data outputs = resources['outputs'] self.outputStatus = {} self.crtcStatus = {} for crtc in resources['crtcs']: crtcinfo = self.display.xrandr_get_crtc_info(crtc, resources['config_timestamp'])._data self.crtcStatus[crtc] = {} #self.crtcStatus[crtc]['width'] = crtcinfo['width'] #self.crtcStatus[crtc]['height'] = crtcinfo['height'] self.crtcStatus[crtc]['x'] = crtcinfo['x'] self.crtcStatus[crtc]['y'] = crtcinfo['y'] self.crtcStatus[crtc]['mode'] = crtcinfo['mode'] self.crtcStatus[crtc]['outputs'] = crtcinfo['outputs'] self.crtcStatus[crtc]['possible_outputs'] = crtcinfo['possible_outputs'] modedata = self.parseModes(resources['mode_names'], resources['modes']) #print(self.crtcStatus) for output in outputs: info = self.display.xrandr_get_output_info(output, resources['config_timestamp'])._data #print(info) if info['connection'] != 0: continue edid = self.get_edid(output) name = info['name'] crtc = info['crtc'] if crtc == 0: continue else: mode = self.crtcStatus[crtc]['mode'] modename = modedata[mode]['name'] posx = self.crtcStatus[crtc]['x'] posy = self.crtcStatus[crtc]['y'] self.outputStatus[name] = {} self.outputStatus[name]['edid'] = edid self.outputStatus[name]['crtc'] = crtc self.outputStatus[name]['modename'] = modename self.outputStatus[name]['posx'] = posx self.outputStatus[name]['posy'] = posy #print(self.display.xrandr_list_output_properties(output)._data) """ print(self.outputStatus) op = self.getOutputForOutputname('eDP1') print(op) md = self.findModeByName(op, '1920x1080') print(md) cr = self.getUsableCrtcForOutput(op) if not cr: #print('Disabling CRTC') cr = self.getCurrentCrtcForOutput(op) self.disableCrtc(cr) else: #print('Enabling CRTC') print(cr) self.configureCrtcForOutputAndMode(cr, op, md, 0, 0) """ def applyConfiguration(self, config): - # Get list of all outputs - # Disable all outputs not in the config - # Check if an output already has a CRTC - # If yes, reconfigure the current CRTC - # If not, find a new CRTC and configure it accordingly - print('FIXME') - pass + # FIXME: + # Rework to use getOutputForEdid and applying + # configuration based on EDID only + resources = self.window.xrandr_get_screen_resources()._data + outputs = resources['outputs'] + for output in outputs: + info = self.display.xrandr_get_output_info(output, resources['config_timestamp'])._data + name = info['name'] + found = False + for nm in config: + if nm == name and config[nm]['modename'] != '0': + found = True + + if not found: + crtc = self.getCurrentCrtcForOutput(output) + if crtc: + self.disableCrtc(crtc) + + for nm in config: + if config[nm]['modename'] != '0': + output = self.getOutputForOutputname(nm) + crtc = self.getCurrentCrtcForOutput(output) + if not crtc: + crtc = self.getUsableCrtcForOutput(output) + mode = self.findModeByName(output, config[nm]['modename']) + self.configureCrtcForOutputAndMode(crtc, output, mode, config[nm]['posx'], config[nm]['posy']) def getScreenConfiguration(self): return self.outputStatus def disableCrtc(self, crtc): resources = self.window.xrandr_get_screen_resources()._data - self.display.xrandr_set_crtc_config(65, resources['config_timestamp'], 0, 0, 0, randr.Rotate_0, []) + self.display.xrandr_set_crtc_config(crtc, resources['config_timestamp'], 0, 0, 0, randr.Rotate_0, []) def getCurrentCrtcForOutput(self, output): resources = self.window.xrandr_get_screen_resources()._data info = self.display.xrandr_get_output_info(output, resources['config_timestamp'])._data return info['crtc'] def configureCrtcForOutputAndMode(self, crtc, output, mode, posx, posy): resources = self.window.xrandr_get_screen_resources()._data self.display.xrandr_set_crtc_config(crtc, resources['config_timestamp'], posx, posy, mode, randr.Rotate_0, [output]) def getOutputForOutputname(self, name): resources = self.window.xrandr_get_screen_resources()._data for output in resources['outputs']: info = self.display.xrandr_get_output_info(output, resources['config_timestamp'])._data if info['name'] == name: return output return None def getUsableCrtcForOutput(self, output): resources = self.window.xrandr_get_screen_resources()._data for crtc in resources['crtcs']: crtcinfo = self.display.xrandr_get_crtc_info(crtc, resources['config_timestamp'])._data if len(crtcinfo['outputs']) == 0: if output in crtcinfo['possible_outputs']: return crtc return None def findModeByName(self, output, modename): resources = self.window.xrandr_get_screen_resources()._data modedata = self.parseModes(resources['mode_names'], resources['modes']) info = self.display.xrandr_get_output_info(output, resources['config_timestamp'])._data modes = info['modes'] for mode in modes: if modedata[mode]['name'] == modename: return mode return None def parseModes(self, mode_names, modes): lastIdx = 0 modedatas = dict() for mode in modes: modedata = dict(mode._data) modedata['name'] = mode_names[lastIdx:lastIdx + modedata['name_length']] modedatas[modedata['id']] = modedata lastIdx += modedata['name_length'] return modedatas + + def getOutputForEdid(self, edid): + resources = self.window.xrandr_get_screen_resources()._data + for output in resources['outputs']: + medid = self.get_edid(output) + if edid == medid: + return output + return 0 # query the edid module for output_nr def get_edid(self, output_nr): PROPERTY_EDID = self.display.intern_atom("EDID", 0) INT_TYPE = 19 props = self.display.xrandr_list_output_properties(output_nr) if PROPERTY_EDID in props.atoms: try: rawedid = self.display.xrandr_get_output_property(output_nr, PROPERTY_EDID, INT_TYPE, 0, 400) except: print("Error loading EDID data of output ", output_nr) return None edidstream = rawedid._data['value'] hx = bytearray() hx.extend(edidstream) hs = hashlib.sha1(hx).hexdigest() return hs else: return None class ConfigManager: def __init__(self, cfgfile): self.cfgfile = cfgfile if not os.path.exists(cfgfile): self.recreateCfg() self.dic = {} self.cfg2dic() def getDic(self): return self.dic def recreateCfg(self): print('Creating new config file: ' + self.cfgfile) config = configparser.ConfigParser() config.add_section('General') config.set('General', 'numEntries', '0') with open(self.cfgfile, 'w') as configfile: config.write(configfile) def cfg2dic(self): dic = {} config = configparser.ConfigParser() config.read(self.cfgfile) for section in config.sections(): dic[section] = {} for key in config[section]: dic[section][key] = config.get(section, key) self.dic = dic return dic def dic2cfg(self): config = configparser.ConfigParser() for section in self.dic: config.add_section(section) for key in self.dic[section]: config.set(section, key, self.dic[section][key]) with open(self.cfgfile, 'w') as configfile: config.write(configfile) def getConfig(self, screenConfiguration): ne = int(self.dic['General']['numentries']) for ii in range(0, ne): name = 'Config' + str(ii+1) if int(self.dic[name]['edidcount']) != len(screenConfiguration): continue found = True for outputName in screenConfiguration: lFound = False for jj in range(0, len(screenConfiguration)): if self.dic[name]['edid' + str(jj+1)] == screenConfiguration[outputName]['edid']: lFound = True if lFound and found: found = True else: found = False if found: for jj in range(0, len(screenConfiguration)): return self.dic[name] return None def saveConfiguration(self, screenConfiguration): ne = int(self.dic['General']['numentries']) ne += 1 self.dic['General']['numentries'] = str(ne) name = 'Config' + str(ne) self.dic[name] = {} self.dic[name]['edidcount'] = str(len(screenConfiguration)) cnt = 1 for outputName in screenConfiguration.keys(): self.dic[name]['edid'+str(cnt)] = screenConfiguration[outputName]['edid'] self.dic[name]['output'+str(cnt)] = outputName self.dic[name]['mode'+str(cnt)] = screenConfiguration[outputName]['modename'] self.dic[name]['posx'+str(cnt)] = str(screenConfiguration[outputName]['posx']) self.dic[name]['posy'+str(cnt)] = str(screenConfiguration[outputName]['posy']) cnt += 1 - self.dic2cfg(cfgfile) - return True - - def loadAndApplyConfig(self, dic, edids): - cfg = getConfig(dic, edids) - print(cfg) - for edid in cfg: - #FIXME: This does not work with negative numbers! - if edid[2] == '0': - subprocess.call(['xrandr', '--output', edid[1], '--off']) - continue - modepos = re.findall(r'\d+', edid[2]) - mode = modepos[0] + 'x' + modepos[1] - pos = modepos[2] + 'x' + modepos[3] - print(pos) - subprocess.call(['xrandr', '--output', edid[1], '--mode', mode, '--pos', pos]) + self.dic2cfg() return True settingsdir = os.path.join(os.environ['HOME'], '.config') cfgfile = os.path.join(settingsdir, 'monitorDaemon.ini') cm = ConfigManager(cfgfile) cfg = cm.getDic() #print(cfg) xri = xrandrInterface() screenConfig = xri.getScreenConfiguration() #print(screenConfig) storedConfig = cm.getConfig(screenConfig) if storedConfig: print('LoadAndApply') xri.applyConfiguration(screenConfig) else: print('SaveConfig') cm.saveConfiguration(screenConfig) sys.exit(0)