#!/usr/bin/python ########################################################################### # xorgconfig.py - description # # ------------------------------ # # begin : Wed Feb 9 2004 # # copyright : (C) 2005 by Simon Edwards # # email : simon@simonzone.com # # # ########################################################################### # # # This program is free software; you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation; either version 2 of the License, or # # (at your option) any later version. # # # ########################################################################### import csv import codecs import locale """ General usage: import xorgconfig config = readConfig("/etc/X11/xorg.conf") input_devices = config.getSections("InputDevice") print input_devices[0].driver options = input_devices[0].options for option in options: # option is of type OptionLine. print option._row[0], if len(option._row)>=2: print "=>",option._row[1] # Add line: Option "XkbModel" "pc105" options.append( options.makeLine("Comment text",["XkbModel" "pc105"]) ) Refactor plan ============= New usage: import xorgconfig config = readConfig("/etc/X11/xorg.conf") input_devices = config.section.InputDevice print input_devices[0].driver options = input_devices[0].options for option in options: # option is of type OptionLine. print option[1], if len(option)>=3: print "=>",option[2] module_section = config.section.module[0] module_section.append(["load","i2c"]) assert module_section.existsLoad("i2c") module_section.removeLoad("i2c") device_section = config.section.device[0] if device_section.busid is not None: print "Found busid:",device_section.busid * direct references to myline._row should be removed. * A ConfigLine should be a subclass of List. With line[i] accessing the parts of the line. * the order of the makeLine() parameters should be reversed. * it should be possible to directly append a list or tuple that represents a line to a section. """ ############################################################################ class ConfigLine(object): """Represents one line from the Xorg.conf file. Each part of the line is printed without quotes. """ def __init__(self,comment,row): self._row = [item for item in row if item!=''] self._comment = comment def toString(self,depth=0): caprow = self._row if len(caprow) > 0: caprow[0] = caprow[0].capitalize() string = ('\t' * (depth/2)) + ' ' * (depth%1) + '\t'.join([unicode(item) for item in caprow]) if self._comment is not None: string += '#' + self._comment return string + '\n' ############################################################################ class ConfigLineQuote(ConfigLine): """Represents one line from the Xorg.conf file. The first item in the line is not quoted, but the remaining items are. """ def toString(self,depth=0): string = ('\t' * (depth/2) + ' ' * (depth%1)) if len(self._row)!=0: string += self._row[0].capitalize() if len(self._row)>1: if len(self._row[0]) < 8: string += '\t' string += '\t"' + '"\t"'.join([unicode(item) for item in self._row[1:]]) + '"' if self._comment is not None: string += '#' + self._comment return string + '\n' ############################################################################ class OptionLine(ConfigLineQuote): def __init__(self,comment,row): arg = ['option'] arg.extend(row) ConfigLineQuote.__init__(self,comment,arg) ############################################################################ class ConfigList(list): def toString(self,depth=0): string = "" for item in self: string += item.toString(depth) return string ############################################################################ class OptionList(ConfigList): name = "option" def __setitem__(self,key,value): list.__setitem__(self,key,value) def makeLine(self,comment,row): return OptionLine(comment,row) def appendOptionRow(self,row): self.append(self.makeLine(None,row)) def removeOptionByName(self,name): name = name.lower() i = 0 while i < len(self): if self[i]._row[1].lower()==name: del self[i] else: i += 1 def getOptionByName(self,name): name = name.lower() for item in self: try: if item._row[1].lower()==name: return item except IndexError: pass return None ############################################################################ class ScreenConfigLine(ConfigLine): def __init__(self,comment,row): arg = ["screen"] arg.extend(row) ConfigLine.__init__(self,comment,arg) def toString(self,depth=0): string = (' ' * depth) try: # Keep on building up the string until the IndexError is thrown. string += self._row[0] i = 1 if self._row[i].isdigit(): string += ' ' + self._row[i] i += 1 string += ' "' + self._row[i] + '"' i += 1 while True: item = self._row[i].lower() if item in ['rightof','leftof','above','below']: string += ' %s "%s"' % (item, self._row[i+1]) i += 1 elif item=='absolute': string += ' %s %d %d' % (item, self._row[i+1], self._row[i+2]) i += 2 elif item.isdigit(): i += 1 string += ' %s %s' % (item,self._row[i]) i += 1 except IndexError: pass if self._comment is not None: string += ' #' + self._comment return string + '\n' ############################################################################ class ScreenConfigList(ConfigList): name = "screen" def __setitem__(self,key,value): list.__setitem__(self,key,value) def makeLine(self,comment,row): return ScreenConfigLine(comment,row) ############################################################################ class ConfigContainer(object): """Acts as a container for ConfigLines and other ConfigContainers. Is used for representing things like the whole config file, sections and subsections inside the file. """ def __init__(self): self._contents = [] def append(self,item): assert (item is not None) self._contents.append(item) def remove(self,item): self._contents.remove(item) def toString(self,depth=0): string = '' for item in self._contents: string += item.toString(depth+1) return string def makeSection(self,comment,row): return Section(comment,row) def isSection(self,name): lname = name.lower() return lname=='section' def isEndSection(self,name): return False def makeLine(self,comment,row): return ConfigLine(comment,row) def isListAttr(self,name): lname = name.lower() return lname in self._listattr def makeListAttr(self,comment,row): listobj = self.__getattr__(row[0].lower()) listobj.append( listobj.makeLine(comment,row[1:]) ) def getSections(self,name): """Get all sections having the given name. Returns a list of ConfigContainer objects. """ name = name.lower() sections = [] for item in self._contents: try: if isinstance(item,ConfigContainer) and item._name.lower()==name: sections.append(item) except IndexError: pass return sections def __getattr__(self,name): if not name.startswith("_"): lname = name.lower() if lname in self._listattr: # Lookup list attributes. for item in self._contents: if isinstance(item,ConfigList) and item.name==lname: return item else: listitem = self._listattr[lname]() self._contents.append(listitem) return listitem else: for item in self._contents: try: if isinstance(item,ConfigLine) and item._row[0].lower()==lname: return item._row[1] except IndexError: pass if lname in self._attr or lname in self._quoteattr: return None raise AttributeError, name def __setattr__(self,name,value): if name.startswith('_'): return super(ConfigContainer,self).__setattr__(name,value) lname = name.lower() for item in self._contents: try: if isinstance(item,ConfigLine) and item._row[0].lower()==lname: item._row[1] = value break except IndexError: pass else: if lname in self._attr or lname in self._quoteattr: line = self.makeLine(None,[name,value]) self.append(line) else: raise AttributeError, name def clear(self): self._contents = [] def getRow(self,name): if not name.startswith("_"): lname = name.lower() for item in self._contents: try: if isinstance(item,ConfigLine) and item._row[0].lower()==lname: return item._row[1:] except IndexError: pass if name in self._attr or name in self._quoteattr: # is a valid name, just has no real value right now. return None raise AttributeError, name ############################################################################ class Section(ConfigContainer): """Represents a Section in the config file. """ # List of config line types allowed inside this section. # A list of strings naming lines that need to be stored in ConfigLine objects. _attr = [] # A list of strings naming the lines that need to be stored in ConfigLineQuote objects. # This is often overridden in subclasses. _quoteattr = [] _listattr = {} def __init__(self,comment,row): ConfigContainer.__init__(self) self._name = row[1] self._comment = comment def __show__(self): """ For debugging """ for a in self._attr: print self._name, "Attribute:", a for a in self._quoteattr: print self._name, "QuoteAttribute:", a for a in self._listattr: print self._name, "ListAttr:", a def isSection(self,name): return name.lower()=='subsection' def isEndSection(self,name): return name.lower()=='endsection' def makeLine(self,comment,row): try: lname = row[0].lower() if lname in self._quoteattr: return ConfigLineQuote(comment,row) if lname in self._attr: return ConfigLine(comment,row) return None except IndexError: pass return ConfigContainer.makeLine(self,comment,row) def toString(self,depth=0): if self._comment is None: return '%sSection "%s"\n%s%sEndSection\n' % \ (' ' * depth, self._name, ConfigContainer.toString(self,depth+1), ' ' * depth) else: return '%sSection "%s" # %s\n%s%sEndSection\n' % \ (' ' * depth, self._name, self._comment, ConfigContainer.toString(self,depth+1), ' ' * depth) ############################################################################ class SubSection(Section): def isSection(self,name): return False def isEndSection(self,name): return name.lower()=='endsubsection' def toString(self,depth=0): return '%sSubSection "%s"\n%s%sEndSubSection\n' % \ ('\t' * (depth/2) + ' ' * (depth%1), self._name, ConfigContainer.toString(self,depth+1), '\t' * (depth/2) + ' ' * (depth%1)) ############################################################################ class DeviceSection(Section): _attr = ["endsection","dacspeed","clocks","videoram","biosbase","membase", \ "iobase","chipid","chiprev","textclockfreq","irq","screen"] _quoteattr = ["identifier","vendorname","boardname","chipset","ramdac", \ "clockchip","card","driver","busid"] _listattr = {"option" : OptionList} ############################################################################ class DriSection(Section): _attr = ["group","buffers","mode"] def makeLine(self,comment,row): try: lname = row[0].lower() if lname=="group" and not row[1].isdigit(): return ConfigLineQuote(comment,row) except IndexError: pass return Section.makeLine(self,comment,row) ############################################################################ class ExtensionsSection(Section): _listattr = {"option" : OptionList} ############################################################################ class FilesSection(Section): _quoteattr = ["fontpath","rgbpath","modulepath","inputdevices","logfile"] def makeLine(self,comment,row): return ConfigLineQuote(comment,row) ############################################################################ class ModuleSection(Section): _quoteattr = ["load","loaddriver","disable"] def makeSection(self,comment,row): return ModuleSubSection(comment,row) def allowModule(self,modname): killlist = [] for item in self._contents: try: if isinstance(item,ConfigLineQuote) \ and item._row[0].lower()=='disable' \ and item._row[1]==modname: killlist.append(item) except IndexError: pass for item in killlist: self._contents.remove(item) def removeModule(self,modname): killlist = [] for item in self._contents: try: if isinstance(item,ConfigLineQuote) \ and item._row[0].lower()=='load' \ and item._row[1]==modname: killlist.append(item) except IndexError: pass for item in killlist: self._contents.remove(item) def disableModule(self,modname): self.removeModule(modname) self._contents.append(ConfigLineQuote(None,['disable',modname])) def addModule(self,modname): self.removeModule(modname) self._contents.append(ConfigLineQuote(None,['load',modname])) ############################################################################ class ModuleSubSection(SubSection): _listattr = {"option" : OptionList} ############################################################################ class ModeSection(Section): _attr = ["dotclock","htimings","vtimings","hskew","bcast","vscan"] _quoteattr = ["flags"] def __init__(self,comment,row): Section.__init__(self,comment,row) self._name = row[1] def isEndSection(self,name): return name.lower()=='endmode' def toString(self,depth=0): if self._comment is None: return '%sMode "%s"\n%s%sEndMode\n' % \ (' ' * depth, self._name, ConfigContainer.toString(self,depth+1), ' ' * depth) else: return '%sMode "%s" # %s\n%s%sEndMode\n' % \ (' ' * depth, self._name, self._comment, ConfigContainer.toString(self,depth+1), ' ' * depth) ############################################################################ class ModeList(ConfigList): name = "mode" def __setitem__(self,key,value): list.__setitem__(self,key,value) def makeLine(self,comment,row): return ModeLine(comment,row) ############################################################################ class ModeLineList(ConfigList): name = "modeline" def __setitem__(self,key,value): list.__setitem__(self,key,value) def makeLine(self,comment,row): return ModeLineConfigLine(comment,row) ############################################################################ class MonitorSection(Section): _attr = ["displaysize","horizsync","vertrefresh","gamma"] _quoteattr = ["identifier","vendorname","modelname","usemodes"] _listattr = {"option" : OptionList, "mode" : ModeList, "modeline" : ModeLineList} def makeLine(self,comment,row): return Section.makeLine(self,comment,row) def isSection(self,name): lname = name.lower() return lname=='mode' def isEndSection(self,name): return name.lower()=='endsection' def makeSection(self,comment,row): if row[0].lower()=='mode': return ModeSection(comment,row) else: return Section.makeSection(self,comment,row) ############################################################################ class ModeLineConfigLine(ConfigLine): def toString(self,depth=0): string = (' ' * depth)+"modeline " if len(self._row)>0: string += ' "' + self._row[0] + '"' if len(self._row)>1: string += ' ' + ' '.join([unicode(item) for item in self._row[1:]]) if self._comment is not None: string += '#' + self._comment return string + '\n' ############################################################################ class ModesSection(MonitorSection): # Like a MonitorSection, only smaller. _attr = ["modeline"] _quoteattr = ["identifier"] ############################################################################ class PointerSection(Section): _attr = ["emulate3timeout","baudrate","samplerate","resolution",\ "devicename","buttons"] _quoteattr = ["protocol","device","port","emulate3buttons","chordmiddle",\ "cleardtr","clearrts","zaxismapping","alwayscore"] ############################################################################ class ScreenSection(Section): _attr = ["screenno","defaultcolordepth","defaultdepth","defaultbpp","defaultfbbpp"] _quoteattr = ["identifier","driver","device","monitor","videoadaptor","option"] _listattr = {"option" : OptionList} def makeSection(self,comment,row): if row[1].lower()=='display': return DisplaySubSection(comment,row) return SubSection(comment,row) ############################################################################ class DisplaySubSection(SubSection): _attr = ["viewport","virtual","black","white","depth","fbbpp","weight"] _quoteattr = ["modes","visual","option"] _listattr = {"option" : OptionList} ############################################################################ class ServerFlagsSection(Section): _quoteattr = ["notrapsignals","dontzap","dontzoom","disablevidmodeextension",\ "allownonlocalxvidtune","disablemodindev","allownonlocalmodindev","allowmouseopenfail", \ "blanktime","standbytime","suspendtime","offtime","defaultserverlayout"] _listattr = {"option" : OptionList} ############################################################################ class ServerLayoutSection(Section): _attr = [] _quoteattr = ["identifier","inactive","inputdevice","option"] _listattr = {"option" : OptionList, "screen" : ScreenConfigList} ############################################################################ class InputDeviceSection(Section): _quoteattr = ["identifier","driver"] _listattr = {"option" : OptionList} ############################################################################ class KeyboardSection(Section): _attr = ["autorepeat","xleds"] _quoteattr = ["protocol","panix106","xkbkeymap","xkbcompat","xkbtypes",\ "xkbkeycodes","xkbgeometry","xkbsymbols","xkbdisable","xkbrules",\ "xkbmodel","xkblayout","xkbvariant","xkboptions","vtinit","vtsysreq",\ "servernumlock","leftalt","rightalt","altgr","scrolllock","rightctl"] ############################################################################ class VendorSection(Section): _attr = [] _quoteattr = ["identifier"] _listattr = {"option" : OptionList} def isSection(self,name): return False ############################################################################ class VideoAdaptorSection(Section): _attr = [] _quoteattr = ["identifier","vendorname","boardname","busid","driver"] _listattr = {"option" : OptionList} def makeSection(self,comment,row): return VideoPortSection(comment,row) ############################################################################ class VideoPortSection(SubSection): _attr = [] _quoteattr = ["identifier"] _listattr = {"option" : OptionList} ############################################################################ class XorgConfig(ConfigContainer): _sectiontypes = { \ 'device': DeviceSection, 'dri': DriSection, 'extensions': ExtensionsSection, 'files': FilesSection, 'inputdevice': InputDeviceSection, 'keyboard': KeyboardSection, 'modes': ModesSection, 'monitor': MonitorSection, 'module': ModuleSection, 'pointer': PointerSection, 'serverflags': ServerFlagsSection, 'serverlayout': ServerLayoutSection, 'screen': ScreenSection, 'videoadaptor': VideoAdaptorSection} def makeSection(self,comment,row): lname = row[1].lower() try: return self._sectiontypes[lname](comment,row) except KeyError: return ConfigContainer.makeSection(self,comment,row) def toString(self,depth=-1): return ConfigContainer.toString(self,depth) def writeConfig(self,filename): try: encoding = locale.getpreferredencoding() except locale.Error: encoding = 'ANSI_X3.4-1968' fhandle = codecs.open(filename,'w',locale.getpreferredencoding()) fhandle.write(self.toString()) fhandle.close() def createUniqueIdentifier(self,stem="id"): """Create a unique identifier for a section """ # Build a list of used identifiers used_identifiers = [] for name in ['monitor','videoadaptor','inputdevice','serverlayout','device','screen']: for section in self.getSections(name): if section.identifier is not None: used_identifiers.append(section.identifier) # Generate a identifier that is not in use. i = 1 while (stem+str(i)) in used_identifiers: i += 1 return stem+str(i) ############################################################################ def addxorg(context, stack): # Add minimal xorg.conf if it's missing rows = [[None, [u'Section', u'Device']], [None, [u'Identifier', u'Configured Video Device']], \ [None, [u'EndSection']], [None, [u'Section', u'Monitor']], \ [None, [u'Identifier', u'Configured Monitor']], \ [None, [u'EndSection']], [None, [u'Section', u'Screen']], \ [None, [u'Identifier', u'Default Screen']], \ [None, [u'Monitor', u'Configured Monitor']], [None, [u'EndSection']], \ [None, [u'Section', u'ServerLayout']], \ [None, [u'Identifier', u'Default Layout']], \ [None, [u'screen', u'Default Screen']], \ [None, [u'EndSection']]] for data in rows: rowcomment = data[0] row = data[1] try: first = row[0].lower() if context.isSection(first): section = context.makeSection(rowcomment,row) context.append(section) stack.append(context) context = section context_class = context.__class__ elif context.isEndSection(first): context = stack.pop() elif context.isListAttr(first): context.makeListAttr(rowcomment,row) else: newline = context.makeLine(rowcomment,row) if newline is None: raise ParseException,"Unknown line type '%s' on line %i" % (first,line) context.append(newline) except IndexError: context.append(ConfigLine(rowcomment,row)) return context, section, stack, first ############################################################################ def addServerLayout(context, section, stack, first): # Add empty server layout section to xorg.conf if it's missing rows = [[None, [u'Section', u'ServerLayout']], \ [None, [u'Identifier', u'Default Layout']], \ [None, [u'screen', u'0', u'Default Screen', u'0', u'0']], \ [None, [u'Inputdevice', u'Generic Keyboard']], \ [None, [u'Inputdevice', u'Configured Mouse']], \ [None, []], ["Uncomment if you have a wacom tablet", []], \ ["InputDevice \"stylus\" \"SendCoreEvents\"", []], \ [" InputDevice \"cursor\" \"SendCoreEvents\"", []], \ [" InputDevice \"eraser\" \"SendCoreEvents\"", []], \ [None, [u'Inputdevice', u'Synaptics Touchpad']], [None, [u'EndSection']]] for data in rows: rowcomment = data[0] row = data[1] try: first = row[0].lower() if context.isSection(first): section = context.makeSection(rowcomment,row) context.append(section) stack.append(context) context = section context_class = context.__class__ elif context.isEndSection(first): context = stack.pop() elif context.isListAttr(first): context.makeListAttr(rowcomment,row) else: newline = context.makeLine(rowcomment,row) if newline is None: raise ParseException,"Unknown line type '%s' on line %i" % (first,line) context.append(newline) except IndexError: context.append(ConfigLine(rowcomment,row)) return context, section, stack, first ############################################################################ def readConfig(filename, check_exists=False): context = XorgConfig() stack = [] line = 1 hasserverlayout = False hasxorg = True try: import os try: if os.path.isfile('/etc/X11/xorg.conf'): if os.path.getsize(filename) == 0: raise IOError, "xorg.conf is empty - making up config" else: raise IOError, "xorg.conf is empty - making up config" except OSError, errmsg: raise IOError, errmsg for row in XorgconfCVSReader(filename=filename).readlines(): try: first = row[0].lower() if context.isSection(first): section = context.makeSection(row.comment,row) if section._name == 'ServerLayout': hasserverlayout = True context.append(section) stack.append(context) context = section context_class = context.__class__ elif context.isEndSection(first): context = stack.pop() elif context.isListAttr(first): context.makeListAttr(row.comment,row) else: newline = context.makeLine(row.comment,row) if newline is None: raise ParseException,"Unknown line type '%s' on line %i" % (first,line) context.append(newline) except IndexError: context.append(ConfigLine(row.comment,row)) line += 1 except IOError, errmsg: ermsg = str(errmsg) print "IOError", ermsg, " - will create xorg.conf if possible." if ermsg[:9] == "[Errno 2]": # No such file or directory: hasxorg = False addxorg(context, stack) try: xorgfile = open(filename, 'a') xorgfile.close() except IOError, errmsg: ermsg = str(errmsg) if ermsg[:9] == "[Errno 13]": #Permission denied: pass # Since we aren't root, changes can't be made anyway. elif ermsg[:9] == "xorg.conf": # xorg.conf exists, but is empty hasxorg = False addxorg(context, stack) if len(stack)!=0: raise ParseException,"Unexpected end of file on line %i" % line if not hasserverlayout and hasxorg: addServerLayout(context, section, stack, first) if check_exists: return context, hasxorg else: return context ############################################################################ class ParseException(Exception): def __init__(self,*args): Exception.__init__(self,*args) ############################################################################ def toBoolean(value): return unicode(value).lower() in ['on','true','1','yes'] ############################################################################ # Our own class for reading CSV file. This version supports unicode while # standard Python (2.4) version doesn't. Hence the need for this class. # class XorgconfCVSReader(object): def __init__(self,filename=None, text=None): assert filename is not None or text is not None STATE_DELIMITER = 0 STATE_ITEM = 1 STATE_QUOTE = 2 QUOTE = '"' LINE_COMMENT = '#' class CommentList(list): def __init__(self): list.__init__(self) self.comment = None if filename is not None: try: loc = locale.getpreferredencoding() except locale.Error: loc = 'ANSI_X3.4-1968' fhandle = codecs.open(filename,'r',loc,'replace') source_lines = fhandle.readlines() fhandle.close() else: source_lines = text.split('\n') self.lines = [] for line in source_lines: if len(line)!=0 and line[-1]=='\n': line = line[:-1] state = STATE_DELIMITER row = CommentList() item = None for i in range(len(line)): c = line[i] if state==STATE_DELIMITER: if not c.isspace(): if c==QUOTE: item = [] state = STATE_QUOTE elif c==LINE_COMMENT: row.comment = line[i+1:] break else: item = [] item.append(c) state = STATE_ITEM elif state==STATE_ITEM: if c.isspace(): row.append(u''.join(item)) state = STATE_DELIMITER item = None else: item.append(c) elif state==STATE_QUOTE: if c==QUOTE: row.append(u''.join(item)) state = STATE_DELIMITER item = None else: item.append(c) if item is not None: row.append(u''.join(item)) self.lines.append(row) def readlines(self): return self.lines ############################################################################ if __name__=='__main__': import sys if len(sys.argv)==2: filename = sys.argv[1] else: filename = "/etc/X11/xorg.conf" print "Reading",filename c = readConfig(filename) print c.toString()