diff options
Diffstat (limited to 'displayconfig/displayconfigabstraction.py')
-rw-r--r-- | displayconfig/displayconfigabstraction.py | 3230 |
1 files changed, 3230 insertions, 0 deletions
diff --git a/displayconfig/displayconfigabstraction.py b/displayconfig/displayconfigabstraction.py new file mode 100644 index 0000000..f59b2ff --- /dev/null +++ b/displayconfig/displayconfigabstraction.py @@ -0,0 +1,3230 @@ +#!/usr/bin/env python + +import os +import sys +import string +import math +import subprocess +import xf86misc +import xorgconfig +import ScanPCI +import csv +import re +from execwithcapture import * + +"""Classes for dealing with X.org configuration in a sane way. + +The object model used here is fairly simple. An XSetup object represents +the complete configuration of the server. The XSetup object contains one or +more GfxCard objects. One for each graphics card present in the machine. +Each GfxCard has one or more Screen objects with each Screen representing +one 'output' on the graphics card. + +Each GfxCard object is also associated with a GfxCardModel object which +describes the model of graphics card. + +Each Screen object is associated with a MonitorModel object which +describes the model of monitor attached. + +""" + +FALLBACK_RESOLUTION = (800,600) + +# FIXME updating /etc/modules for fglrx. +data_file_dir = "." +def SetDataFileDir(dir_name): + global data_file_dir + data_file_dir = dir_name + +var_data_dir = "/var/lib/guidance-backends" +def SetVarDataDir(dir_name): + global var_data_dir + var_data_dir = dir_name + +############################################################################ +class XSetup(object): + """Represents the current configuration of the X.org X11 server. + + + """ + # Map positions + ABOVE = 0 + UNDER = 1 + LEFTOF = 2 + RIGHTOF = 3 + + RESTART_NONE = 0 + RESTART_X = 1 + RESTART_SYSTEM = 2 + + LAYOUT_SINGLE = 1 # These are bit flags. + LAYOUT_CLONE = 2 + LAYOUT_DUAL = 4 + LAYOUT_SINGLE_XINERAMA = 256 # For internal use. + LAYOUT_CLONE_XINERAMA = 512 # For internal use. + + POSITION_LEFTOF = 0 + POSITION_RIGHTOF = 1 + POSITION_ABOVE = 2 + POSITION_BELOW = 3 + + ROLE_UNUSED = 0 + ROLE_PRIMARY = 1 + ROLE_SECONDARY = 2 + + def __init__(self,xorg_config_filename='/etc/X11/xorg.conf',debug_scan_pci_filename=None,secondtry=False): + self.screens = [] + self.gfxcards = [] + self.xorg_config, self.hasxorg = xorgconfig.readConfig(xorg_config_filename, check_exists=True) + if not secondtry: + self.xorg_config_filename = xorg_config_filename + if self.xorg_config_filename == None: + self.xorg_config_filename = '/etc/X11/xorg.conf'; + self.x_live_info = xf86misc.XF86Server() + + self.primary_screen = None + self.secondary_screen = None + + pci_bus = ScanPCI.PCIBus(data_file_dir) + if debug_scan_pci_filename is None: + pci_bus.detect() + else: + pci_bus.loadFromFile(debug_scan_pci_filename) + + # First thing. Scan the PCI bus and find out how many Gfx cards we have. + found_list = self._detectGfxCards(pci_bus) + # list of (PCI_ID, PCIDevice, GfxCard) tuples + + found_list.sort() + + # Prepare some useful data structures. + self.layout = self.LAYOUT_SINGLE + self.xinerama = False + self.orientation = self.POSITION_LEFTOF + + # Maps screen section names to xorg screens section objects. + xorg_screen_name_dict = {} + xorg_unused_screen_sections = self.xorg_config.getSections("Screen") + for screen in xorg_unused_screen_sections: + xorg_screen_name_dict[screen.identifier] = screen + + # Maps device sections names to xorg device sections + xorg_device_name_dict = {} + xorg_unused_device_sections = self.xorg_config.getSections("Device") + for device in xorg_unused_device_sections: + xorg_device_name_dict[device.identifier] = device + + # Maps device sections names to xorg device sections + xorg_monitor_name_dict = {} + xorg_monitor_sections = self.xorg_config.getSections("Monitor") + for monitor in xorg_monitor_sections: + xorg_monitor_name_dict[monitor.identifier] = monitor + + # Maps GfxCard objects to ScanPCI.PCIDevice objects. + gfx_card_pcidevice_dict = {} + + #------------------------------------------------------------------- + # Decode the server layout. + server_layouts = self.xorg_config.getSections("ServerLayout") + if len(server_layouts)==0: + print "*** Error: couldn't find any ServerLayout sections" + return + layout = server_layouts[0] # Grab the first ServerLayout + + if len(layout.screen)==0: + print "*** Error: no screens were specified in the ServerLayout section" + + # Handle leftof rightof below and above. + (primary_name, secondary_name, layout, self.orientation) = self._decodeServerLayoutScreens(layout.screen) + + screen_list = [primary_name] + if secondary_name is not None: + screen_list.append(secondary_name) + + for screen_name in screen_list: + if screen_name in xorg_screen_name_dict: + screen_section = xorg_screen_name_dict[screen_name] + if screen_section.device in xorg_device_name_dict: + + device_section = xorg_device_name_dict[screen_section.device] + + # Ok, we've now got a screen section and its device. + gfx_card = None + + if device_section.busid is not None: + # Try to match this device to a gfxcard by using the PCI bus ID. + bus_id = self._canonicalPCIBusID(device_section.busid) + + # See if there is already a known gfxcard at this PCI ID. + gfx_card = self.getGfxCardByPCIBusID(bus_id) + if gfx_card is not None: + # Let the gfxcard know that we have another device section for it to manage. + gfx_card._addXDevice(device_section) + try: + xorg_unused_device_sections.remove(device_section) + except ValueError: + pass + else: + # Not known, look for a matching pci device instead. + for pci_device_tuple in found_list: + if pci_device_tuple[0]==bus_id: + # Got a hit, create a gfxcard object. + gfx_card = GfxCard( self, pci_id=bus_id, \ + x_device=device_section, \ + detected_model=pci_device_tuple[2]) + + self.gfxcards.append(gfx_card) + gfx_card_pcidevice_dict[gfx_card] = pci_device_tuple[1] + xorg_unused_device_sections.remove(device_section) + found_list.remove(pci_device_tuple) + break + + else: + + # OK, no PCI ID, try matching to a PCI device by X driver name, + # or if there is one only gfx card then just grab it. + driver_name = device_section.driver + for pci_device_tuple in found_list: + if pci_device_tuple[2].getDriver()==driver_name \ + or pci_device_tuple[2].getProprietaryDriver()==driver_name \ + or len(found_list)==1: + # Got a hit, create a gfxcard object. + gfx_card = GfxCard( self, pci_id=pci_device_tuple[0], \ + x_device=device_section, \ + detected_model=pci_device_tuple[2]) + + self.gfxcards.append(gfx_card) + gfx_card_pcidevice_dict[gfx_card] = pci_device_tuple[1] + xorg_unused_device_sections.remove(device_section) + found_list.remove(pci_device_tuple) + break + + if gfx_card is not None: + # Look up the monitor section from the monitor name. + monitor_section = None + monitor_model = None + if screen_section.monitor in xorg_monitor_name_dict: + monitor_section = xorg_monitor_name_dict[screen_section.monitor] + monitor_model = self._matchMonitor(monitor_section) + + screen = Screen(x_config_screen=screen_section, gfx_card=gfx_card, \ + x_config_monitor=monitor_section, monitor_model=monitor_model, \ + x_config=self.xorg_config) + gfx_card._addScreen(screen) + + if self.primary_screen is None: + self.primary_screen = screen + elif self.secondary_screen is None: + self.secondary_screen = screen + + xorg_unused_screen_sections.remove(screen_section) + + if self.primary_screen is not None and self.secondary_screen is not None: + self.layout = layout + + #------------------------------------------------------------------- + # Dualhead hardware detection. + gfx_cards_needing_second_heads = [] + + # Detect dualhead ATI cards + for pci_device in pci_bus.devices: + if pci_device.text is not None and pci_device.text.find("Secondary")!=-1: + + pci_device_id = "PCI:%i:%i:%i" % (pci_device.pci_bus, pci_device.pci_device, pci_device.pci_function) + + for gfx_card in self.gfxcards: + if gfx_card.getPCIBusID() != pci_device_id: + # Compare the first two numbers that make up a PCI bus id (e.g. middle part of PCI:1:0:0) + if gfx_card.getPCIBusID().split(":")[1:-1] == [str(pci_device.pci_bus),str(pci_device.pci_device)]: + if len(gfx_card.getScreens())<2: + gfx_cards_needing_second_heads.append(gfx_card) + found_list = [x for x in found_list if x[0]!=pci_device_id] + break + + # Detect dualhead Intel cards + for gfx_card in self.gfxcards: + if gfx_card._getDetectedGfxCardModel().getDriver() in ['i740','i810', 'intel']: + gfx_card_pci_id = gfx_card.getPCIBusID().split(":")[1:] + base_pci_id = gfx_card_pci_id[:-1] + + for pci_device in pci_bus.devices: + if gfx_card_pci_id != [str(pci_device.pci_bus),str(pci_device.pci_device),str(pci_device.pci_function)]: + if base_pci_id == [str(pci_device.pci_bus),str(pci_device.pci_device)]: + pci_device_id = "PCI:%i:%i:%i" % (pci_device.pci_bus, pci_device.pci_device, pci_device.pci_function) + found_list = [x for x in found_list if x[0]!=pci_device_id] + # Try to configure a second head later if not yet available + if len(gfx_card.getScreens()) < 2: + gfx_cards_needing_second_heads.append(gfx_card) + break + + # Detect dualhead nVidia cards + for gfx_card in self.gfxcards: + if gfx_card._getDetectedGfxCardModel().getDriver() in ['nv','nvidia']: + if self._isNVidiaCardDualhead(gfx_card_pcidevice_dict[gfx_card]): + if len(gfx_card.getScreens())<2: + if gfx_card not in gfx_cards_needing_second_heads: + gfx_cards_needing_second_heads.append(gfx_card) + continue + + # Detect dualhead Matrox cards. This info is from the Cards+ db. + for gfx_card in self.gfxcards: + if (gfx_card._getDetectedGfxCardModel().getMultiHead()>1) and (len(gfx_card.getScreens())<2): + if gfx_card not in gfx_cards_needing_second_heads: + gfx_cards_needing_second_heads.append(gfx_card) + + # Detect laptops. Dualhead/clone mode is standard functionality on laptops. + # (but can be hard to detect). + if os.path.isfile('/usr/sbin/laptop-detect'): + if subprocess.call(['/usr/sbin/laptop-detect'])==0: + if len(self.gfxcards)!=0: + gfx_card = self.gfxcards[0] + if gfx_card not in gfx_cards_needing_second_heads and \ + len(gfx_card.getScreens())<2: + gfx_cards_needing_second_heads.append(gfx_card) + + # Match up the second heads with any loose sections in xorg.conf. + for gfx_card in gfx_cards_needing_second_heads: + screens = gfx_card.getScreens() + # Try to find a loose xorg.conf Screen section that also + # references this gfx card. That is probably the config + # for the second screen. + for screen_section in xorg_unused_screen_sections: + if screen_section.device in xorg_device_name_dict: + device_section = xorg_device_name_dict[screen_section.device] + + # Is this the second screen for the same PCI device aka gfxcard? + + # Note: even though the second head shows up as a separate PCI ID, the screen + # section in xorg.conf still uses the primary PCI ID. + if str(device_section.screen)=="1" and \ + self._canonicalPCIBusID(device_section.busid)==gfx_card.getPCIBusID(): + + # Look up the monitor section from the monitor name. + monitor_section = None + monitor_model = None + if screen_section.monitor in xorg_monitor_name_dict: + monitor_section = xorg_monitor_name_dict[screen_section.monitor] + monitor_model = self._matchMonitor(monitor_section) + + gfx_card._addXDevice(device_section) + xorg_unused_device_sections.remove(device_section) + + screen = Screen(x_config_screen=screen_section, gfx_card=gfx_card, \ + x_config_monitor=monitor_section, monitor_model=monitor_model, \ + x_config=self.xorg_config) + gfx_card._addScreen(screen) + self.secondary_screen = screen + xorg_unused_screen_sections.remove(screen_section) + break + else: + # Couldn't anything in xorg.conf, just make an empty screen + screen = Screen(gfx_card=gfx_card, x_config=self.xorg_config) + gfx_card._addScreen(screen) + + #------------------------------------------------------------------- + # Handle loose gfx card devices. Check that all PCI gfxcards are accounted for. + for pci_device_tuple in found_list: + + bus_id = pci_device_tuple[0] + for device_section in xorg_unused_device_sections: + if bus_id == self._canonicalPCIBusID(device_section.busid): + xorg_unused_device_sections.remove(device_section) + break + else: + device_section = None + + # Got a hit, create a gfxcard object. + gfx_card = GfxCard( self, pci_id=pci_device_tuple[0], \ + x_device=device_section, \ + detected_model=pci_device_tuple[2]) + + gfx_card_pcidevice_dict[gfx_card] = pci_device_tuple[1] + self.gfxcards.append(gfx_card) + + screen = None + # See if this device section is referenced by a screen section. + # if so, then we grab the screen section. + if device_section is not None: + for screen_section in xorg_unused_screen_sections: + if screen_section.device==device_section.identifier: + + # Ok, we have found the screen section, monitor? + monitor_section = None + monitor_model = None + if screen_section.monitor in xorg_monitor_name_dict: + monitor_section = xorg_monitor_name_dict[screen_section.monitor] + monitor_model = self._matchMonitor(monitor_section) + + screen = Screen(x_config_screen=screen_section, gfx_card=gfx_card, \ + x_config_monitor=monitor_section, monitor_model=monitor_model, \ + x_config=self.xorg_config) + gfx_card._addScreen(screen) + xorg_unused_screen_sections.remove(screen_section) + break + + if screen is None: + # Manually add a screen. + screen = Screen(gfx_card=gfx_card, x_config=self.xorg_config) + gfx_card._addScreen(screen) + + #------------------------------------------------------------------- + # Sort the gfx cards by PCI id. + def gfxcard_pci_sort(a,b): return cmp(a.getPCIBusID(),b.getPCIBusID()) + self.gfxcards.sort(gfxcard_pci_sort) + + # Hand out some randr live screens + x_live_screens = self.x_live_info.getScreens() + i = 0 + for gfx_card in self.gfxcards: + for screen in gfx_card.getScreens(): + if i<len(x_live_screens) and self.getScreenRole(screen)!=XSetup.ROLE_UNUSED: + screen._setXLiveScreen(x_live_screens[i]) + i += 1 + + # Ensure that all of the screen roles have been handed out. + if self.primary_screen is None: + for screen in self.getAllScreens(): + if screen is not self.secondary_screen: + self.primary_screen = screen + break + if self.secondary_screen is None: + for screen in self.getAllScreens(): + if screen is not self.primary_screen: + self.secondary_screen = screen + break + + self._finalizeInit() + + if not self.hasxorg and not secondtry: + """No xorg.conf, so we need to write a temporary one and reload from that one""" + self.writeXorgConfig('/tmp/xorg.conf.displayconfig') + self.__init__(xorg_config_filename='/tmp/xorg.conf.displayconfig',secondtry=True) + return + + def _finalizeInit(self): + for gfxcard in self.gfxcards: + gfxcard._finalizeInit() + + # Check the 'layout' on the gfx cards to detect optimised configs. + gfxcard = self.primary_screen._getGfxCard() + if gfxcard._getDetectedLayout()!=XSetup.LAYOUT_SINGLE: + # Something interesting is going on on this gfx card. The + # secondary screen is in this case going to actually be the + # other 'head' on this gfx card. + if gfxcard.getScreens()[0] is not self.primary_screen: + self.secondary_screen = gfxcard.getScreens()[0] + else: + self.secondary_screen = gfxcard.getScreens()[1] + + self.orientation = gfxcard._getDetectedDualheadOrientation() + self.setLayout(gfxcard._getDetectedLayout()) + + self.setLayout(self.layout) # Propogate any XINERAMA_SINGLE stuff out to the gfxcards. + self.original_layout = self.layout + self.original_orientation = self.orientation + self.original_primary_screen = self.primary_screen + self.original_secondary_screen = self.secondary_screen + + def _matchMonitor(self,monitor_section): + monitor_model_db = GetMonitorModelDB() + + model_name = monitor_section.modelname + if monitor_model_db.getMonitorByName(model_name) is not None: + monitor_model = monitor_model_db.getMonitorByName(model_name) + else: + + if monitor_section.getRow('horizsync') is not None and monitor_section.getRow('vertrefresh') is not None: + # Create a monitor object for the monitor in the xconfig. + # It is probably a Plug N Play monitor and as such doesn't + # appear in our monitor DB. + monitor_model = monitor_model_db.newCustomMonitor(name=model_name) + monitor_model.setType(MonitorModel.TYPE_PLUGNPLAY) + + if monitor_section.vendorname is not None: + monitor_model.setManufacturer(monitor_section.vendorname) + + monitor_model.setHorizontalSync(' '.join(monitor_section.getRow('horizsync'))) + monitor_model.setVerticalSync(' '.join(monitor_section.getRow('vertrefresh'))) + + else: + # Try detecting the monitor. + monitor_model = monitor_model_db.detect() + + monitor_model.setDpms("dpms" in monitor_section.option) + + return monitor_model + + def _decodeServerLayoutScreens(self,screens_lines): + primary_name = None + secondary_name = None + layout = XSetup.LAYOUT_SINGLE + position = XSetup.POSITION_LEFTOF + + for line in screens_lines: + try: + i = 1 + if line._row[i].isdigit(): # Skip any screen ID number. + i += 1 + + screen_name1 = line._row[i] + if primary_name is None: + primary_name = screen_name1 + elif secondary_name is None: + secondary_name = screen_name1 + layout = XSetup.LAYOUT_CLONE + i += 1 + + if line._row[i].isdigit(): + # Skip absolute coords. + i += 2 + else: + if line._row[i].lower()=='absolute': + # Skip the absolute keyword and coords + i += 3 + + screen_name2 = line._row[i+1] + secondary_name = screen_name2 + + position = { + 'leftof': XSetup.POSITION_LEFTOF, + 'rightof': XSetup.POSITION_RIGHTOF, + 'above': XSetup.POSITION_ABOVE, + 'below': XSetup.POSITION_BELOW + }[line._row[i].lower()] + + layout = XSetup.LAYOUT_DUAL + + if screen_name1!=primary_name: + # Swap the screens around. The primary wasn't given first on this + # dualhead screen line. + secondary_name = screen_name1 + position = { + XSetup.POSITION_LEFTOF: XSetup.POSITION_RIGHTOF, + XSetup.POSITION_RIGHTOF: XSetup.POSITION_LEFTOF, + XSetup.POSITION_ABOVE: XSetup.POSITION_BELOW, + XSetup.POSITION_BELOW: XSetup.POSITION_ABOVE + }[position] + + except IndexError: + pass + except KeyError: + pass + + return (primary_name, secondary_name, layout, position) + + def _detectGfxCards(self,pci_bus): + """Scans the PCI bus for graphics cards. + + Returns a list of (PCI_ID, PCIDevice, GfxCard) tuples.""" + self.gfx_card_db = GetGfxCardModelDB() + vesa_model = "VESA driver (generic)" + + # Look for a gfxcard. + found_list = [] + for pci_device in pci_bus.devices: + if pci_device.isGfxCard(): + pci_id = "PCI:%i:%i:%i" % (pci_device.pci_bus, pci_device.pci_device, pci_device.pci_function) + model = None + try: + cardname = pci_device.getModule() + if not pci_device.isModuleXorgDriver(): + cardname = vesa_model + model = self.gfx_card_db.getGfxCardModelByName(cardname) + except KeyError: + model = self.gfx_card_db.getGfxCardModelByName(vesa_model) + found_list.append( (pci_id,pci_device,model) ) + + return found_list + + def _canonicalPCIBusID(self,bus_id): + try: + parts = bus_id.split(":") + if parts[0].lower()!="pci": + return None + bus = int(parts[1]) + device = int(parts[2]) + function = int(parts[3]) + return "PCI:%i:%i:%i" % (bus,device,function) + except IndexError: + return None + except ValueError: + return None + except AttributeError: + return None + + def _isNVidiaCardDualhead(self,PCIDeviceObject): + """ + PCIDevice - ScanPCI.PCIDevice + + Returns true if the given nVidia PCI device ID supports dualhead. + """ + # From Xorg source xc/programs/Xserver/hw/xfree86/drivers/nv/nv_setup.c + # See line "pNv->twoHeads = " + # + NV_ARCH_04 = 0x4 + NV_ARCH_10 = 0x10 + NV_ARCH_20 = 0x20 + NV_ARCH_30 = 0x30 + NV_ARCH_40 = 0x40 + + pci_device = PCIDeviceObject.device + + if pci_device & 0xfff0 == 0x00f0: + return True # FIXME PCIXpress chipsets + + # These IDs come from the Xorg source. + # xc/programs/Xserver/hw/xfree86/drivers/nv/nv_driver.c + # And should be periodically updated. + chipset = pci_device & 0x0ff0 + if chipset in [ + 0x0100, # GeForce 256 + 0x0110, # GeForce2 MX + 0x0150, # GeForce2 + 0x0170, # GeForce4 MX + 0x0180, # GeForce4 MX (8x AGP) + 0x01A0, # nForce + 0x01F0]:# nForce2 + architecture = NV_ARCH_10 + elif chipset in [ + 0x0200, # GeForce3 + 0x0250, # GeForce4 Ti + 0x0280]:# GeForce4 Ti (8x AGP) + architecture = NV_ARCH_20 + elif chipset in [ + 0x0300, # GeForceFX 5800 + 0x0310, # GeForceFX 5600 + 0x0320, # GeForceFX 5200 + 0x0330, # GeForceFX 5900 + 0x0340]:# GeForceFX 5700 + architecture = NV_ARCH_30 + elif chipset in [ + 0x0040, + 0x00C0, + 0x0120, + 0x0130, + 0x0140, + 0x0160, + 0x01D0, + 0x0090, + 0x0210, + 0x0220, + 0x0230, + 0x0290, + 0x0390]: + architecture = NV_ARCH_40 + else: + architecture = NV_ARCH_04 + + return (architecture >= NV_ARCH_10) and \ + (chipset != 0x0100) and \ + (chipset != 0x0150) and \ + (chipset != 0x01A0) and \ + (chipset != 0x0200) + + def _syncXorgConfig(self): + + xinerama_clone = (self.layout==XSetup.LAYOUT_CLONE) and \ + not ((self.secondary_screen._getGfxCard() is self.primary_screen._getGfxCard()) \ + and (self.primary_screen._getGfxCard()._getAvailableLayouts() & XSetup.LAYOUT_CLONE)) + + if xinerama_clone: + # For clone mode with xinerama we copy the screen settings from the primary screen + # to the secondary screen. + primary_screen = self.getPrimaryScreen() + secondary_screen = self.getSecondaryScreen() + + resolution = primary_screen.getAvailableResolutions()[primary_screen.getResolutionIndex()] + secondary_resolution_index = secondary_screen.getAvailableResolutions().index(resolution) + secondary_screen.setResolutionIndex(secondary_resolution_index) + secondary_rates = secondary_screen.getAvailableRefreshRatesForResolution(secondary_resolution_index) + primary_rate = primary_screen.getAvailableRefreshRates()[primary_screen.getRefreshRateIndex()] + + best_rate_index = 0 + best_score = 1000000 + for i in range(len(secondary_rates)): + rate = secondary_rates[i] + score = abs(rate-primary_rate) + if score < best_score: + best_score = score + best_rate_index = i + + secondary_screen.setRefreshRateIndex(best_rate_index) + + # Sync up the graphics cards. + for gfxcard in self.gfxcards: + gfxcard._syncXorgConfig() + + server_flags_sections = self.xorg_config.getSections("ServerFlags") + if len(server_flags_sections)!=0: + server_flags = server_flags_sections[0] + server_flags.option.removeOptionByName("Xinerama") + else: + server_flags = self.xorg_config.makeSection(None,["Section","ServerFlags"]) + self.xorg_config.append(server_flags) + + # Delete any old screen entries in the serverlayout section. + server_layout = self.xorg_config.getSections("ServerLayout")[0] + for screen in server_layout.screen[:]: + server_layout.screen.remove(screen) + + # Add the first Screen row + screen_id_1 = self.primary_screen._getXorgScreenSection().identifier + server_layout.screen.append(server_layout.screen.makeLine(None, + ["0",screen_id_1,"0","0"])) + + # FIXME server_flags -> Option "DefaultServerLayout" "???" + if self.layout==XSetup.LAYOUT_DUAL or xinerama_clone: + server_flags.option.append( server_flags.option.makeLine(None,["Xinerama","true"]) ) + + # Add the second screen row. This one also has the dual screen + # orientation info. + screen_id_2 = self.secondary_screen._getXorgScreenSection().identifier + + if not xinerama_clone: + + position = {XSetup.POSITION_LEFTOF:"RightOf", + XSetup.POSITION_RIGHTOF:"LeftOf", + XSetup.POSITION_ABOVE:"Below", + XSetup.POSITION_BELOW:"Above"}[self.orientation] + + server_layout.screen.append(server_layout.screen.makeLine(None, + ["1",screen_id_2,position,screen_id_1])) + else: + # Xinerama clone mode. Place the second screen directly on top of the + # primary screen. + server_layout.screen.append(server_layout.screen.makeLine(None, + ["1",screen_id_2,"0","0"])) + + self.original_layout = self.layout + self.original_orientation = self.orientation + self.original_primary_screen = self.primary_screen + self.original_secondary_screen = self.secondary_screen + + def writeXorgConfig(self,filename): + self._syncXorgConfig() + self.xorg_config.writeConfig(filename) + + def xorgConfigToString(self): + return self.xorg_config.toString() + + def getUsedScreens(self): + """Returns the list of Screen objects that the current setup is using.""" + if self.layout==XSetup.LAYOUT_SINGLE: + return [self.primary_screen] + else: + return [self.primary_screen, self.secondary_screen] + + def getAllScreens(self): + """Returns a list of all Screen object.""" + screens = [] + for card in self.gfxcards: + for screen in card.getScreens(): + screens.append(screen) + return screens + + def getScreen(self,screenindex): + return self.getUsedScreens()[screenindex] + + def getGfxCards(self): + return self.gfxcards[:] # No messin' with the gfx card list. + + def getGfxCardByPCIBusID(self,bus_id): + for gfxcard in self.gfxcards: + if gfxcard.getPCIBusID()==bus_id: + return gfxcard + return None + + def getPrimaryScreen(self): + return self.primary_screen + + def getSecondaryScreen(self): + return self.secondary_screen + + def getScreenRole(self,screen): + if screen is self.primary_screen: + return XSetup.ROLE_PRIMARY + if screen is self.secondary_screen: + return XSetup.ROLE_SECONDARY + return XSetup.ROLE_UNUSED + + def setScreenRole(self,screen,role): + if role==XSetup.ROLE_PRIMARY: + if screen is self.secondary_screen: + # Swap the roles around. + self.secondary_screen = self.primary_screen + self.primary_screen = screen + else: + self.primary_screen = screen + + elif role==XSetup.ROLE_SECONDARY: + if screen is self.primary_screen: + # Swap the roles around. + self.primary_screen = self.secondary_screen + self.secondary_screen = screen + else: + self.secondary_screen = screen + else: + # ROLE_UNUSED + if screen is not self.primary_screen and screen is not self.secondary_screen: + return + + # Find the first screen unused. + for unused_screen in self.getAllScreens(): + if screen is not self.primary_screen and screen is not self.secondary_screen: + if screen is self.primary_screen: + self.primary_screen = unused_screen + else: + self.secondary_screen = unused_screen + return + + def mayModifyXorgConfig(self): + """Check if the current user may modify the xorg.conf file + + Returns True or False + """ + return os.access(self.xorg_config_filename, os.W_OK|os.R_OK) + + def mayModifyGamma(self): + """Check if the current user may modify the gamma settings. + + Returns True or False. + """ + return self.isGammaLive() or self.mayModifyXorgConfig() + + def mayModifyResolution(self): + """Check if the current user may modify the screen resolution. + + Returns True or False. + """ + for screen in self.x_live_info.getScreens(): + if screen.resolutionSupportAvailable(): + return True + + return self.mayModifyXorgConfig() + + def isGammaLive(self): + """Check if gamma changes are done immediately. + + Returns True or False. + """ + return True # FIXME depends on the xvid extension and if86misc. + + def isLiveResolutionConfigChanged(self): + """Check if the live server configuration is changed + + Checks if the configuration has been modified with changes that can be + pushed to the running X server. + + Returns True or False. + """ + # XRandR tends to break Xinerama + if self.primary_screen._getGfxCard().getLayout() in \ + (XSetup.LAYOUT_SINGLE_XINERAMA, XSetup.LAYOUT_DUAL): + return False + for screen in self.getAllScreens(): + if screen.isLive() and screen.isResolutionSettingsChanged(): + return True + + return False + + def applyLiveResolutionChanges(self): + """Apply any changes that can be done live + + Returns True if running server resolution has been changed. + """ + rc = False + for s in self.getUsedScreens(): + if s.isResolutionSettingsChanged(): + s.applyResolutionSettings() + rc = rc or s.isResolutionLive() + return rc + + def acceptLiveResolutionChanges(self): + """ + + + """ + for s in self.getUsedScreens(): + s.acceptResolutionSettings() + + def rejectLiveResolutionChanges(self): + """Rejects and reverts the last live server resolution changes + + Rejects the last resolution changes that were made to the live server + and reverts it back to the previous configuration. + """ + for s in self.getUsedScreens(): + s.revertResolutionSettings() + + def isLiveGammaConfigChanged(self): + """Check if the live server gamma configuration is changed + + Checks if the configuration has been modified with changes that can be + pushed to the running X server. + + Returns True or False. + """ + for screen in self.getAllScreens(): + if screen.isLive() and screen.isGammaSettingsChanged(): + return True + + return False + + def applyLiveGammaChanges(self): + """Apply any changes that can be done live + + Returns True if running server gamma has been changed. + """ + rc = False + for s in self.getUsedScreens(): + if s.isGammaSettingsChanged(): + s.applyGammaSettings() + rc = rc or s.isGammaLive() + return rc + + def acceptLiveGammaChanges(self): + """ + + + """ + for s in self.getUsedScreens(): + s.acceptGammaSettings() + + def rejectLiveGammaChanges(self): + """Rejects and reverts the last live server gamma changes + + Rejects the last gamma changes that were made to the live server + and reverts it back to the previous configuration. + """ + for s in self.getUsedScreens(): + s.revertGammaSettings() + + def isXorgConfigChanged(self): + """Check if the xorg.config needs to updated + + Returns True if the xorg.config file needs to updated to reflect new changes. + """ + changed = self.original_layout!=self.layout or \ + self.original_orientation!=self.orientation or \ + self.original_primary_screen!=self.primary_screen or \ + self.original_secondary_screen!=self.secondary_screen + + for gfxcard in self.gfxcards: + changed = changed or gfxcard.isXorgConfigChanged() + for screen in self.getAllScreens(): + changed = changed or screen.isXorgConfigChanged() + return changed + + def getRestartHint(self): + hint = XSetup.RESTART_NONE + if self.original_layout!= self.layout or self.original_orientation != self.orientation: + hint = XSetup.RESTART_X + return max(hint,max( [gfxcard.getRestartHint() for gfxcard in self.gfxcards] )) + + def reset(self): + for card in self.gfxcards: + card.reset() + + self.layout = self.original_layout + self.orientation = self.original_orientation + self.primary_screen = self.original_primary_screen + self.secondary_screen = self.original_secondary_screen + + # Dualhead and secondary monitor support ---------- + def getLayout(self): + return self.layout + + def setLayout(self,layout): + """ + + Keyword arguments: + layout - XSetup.LAYOUT_SINGLE, XSetup.LAYOUT_CLONE or XSetup.LAYOUT_DUAL. + """ + self.layout = layout + + if self.layout==XSetup.LAYOUT_SINGLE: + for gfxcard in self.gfxcards: + gfxcard.setLayout(XSetup.LAYOUT_SINGLE) + self.xinerama = False + elif self.layout==XSetup.LAYOUT_DUAL: + # 'xinerama' screens can be combined by the ServerLayout xorg.conf + # sections into a multihead configurations. Gfxcard objects just + # have to output xinerama friendly xorg.conf device and screen + # sections. + self.xinerama = True + for gfxcard in self.gfxcards: + gfxcard.setLayout(XSetup.LAYOUT_SINGLE_XINERAMA) + + # Check if the primary and secondary screen are on the same gfx card. + # If so then see if the gfxcard can directly (read: accelarated) support + # the layout we want. + if self.primary_screen._getGfxCard() is self.secondary_screen._getGfxCard(): + if self.primary_screen._getGfxCard().getAvailableLayouts() & self.layout: + self.primary_screen._getGfxCard().setLayout(self.layout) + self.xinerama = False + + elif self.layout==XSetup.LAYOUT_CLONE: + + # If the graphics card itself has both heads and it can offer a better clone + # mode, then we use that instead of faking it with xinerama. + if (self.secondary_screen._getGfxCard() is self.primary_screen._getGfxCard()) \ + and (self.primary_screen._getGfxCard()._getAvailableLayouts() & XSetup.LAYOUT_CLONE): + self.xinerama = False + for gfxcard in self.gfxcards: + gfxcard.setLayout(XSetup.LAYOUT_CLONE) + else: + self.xinerama = True + for gfxcard in self.gfxcards: + gfxcard.setLayout(XSetup.LAYOUT_SINGLE_XINERAMA) + + def mayModifyLayout(self): + return self.mayModifyXorgConfig() + + def getAvailableLayouts(self): + if self.secondary_screen is not None: + return XSetup.LAYOUT_SINGLE | XSetup.LAYOUT_DUAL | XSetup.LAYOUT_CLONE + else: + return XSetup.LAYOUT_SINGLE + + def setDualheadOrientation(self,orientation): + """ Sets orientation of monitor to one of + XSetup.ABOVE, XSetup.UNDER, XSetup.LEFTOF, XSetup.RIGHTOF + """ + self.orientation = orientation + + def getDualheadOrientation(self): + """ Returns the current orientation, one of + XSetup.ABOVE, XSetup.UNDER, XSetup.LEFTOF, XSetup.RIGHTOF + """ + return self.orientation + + def isHWAccelerated(self): + # FIXME: + # if twinview-alike and screen[0].res = screen[1].res + # else: if primary screen + return True + + # Internal ---------- + def _addScreen(self,screen): + self.screens.append(screen) + + def _addGfxCard(self,gfxcard): + self.gfxcards.append(gfxcard) + + def _getColorDepth(self): + return min([s._getColorDepth() for s in self.getUsedScreens()]) + + def __str__(self): + string = "XSetup:\n" + string += " Layout: %s\n" % ({self.LAYOUT_SINGLE: "Single", + self.LAYOUT_CLONE: "Clone", + self.LAYOUT_DUAL: "Dual" + }[self.getLayout()]) + + i = 1 + for gfxcard in self.gfxcards: + string += " Gfxcard %i: %s\n" % (i,str(gfxcard)) + i += 1 + return string + +############################################################################ +class GfxCard(object): + """Represents a graphics card that is present in this computer.""" + + def __init__(self, setup, pci_id=None, x_device=None, detected_model=None, proprietary_driver=False): + self.setup = setup + self.x_config = self.setup.xorg_config + self.layout = XSetup.LAYOUT_SINGLE + self.pci_id = pci_id + self.screens = [] + + self.detected_model = detected_model + self.proprietary_driver = proprietary_driver + + self.video_ram = 1024 + + # The (optimised) layout that was detected in xorg.conf on device level. + self.detected_layout = XSetup.LAYOUT_SINGLE + self.detected_orientation = XSetup.POSITION_LEFTOF + + self.x_device = [] # This can be a list of xorg device sections + if x_device is not None: + self.x_device.append(x_device) + + def _addScreen(self,screen): + self.screens.append(screen) + + def _addXDevice(self,x_device): + self.x_device.append(x_device) + + def _finalizeInit(self): + # Finish initalisation. + + if len(self.x_device)!=0: + + # Try to find a gfx card model. + self.gfxcard_model = None + if self.x_device[0].boardname is not None: + # Look up the model by boardname. + try: + self.gfxcard_model = GetGfxCardModelDB().getGfxCardModelByName(self.x_device[0].boardname) + except KeyError: + pass + + if self.gfxcard_model is None: + # OK, try looking it up by driver. + try: + self.gfxcard_model = GetGfxCardModelDB().getGfxCardModelByDriverName(self.x_device[0].driver) + except KeyError: + self.gfxcard_model = self.detected_model + + # Write the current driver in the model + if self.x_device[0].driver: + self.gfxcard_model.setDriver(self.x_device[0].driver) + + self.proprietary_driver = self.gfxcard_model.getProprietaryDriver()==self.x_device[0].driver + + if self.x_device[0].videoram is not None: + self.video_ram = int(self.x_device[0].videoram) + + # Detect layout + if len(self.screens)>=2: + # Xorg ATI driver. + if self._getCurrentDriver() in ['ati','r128','radeon']: + merged = self.x_device[0].option.getOptionByName('mergedfb') + if merged is not None and xorgconfig.toBoolean(merged._row[2]): + self.detected_layout = XSetup.LAYOUT_CLONE + # ATI proprietary driver + elif self._getCurrentDriver()=='fglrx': + desktopsetup = self.x_device[0].option.getOptionByName('desktopsetup') + if desktopsetup is not None and desktopsetup._row[2]=='c': + self.detected_layout = XSetup.LAYOUT_CLONE + # nVidia proprietary driver + elif self._getCurrentDriver()=='nvidia': + twinview = self.x_device[0].option.getOptionByName('twinview') + if twinview is not None: + desktopsetup = self.x_device[0].option.getOptionByName('twinvieworientation') + if desktopsetup is not None and desktopsetup._row[2].lower()=='clone': + self.detected_layout = XSetup.LAYOUT_CLONE + # i810 driver + elif self._getCurrentDriver() in ['i810', 'intel']: + clone = self.x_device[0].option.getOptionByName('clone') + if clone is not None: + if xorgconfig.toBoolean(clone._row[2]): + self.detected_layout = XSetup.LAYOUT_CLONE + + else: + self.gfxcard_model = self.detected_model + + self.original_gfxcard_model = self.gfxcard_model + self.original_proprietary_driver = self.proprietary_driver + self.original_layout = self.layout + + # Give the screens a chance to finish initialisation. + for screen in self.screens: + screen._finalizeInit() + for screen in self.screens: + screen._resyncResolution() + + def _getDetectedLayout(self): + return self.detected_layout + + def _getDetectedDualheadOrientation(self): + return self.detected_orientation + + def _getDetectedGfxCardModel(self): + return self.detected_model + + def getScreens(self): + return self.screens[:] + + def getGfxCardModel(self): + return self.gfxcard_model + + def setGfxCardModel(self,gfx_card_model): + self.gfxcard_model = gfx_card_model + + for screen in self.screens: + screen._resyncResolution() + + def getXorgDeviceSection(self,index): + return self.x_device[index] + + def isProprietaryDriver(self): + return self.proprietary_driver + + def setProprietaryDriver(self,proprietary_driver): + self.proprietary_driver = proprietary_driver + + def getDetectedGfxCardModel(self): + return self.detected_model + + def getPCIBusID(self): + return self.pci_id + + def isXorgConfigChanged(self): + return self.original_gfxcard_model is not self.gfxcard_model or \ + self.original_proprietary_driver != self.proprietary_driver or \ + self.original_layout != self.layout + + def reset(self): + for screen in self.screens: + screen.reset() + + self.gfxcard_model = self.original_gfxcard_model + self.proprietary_driver = self.original_proprietary_driver + + def isAGP(self): + return self.pci_id=='PCI:1:0:0' # FIXME this might not be accurate. + + def getLayout(self): + return self.layout + + def setLayout(self,layout): + self.layout = layout + for screen in self.screens: + screen._resyncResolution() + + def getAvailableLayouts(self): + layouts = XSetup.LAYOUT_SINGLE + if len(self.screens)==2: + if self._getCurrentDriver() in ['fglrx', 'nvidia', 'i810']: + layouts |= XSetup.LAYOUT_CLONE | XSetup.LAYOUT_DUAL + return layouts + + def getVideoRam(self): + """ + Get the amount of video ram that this card has. + + The values returned have the following meanings: + 256 => 256 kB + 512 => 512 kB + 1024 => 1 MB + 2048 => 2 MB + 4096 => 4 MB + 8192 => 8 MB + 16384 => 16 MB + 32768 => 32 MB + 65536 => 64 MB or more + + The video ram setting is only used if the selected graphics card model requires + that the amount of video ram be explicitly specified. That is to say that + gfxcard.getGfxCardModel().getNeedVideoRam() returns true. + + Returns an integer from the list above. + """ + return self.video_ram + + def setVideoRam(self,ram): + self.video_ram = ram + + for screen in self.screens: + screen._resyncResolution() + + def _getCurrentDriver(self): + if self.proprietary_driver: + return self.gfxcard_model.getProprietaryDriver() + else: + return self.gfxcard_model.getDriver() + + def getRestartHint(self): + # The ATI propreitary drivers need a special AGP kernel module to be loaded. + # The best, and often only way to get this module loaded is to reboot. + # The same applies for removing the module. + hint = XSetup.RESTART_NONE + + if self.original_proprietary_driver: + original_gfx_driver = self.original_gfxcard_model.getProprietaryDriver() + else: + original_gfx_driver = self.original_gfxcard_model.getDriver() + + current_gfx_driver = self._getCurrentDriver() + + if current_gfx_driver!=original_gfx_driver: + # Restart X if the driver is different. + hint = XSetup.RESTART_X + + # Layout changes also require an X server restart. + if self.original_layout!=self.layout: + hint = XSetup.RESTART_X + + if original_gfx_driver=='fglrx' and current_gfx_driver in ['ati','r128','radeon']: + hint = XSetup.RESTART_SYSTEM + + # If a different kernel module is needed, then restart the system. + kernel_module_list = self._getLoadedKernelModules() + if self._needATIFglrx() and 'fglrx' not in kernel_module_list: + hint = XSetup.RESTART_SYSTEM + else: + if 'fglrx' in kernel_module_list: + hint = XSetup.RESTART_SYSTEM + + hintlist = [hint] + hintlist.extend([screen.getRestartHint() for screen in self.screens]) + return max(hintlist) + + def _needATIFglrx(self): + """Work out if the current configuration require the ATI fglrx kernel module.""" + return self.isAGP() and self.getGfxCardModel() is not None and \ + self.getGfxCardModel().getProprietaryDriver()=='fglrx' and self.isProprietaryDriver() + + def _getLoadedKernelModules(self): + return [line.split()[0] for line in open('/proc/modules')] + + def _getAvailableLayouts(self): + if len(self.screens)>=2: + drv = self._getCurrentDriver() + layouts = XSetup.LAYOUT_SINGLE | XSetup.LAYOUT_DUAL + if drv in ['fglrx', 'nvidia', 'i810']: + layouts |= XSetup.LAYOUT_CLONE + elif drv in ['ati', 'radeon', 'r128', 'intel']: + layouts = XSetup.LAYOUT_SINGLE + return layouts + else: + return XSetup.LAYOUT_SINGLE + + def __str__(self): + screen_string = ",".join([str(s) for s in self.screens]) + driver = self._getCurrentDriver() + return "GfxCard: {model:"+str(self.gfxcard_model)+", driver:"+driver+", screens:"+screen_string+"}" + + def _syncXorgConfig(self): + if self.proprietary_driver and self.gfxcard_model.getProprietaryDriver() is not None: + driver = self.gfxcard_model.getProprietaryDriver() + else: + driver = self.gfxcard_model.getDriver() + + # FIXME maybe this module stuff should migrate into XSetup. + + # --- Fix the module section --- + + # $raw_X->set_devices($card, @{$card->{cards} || []}); + # $raw_X->get_ServerLayout->{Xinerama} = { commented => !$card->{Xinerama}, Option => 1 } + #if defined $card->{Xinerama}; + module_sections = self.x_config.getSections("Module") + if len(module_sections) > 0: + module = module_sections[0] + else: + module = self.x_config.makeSection(None, ["Section", "Module"]) + self.x_config.append(module) + + module.removeModule('GLcore') + module.removeModule('glx') + module.removeModule('dbe') + + # Mandriva + #module.removeModule("/usr/X11R6/lib/modules/extensions/libglx.so") + + if driver=='nvidia': + module.addModule("glx") + + # Mandriva + # This loads the NVIDIA GLX extension module. + # IT IS IMPORTANT TO KEEP NAME AS FULL PATH TO libglx.so ELSE + # IT WILL LOAD XFree86 glx module and the server will crash. + + # module.addModule("/usr/X11R6/lib/modules/extensions/libglx.so") + # FIXME lib64 + elif self.gfxcard_model.getProprietaryDriver()!='fglrx': + module.addModule('glx') + module.addModule('GLcore') + + #module.removeModule("/usr/X11R6/lib/modules/extensions/libglx.a") + if driver=='fglrx': + module.addModule("glx") + module.addModule("dbe") + #elif driver!='nvidia': + # module.addModule("/usr/X11R6/lib/modules/extensions/libglx.a") + + # DRI + module.removeModule('dri') + if self.gfxcard_model.getDriGlx(): + module.addModule('dri') + + module.removeModule('v4l') + if not (self.gfxcard_model.getDriGlx() and self.gfxcard_model.getDriver()=='r128'): + module.addModule('v4l') + + # --- Fix all of the Device sections --- + for i in range(len(self.screens)): + + if i==len(self.x_device): + new_device = self.x_config.makeSection('',['section','device']) + self.x_config.append(new_device) + self.x_device.append(new_device) + new_device.identifier = self.x_config.createUniqueIdentifier("device") + + identifier = self.x_device[i].identifier + busid = self.x_device[i].busid + + self.x_device[i].clear() + + # Create a new Device section in the Xorg config file. + self.x_device[i].identifier = identifier + self.x_device[i].boardname = self.gfxcard_model.getName() + self.x_device[i].busid = self.pci_id + self.x_device[i].driver = driver + self.x_device[i].screen = str(i) + + if self.gfxcard_model.getVendor() is not None: + self.x_device[i].vendorname = self.gfxcard_model.getVendor() + + if self.gfxcard_model.getNeedVideoRam(): + self.x_device[i].videoram = self.video_ram + + # Setup Clone mode for second heads. + if driver in ["ati","r128","radeon"]: # Xorg ATI driver + merged_value = { + XSetup.LAYOUT_CLONE: "on", + XSetup.LAYOUT_SINGLE: "off", + XSetup.LAYOUT_DUAL: "on", + XSetup.LAYOUT_SINGLE_XINERAMA: "off" + }[self.layout] + + merged_option = self.x_device[i].option.makeLine(None,["MergedFB",merged_value]) + self.x_device[i].option.append(merged_option) + + if self.layout==XSetup.LAYOUT_CLONE: + monitor_model = self.setup.getSecondaryScreen().getMonitorModel() + if monitor_model is not None: + if monitor_model.getHorizontalSync() is not None: + hsyncline = self.x_device[i].option.makeLine(None,['CRT2HSync',monitor_model.getHorizontalSync()]) + self.x_device[i].option.append(hsyncline) + + if monitor_model.getVerticalSync() is not None: + vsyncline = self.x_device[i].option.makeLine(None,['CRT2VRefresh',monitor_model.getVerticalSync()]) + self.x_device[i].option.append(vsyncline) + + # FIXME option "CloneMode" "off" + + if driver=='fglrx': # ATI proprietary driver. + if self.layout==XSetup.LAYOUT_CLONE: + new_option = self.x_device[i].option.makeLine(None,["DesktopSetup","c"]) + self.x_device[i].option.append(new_option) + + # FIXME this probably won't work on laptops and DVI. The user will probably + # have to manually select the monitor types. + + # We do this to make sure that the driver starts up in clone mode even + # if it can't detect the second monitor. + new_option = self.x_device[i].option.makeLine(None,["ForceMonitors","crt1,crt2"]) + self.x_device[i].option.append(new_option) + + monitor_model = self.setup.getSecondaryScreen().getMonitorModel() + if monitor_model is not None: + if monitor_model.getHorizontalSync() is not None: + hsyncline = self.x_device[i].option.makeLine(None,['HSync2',monitor_model.getHorizontalSync()]) + self.x_device[i].option.append(hsyncline) + + if monitor_model.getVerticalSync() is not None: + vsyncline = self.x_device[i].option.makeLine(None,['VRefresh2',monitor_model.getVerticalSync()]) + self.x_device[i].option.append(vsyncline) + + if driver=='nvidia': # nVidia proprietary driver. + if self.layout==XSetup.LAYOUT_CLONE: + new_option = self.x_device[i].option.makeLine(None,["TwinView","on"]) + self.x_device[i].option.append(new_option) + new_option = self.x_device[i].option.makeLine(None,["TwinViewOrientation","clone"]) + self.x_device[i].option.append(new_option) + + monitor_model = self.setup.getSecondaryScreen().getMonitorModel() + if monitor_model is not None: + if monitor_model.getHorizontalSync() is not None: + hsyncline = self.x_device[i].option.makeLine(None,['SecondMonitorHorizSync',monitor_model.getHorizontalSync()]) + self.x_device[i].option.append(hsyncline) + + if monitor_model.getVerticalSync() is not None: + vsyncline = self.x_device[i].option.makeLine(None,['SecondMonitorVertRefresh',monitor_model.getVerticalSync()]) + self.x_device[i].option.append(vsyncline) + + if driver in ['i810']: # i810 driver + if self.layout in (XSetup.LAYOUT_SINGLE_XINERAMA, + XSetup.LAYOUT_DUAL, + XSetup.LAYOUT_CLONE): + new_option = self.x_device[i].option.makeLine(None,["MonitorLayout", "CRT,LFP"]) + self.x_device[i].option.append(new_option) + if self.layout==XSetup.LAYOUT_CLONE: + new_option = self.x_device[i].option.makeLine(None,["Clone","on"]) + self.x_device[i].option.append(new_option) + + # Find the closest matching refresh rate for the second monitor. + primary_screen = self.setup.getPrimaryScreen() + secondary_screen = self.setup.getSecondaryScreen() + resolution = primary_screen.getAvailableResolutions()[primary_screen.getResolutionIndex()] + secondary_resolution_index = secondary_screen.getAvailableResolutions().index(resolution) + secondary_rates = secondary_screen.getAvailableRefreshRatesForResolution(secondary_resolution_index) + primary_rate = primary_screen.getAvailableRefreshRates()[primary_screen.getRefreshRateIndex()] + + best_rate = 50 + best_score = 1000000 + for rate in secondary_rates: + score = abs(rate-primary_rate) + if score < best_score: + best_score = score + best_rate = rate + + # Specify a working refresh rate for the second monitor. + new_option = self.x_device[i].option.makeLine(None,["CloneRefresh",str(best_rate)]) + self.x_device[i].option.append(new_option) + + self._insertRawLinesIntoConfig(self.x_device[i], self.gfxcard_model.getLines()) + + self.screens[i]._syncXorgConfig(self.x_device[i]) + + self.original_gfxcard_model = self.gfxcard_model + self.original_proprietary_driver = self.proprietary_driver + self.original_layout = self.layout + + def _insertRawLinesIntoConfig(self,section,lines): + reader = csv.reader(lines,delimiter=' ') + for row in reader: + if len(row)>=2: + if row[0].lower()=="option": + option = section.option.makeLine(None,row[1:]) + section.option.append(option) + +############################################################################ +class Screen(object): + """Represents a single output/screen/monitor on a graphics card. + + Changes to the screen resolution, refresh rate, rotation and reflection + settings are not made active until the method applyResolutionSettings() is + called. After calling applyResolutionSettings(), changes can be backed out + of with the revertResolutionSettings() method. If you, should I say the user, + is satisfied with the new settings then call the acceptResolutionSettings() + method. + + Gamma correction settings take effect immediately, and don't take part in the + apply, revert and accept mechanism above. + """ + + RR_Rotate_0 = xf86misc.XF86Screen.RR_Rotate_0 + RR_Rotate_90 = xf86misc.XF86Screen.RR_Rotate_90 + RR_Rotate_180 = xf86misc.XF86Screen.RR_Rotate_180 + RR_Rotate_270 = xf86misc.XF86Screen.RR_Rotate_270 + RR_Reflect_X = xf86misc.XF86Screen.RR_Reflect_X + RR_Reflect_Y = xf86misc.XF86Screen.RR_Reflect_Y + + def __init__(self, gfx_card=None, x_config_screen=None, x_config_monitor=None, \ + monitor_model=None, x_config=None): + """Create a Screen object. + + This method is private to this module. + """ + self.gfx_card = gfx_card + self.x_config_screen = x_config_screen + + self.x_config_monitor = x_config_monitor + self.monitor_model = monitor_model + self.monitor_aspect = ModeLine.ASPECT_4_3 + self.original_monitor_model = monitor_model + self.x_config = x_config + + # Cookup some sensible screen sizes. + self.standard_sizes = GetMonitorModeDB().getAllResolutions() + + self.x_live_screen = None + + # Intialise the gamma settings with defaults. + self.redgamma = 1.0 + self.greengamma = 1.0 + self.bluegamma = 1.0 + self.allgamma = 1.0 + self.settingall = True + + # If there is a monitor xorg.conf section then look there for gamma info. + if self.x_config_monitor is not None: + gamma_row = self.x_config_monitor.getRow('gamma') + if gamma_row is not None: + # Read the gamma info out of xorg.conf + try: + if len(gamma_row)==3: + self.redgamma = float(gamma_row[0]) + self.greengamma = float(gamma_row[1]) + self.bluegamma = float(gamma_row[2]) + self.allgamma = self.redgamma + elif len(gamma_row)==1: + self.allgamma = float(gamma_row[0]) + self.redgamma = self.allgamma + self.greengamma = self.allgamma + self.bluegamma = self.allgamma + except ValueError: + pass + + # Try to work out if this monitor is setup for 4:3 modes or 16:9. + aspect_43_count = 0 + aspect_169_count = 0 + # Count the number of 4:3 modelines compared to 16:9 modes. + for mode in self.x_config_monitor.modeline: + try: + # Don't count the fallback resolution. It is also present + # if the monitor is widescreen. Just ignore it. + if (mode._row[2],mode._row[6])!=FALLBACK_RESOLUTION: + if MonitorModeDB.aspectRatio(mode._row[2],mode._row[6])==ModeLine.ASPECT_4_3: + aspect_43_count += 1 + else: + aspect_169_count += 1 + except IndexError: + pass + + if aspect_43_count >= aspect_169_count: + self.monitor_aspect = ModeLine.ASPECT_4_3 + else: + self.monitor_aspect = ModeLine.ASPECT_16_9 + + # Backup the settings + (self.originalredgamma, self.originalgreengamma, self.originalbluegamma) = ( + self.redgamma, self.greengamma, self.bluegamma) + self.originalallgamma = self.allgamma + self.originalsettingall = self.settingall + self.original_monitor_aspect = self.monitor_aspect + + def _setXLiveScreen(self,x_live_screen): + self.x_live_screen = x_live_screen + + def _finalizeInit(self): + + if self.x_live_screen is not None and self.x_live_screen.resolutionSupportAvailable(): + self._computeSizesFromXorg() + + (cw,ch,x,x) = self.x_live_screen.getSize() + i = 0 + self.currentsizeindex = 0 + for size in self.available_sizes: + if (cw,ch)==size: + self.currentsizeindex = i + break + i += 1 + + self.currentrefreshrate = self.x_live_screen.getRefreshRate() + self.currentrotation = self.x_live_screen.getRotation() & ( + Screen.RR_Rotate_0 | Screen.RR_Rotate_90 | Screen.RR_Rotate_180 | Screen.RR_Rotate_270) + self.currentreflection = self.x_live_screen.getRotation() & ( + Screen.RR_Reflect_X | Screen.RR_Reflect_Y) + + else: + # There is no live info, so try to collect some info out + # of xorg.conf itself. + + # Cookup some reasonable screen resolutions based on what we know about the monitor. + self._computeSizesFromMonitor() + + (cw,ch) = self.available_sizes[0] + self.currentrefreshrate = None + + # Dig through the Display sections in the xorg.conf Screen section + # and try to find the first resolution/mode. + if self.x_config_screen is not None: + default_depth = self.x_config_screen.defaultdepth + + current_mode_name = None + + for display_section in self.x_config_screen.getSections('display'): + if default_depth is None or display_section.depth==default_depth: + modes_row = display_section.getRow('modes') + if modes_row is not None and len(modes_row)>=1: + current_mode_name = modes_row[0] + break + + if current_mode_name is not None: + for mode in self.mode_list: + if mode.getName()==current_mode_name: + cw = mode.getWidth() + ch = mode.getHeight() + self.currentrefreshrate = mode.getVRefresh() + break + + # Work out the index of the current resolution + i = 0 + for size in self.available_sizes: + if (cw,ch)==size: + self.currentsizeindex = i + break + i += 1 + + if self.currentrefreshrate is None: + self.currentrefreshrate = self.getAvailableRefreshRates()[0] + + self.currentrotation = Screen.RR_Rotate_0 # FIXME + self.currentreflection = 0 # FIXME + + # Gamma settings + if self.x_live_screen is not None: + try: + (self.redgamma, self.greengamma, self.bluegamma, self.allgama, + self.settingall) = self._getGammaFromLiveScreen() + except: + (self.redgamma, self.greengamma, self.bluegamma, self.allgama, + self.settingall) = self._getGammaFromXorg() + else: + (self.redgamma, self.greengamma, self.bluegamma, self.allgama, + self.settingall) = self._getGammaFromXorg() + + self.originalsizeindex = self.currentsizeindex + self.original_size = self.getAvailableResolutions()[self.currentsizeindex] + self.originalrefreshrate = self.currentrefreshrate + self.originalrotation = self.currentrotation + self.originalreflection = self.currentreflection + + self.originalredgamma = self.redgamma + self.originalgreengamma = self.greengamma + self.originalbluegamma = self.bluegamma + + self.originalallgamma = self.allgamma + self.originalsettingall = self.settingall + + def _getGammaFromLiveScreen(self): + """Reads the gamma information from the x server""" + # Read the current gamma settings directly from X. + (redgamma, greengamma, bluegamma) = self.x_live_screen.getGamma() + + # Round the values off to 2 decimal places. + redgamma = round(self.redgamma,2) + greengamma = round(self.greengamma,2) + bluegamma = round(self.bluegamma,2) + + allgamma = redgamma + settingall = redgamma==greengamma==bluegamma + return (redgamma, greengamma, bluegamma, allgamma, settingall) + + def _getGammaFromXorg(self): + """Extracts the gamma information from the xorg configuration""" + # Set some gamma defaults + redgamma = greengamma = bluegamma = allgamma = 1.0 + settingall = True + + # Look for gamma information in xorg.conf + if self.x_config_monitor is not None: + gamma_row = self.x_config_monitor.getRow('gamma') + if gamma_row is not None: + try: + if len(gamma_row)==1: + allgamma = float(gamma_row[0]) + redgamma = greengamma = bluegamma = allgamma + self.settingall = True + elif len(gamma_row.row)==3: + redgamma = float(gamma_row[0]) + greengamma = float(gamma_row[1]) + bluegamma = float(gamma_row[2]) + allgamma = self.redgamma + settingall = False + except ValueError: + pass + return (redgamma, greengamma, bluegamma, allgamma, settingall) + + def _computeSizesFromXorg(self): + all_sizes = self.x_live_screen.getAvailableSizes() + self.available_sizes = [] + + # Some dualhead setups repolonger sizelists, those are unlikely + # to be standard zes, we still want to be able to use them.s + for i in range(len(all_sizes)): + if len(all_sizes[i]) > 2: + self.available_sizes.append(all_sizes[i][:2]) + elif len(alls_sizes[i]) == 2: + if (size[0],size[1]) in self.standard_sizes: + self.available_sizes.append(all_sizes[i]) + self.available_sizes.sort() + + def _computeSizesFromMonitor(self): + monitor_model = self.monitor_model + + if monitor_model is None: + # If there is no monitor model selected, then just use a default + # model so that we at least get some fairly safe resolutions. + monitor_model = GetMonitorModelDB().getMonitorByName("Monitor 800x600") + + self.mode_list = GetMonitorModeDB().getAvailableModes(monitor_model,self.monitor_aspect) + resolutions = set() + for mode in self.mode_list: + pw = mode.getWidth() + ph = mode.getHeight() + if (pw,ph) in self.standard_sizes and pw>=640 and ph>=480: + resolutions.add( (pw,ph) ) + + # Filter the sizes by the amount of video ram that we have. + color_bytes = self._getColorDepth()/8 + if self.gfx_card.getGfxCardModel().getNeedVideoRam(): + video_memory = self.gfx_card.getVideoRam() + else: + video_memory = 65536 # Big enough. + video_memory *= 1024 # Convert to bytes. + + # Filter the list of modes according to available memory. + self.available_sizes = [mode for mode in resolutions if mode[0]*mode[1]*color_bytes <= video_memory] + + self.available_sizes.sort() + + def _getColorDepth(self): + if self.gfx_card.getGfxCardModel().getNeedVideoRam(): + # If this card has limited memory then we fall back to 16bit colour. + if self.gfx_card.getVideoRam() <= 4096: + return 16 # 16bit colour + else: + return 24 # 24bit colour + else: + return 24 + + def getName(self): + try: + return "Screen %i" % (self.gfx_card.setup.getUsedScreens().index(self)+1) + except ValueError: + return "Screen ?" + + def isLive(self): + """Returns True if this screen is currently being used by the X server. + """ + return self.x_live_screen is not None + + def getMonitorModel(self): + """ + + Returns a MonitorModel object or None. + """ + return self.monitor_model + + def setMonitorModel(self,monitor_model): + """ + + Setting the monitor also changes the resolutions that are available. + + """ + self.monitor_model = monitor_model + self._resyncResolution() + + def getMonitorAspect(self): + """ + Get the aspect ratio for the monitor + + Returns one of ModeLine.ASPECT_4_3 or ModeLine.ASPECT_16_9. + """ + return self.monitor_aspect + + def setMonitorAspect(self,aspect): + """Specify the aspect ratio of the monitor. + + Keyword arguments: + aspect -- Aspect ratio. Either the constant ModeLine.ASPECT_4_3 or ModeLine.ASPECT_16_9. + + Setting this also changes the resolutions that are available. + """ + self.monitor_aspect = aspect + self._resyncResolution() + + def _resyncResolution(self): + try: + (preferred_width,preferred_height) = self.getAvailableResolutions()[self.getResolutionIndex()] + except IndexError: + print self.getAvailableResolutions() + (preferred_width,preferred_height) = self.getAvailableResolutions()[-1] + + if self.isResolutionLive(): + self._computeSizesFromXorg() + else: + # The list of resolutions needs to be updated. + self._computeSizesFromMonitor() + + if self.gfx_card.setup.getLayout()==XSetup.LAYOUT_CLONE: + if self.gfx_card.setup.getPrimaryScreen() is self: + # Filter the list of resolutions based on what the secondary screen can show. + secondary_screen = self.gfx_card.setup.getSecondaryScreen() + primary_set = set(self.available_sizes) + secondary_set = set(secondary_screen.available_sizes) + + common_set = primary_set.intersection(secondary_set) + + suitable_resolutions = [] + # Make sure that each resolution also has a common refresh rate. + for resolution in common_set: + primary_rates = self.getAvailableRefreshRatesForResolution(self.available_sizes.index(resolution)) + secondary_rates = secondary_screen.getAvailableRefreshRatesForResolution(secondary_screen.available_sizes.index(resolution)) + + if len(set(primary_rates).intersection(set(secondary_rates)))!=0: + suitable_resolutions.append(resolution) + + suitable_resolutions.sort() + self.available_sizes = suitable_resolutions + + # Now we select a resolution that closely matches the previous resolution. + best_score = 2000000 # big number. + best_index = 0 + resolution_list = self.getAvailableResolutions() + for i in range(len(resolution_list)): + (width,height) = resolution_list[i] + new_score = abs(width-preferred_width) + abs(height-preferred_height) + + if new_score < best_score: + best_index = i + best_score = new_score + self.setResolutionIndex(best_index) + + if self.gfx_card.setup.getLayout()==XSetup.LAYOUT_CLONE: + if self.gfx_card.setup.getSecondaryScreen() is self: + self.gfx_card.setup.getPrimaryScreen()._resyncResolution() + + def isXorgConfigChanged(self): + isroot = os.getuid()==0 + return self.original_monitor_model is not self.monitor_model \ + or self.original_monitor_aspect != self.monitor_aspect \ + or self.isGammaSettingsChanged() \ + or (self.isResolutionSettingsChanged() and (not self.isResolutionLive() or isroot)) + + def getRedGamma(self): + """Get the current red gamma value. + """ + return self.redgamma + + def setRedGamma(self,value): + """Set gamma correction value for red + + This method takes effect immediately on the X server if possible. + + Keyword arguments: + value -- gamma correction value (float) + """ + self.redgamma = value + if self.x_live_screen is not None: + self.x_live_screen.setGamma( (self.redgamma,self.greengamma,self.bluegamma) ) + self.settingall = False + + def getGreenGamma(self): + """Get the current green gamma value + """ + return self.greengamma + + def setGreenGamma(self,value): + """Set gamma correction value for green + + This method takes effect immediately on the X server if possible. + + Keyword arguments: + value -- gamma correction value (float) + """ + self.greengamma = value + if self.x_live_screen is not None: + self.x_live_screen.setGamma( (self.redgamma,self.greengamma,self.bluegamma) ) + self.settingall = False + + def getBlueGamma(self): + """Get the current blue gamma value + """ + return self.bluegamma + + def setBlueGamma(self,value): + """Set gamma correction value for blue + + This method takes effect immediately on the X server if possible. + + Keyword arguments: + value -- gamma correction value (float) + """ + self.bluegamma = value + if self.x_live_screen is not None: + self.x_live_screen.setGamma( (self.redgamma,self.greengamma,self.bluegamma) ) + self.settingall = False + + def getAllGamma(self): + """Get the gamma correction value for all colours. + + Returns a float. + + See isGammaEqual() + """ + return self.allgamma + + def setAllGamma(self,value): + """Set the gamma correction value for all colours. + + Keyword arguments: + value -- gamma correction value (float) + """ + self.allgamma = value + if self.x_live_screen is not None: + self.x_live_screen.setGamma( (self.allgamma,self.allgamma,self.allgamma) ) + self.settingall = True + + def isGammaLive(self): + """Returns true if modifications to the gamma are immediately visible. + """ + return self.x_live_screen is not None + + def isGammaEqual(self): + """Test whether each colour is using the same gamma correction value. + + Returns True if the gamma value is the same for all colours + """ + return self.getRedGamma()==self.getGreenGamma()==self.getBlueGamma() + + def getScreenIndex(self): + return self.gfx_card.getScreens().index(self) + + # Size and resolution + def getResolutionIndex(self): + """Get the current resolution of this screen. + + Returns an index into the list of available resolutions. See + getAvailableResolutions(). + """ + return self.currentsizeindex + + def setResolutionIndex(self,index): + """Set the resolution for this screen. + + This method does not take effect immediately, only after applyResolutionSetttings() + has been called. + + Keyword arguments: + index -- The index of the resolution to use. See getAvailableResolutions(). + """ + self.currentsizeindex = index + + def getAvailableResolutions(self): + """Get the list of available resolutions. + + Returns a list of screen (width,height) tuples. width and height are in + pixels. + """ + return self.available_sizes[:] + + # Rotation + def getRotation(self): + """Get the current rotation settings for this screen. + + Returns one of Screen.RR_Rotate_0, Screen.RR_Rotate_90, + Screen.RR_Rotate_180 or Screen.RR_Rotate_270 + """ + return self.currentrotation + + def setRotation(self,rotation): + """Set the rotation for this screen + + This method does not take effect immediately, only after + applyResolutionSetttings() has been called. See getAvailableRotations() + for how to find out which rotations are supported. + + Keyword arguments: + rotation -- One of Screen.RR_Rotate_0, Screen.RR_Rotate_90, + Screen.RR_Rotate_180 or Screen.RR_Rotate_270 + """ + self.currentrotation = rotation + + def getAvailableRotations(self): + """Get the supported rotations for this screen. + + Returns a bitmask of support rotations for this screen. The returned + integer is the bitwise OR of one or more of the constants here below. + * Screen.RR_Rotate_0 + * Screen.RR_Rotate_90 + * Screen.RR_Rotate_180 + * Screen.RR_Rotate_270 + """ + if self.x_live_screen is not None and self.x_live_screen.resolutionSupportAvailable(): + return self.x_live_screen.getAvailableRotations() & \ + (self.RR_Rotate_0 | self.RR_Rotate_90 | self.RR_Rotate_180 | self.RR_Rotate_270) + else: + return self.RR_Rotate_0 # FIXME + + + # Reflection + def getReflection(self): + """Get the current reflection settings for this screen. + + Returns the reflection settings as a bit string. Use Screen.RR_Reflect_X + and Screen.RR_Reflect_Y as bitmasks to determine which reflections are + in use. + """ + return self.currentreflection + + def setReflection(self,reflection): + """Set the reflection settings for this screen. + + This method does not take effect immediately, only after + applyResolutionSetttings() has been called. See getAvailableReflections() + for how to find out which rotations are supported. + + Keyword arguments: + reflection -- Bit string (Python integer) of desired reflections. + Bitwise OR Screen.RR_Reflect_X and Screen.RR_Reflect_Y + to construct the string. + """ + self.currentreflection = reflection + + def getAvailableReflections(self): + """Get the supported reflections for this screen. + + Returns a bit string (Python integer) of supported reflections. Use + Screen.RR_Reflect_X and Screen.RR_Reflect_Y as bitmasks to determine + which reflections are available. + """ + if self.x_live_screen is not None and self.x_live_screen.resolutionSupportAvailable(): + return self.x_live_screen.getAvailableRotations() & (self.RR_Reflect_X | self.RR_Reflect_Y) + else: + return 0 # FIXME + + # Refresh rates + def getRefreshRateIndex(self): + """Get the current refresh rate index for this screen. + + Returns an index into the list of refresh rates. See getAvailableRefreshRates(). + """ + rates = self.getAvailableRefreshRates() + i = 0 + for r in rates: + if r>=self.currentrefreshrate: + return i + i += 1 + return len(rates)-1 + + def setRefreshRateIndex(self,index): + """Set the refresh rate for this screen. + + Keyword arguments: + index -- Index into the list of refresh rates. See getAvailableRefreshRates(). + """ + self.currentrefreshrate = self.getAvailableRefreshRates()[index] + + def getAvailableRefreshRates(self): + """Get the list of available refresh rates + + Get the list of available refresh rates for the currently selected + resolution. See setResolutionIndex() and getAvailableRefreshRatesForResolution() + + Returns a list of integers in Hz. + """ + return self.getAvailableRefreshRatesForResolution(self.currentsizeindex) + + def getAvailableRefreshRatesForResolution(self,resolution_index): + """Get the list of available refresh rates for the given resolution + + Get the list of available refresh rates for the given resolution. + + Keyword arguments: + resolution_index -- Index into the list of resolutions. + + Returns a list of integers in Hz. + """ + isize = self.available_sizes[resolution_index] + if self.isResolutionLive(): + j = 0 + for size in self.x_live_screen.getAvailableSizes(): + (sw,sh,wm,hm) = size + if isize==(sw,sh): + rates = self.x_live_screen.getAvailableRefreshRates(j) + rates.sort() + return rates + j += 1 + assert False,"Can't find matching screen resolution" + else: + # + rates = [] + for mode in self.mode_list: + if isize==(mode.getWidth(),mode.getHeight()): + rates.append(mode.getVRefresh()) + + rates.sort() + return rates + + # Applying changes. + + def isResolutionLive(self): + return self.x_live_screen is not None and \ + self.x_live_screen.resolutionSupportAvailable() and \ + self.original_monitor_model is self.monitor_model and \ + self.original_monitor_aspect==self.monitor_aspect + + def isResolutionSettingsChanged(self): + try: + current_size = self.getAvailableResolutions()[self.currentsizeindex] + except IndexError: + #FIXME: why does this happen? + return False + return current_size != self.original_size or \ + self.currentrefreshrate != self.originalrefreshrate or \ + self.currentrotation != self.originalrotation or \ + self.currentreflection != self.originalreflection + + def applyResolutionSettings(self): + """Apply any tending resolution changes on the X server if possible. + + + """ + if self.isResolutionSettingsChanged() and self.isResolutionLive(): + # Work out what the correct index is for randr. + (width,height) = self.available_sizes[self.currentsizeindex] + sizeindex = 0 + for size in self.x_live_screen.getAvailableSizes(): + (pw,ph,wm,hm) = size + if pw==width and ph==height: + break + sizeindex += 1 + + rc = self.x_live_screen.setScreenConfigAndRate(sizeindex, \ + self.currentrotation | self.currentreflection, self.currentrefreshrate) + + # FIXME this can fail if the config on the server has been updated. + + def acceptResolutionSettings(self): + """Accept the last resolution change + """ + self.originalsizeindex = self.currentsizeindex + self.original_size = self.getAvailableResolutions()[self.currentsizeindex] + self.originalrefreshrate = self.currentrefreshrate + self.originalrotation = self.currentrotation + self.originalreflection = self.currentreflection + + def revertResolutionSettings(self): + """Revert the last resolution change on the X server + + """ + if self.x_live_screen is not None and self.x_live_screen.resolutionSupportAvailable(): + # Work out what the correct index is for randr. + (width,height) = self.available_sizes[self.originalsizeindex] + sizeindex = 0 + for size in self.x_live_screen.getAvailableSizes(): + (pw,ph,wm,hm) = size + if pw==width and ph==height: + break + sizeindex += 1 + + self.x_live_screen.setScreenConfigAndRate(sizeindex, \ + self.originalrotation | self.originalreflection, self.originalrefreshrate) + # FIXME this can fail if the config on the server has been updated. + + def resetResolutionSettings(self): + """Reset the resolution settings to the last accepted state + + """ + # Restore the resolution settings to their original state. + self.currentsizeindex = self.originalsizeindex + self.currentrefreshrate = self.originalrefreshrate + self.currentrotation = self.originalrotation + self.currentreflection = self.originalreflection + + def isGammaSettingsChanged(self): + return self.redgamma != self.originalredgamma or \ + self.greengamma != self.originalgreengamma or \ + self.bluegamma != self.originalbluegamma or \ + self.allgamma != self.originalallgamma or \ + self.settingall != self.originalsettingall + + def acceptGammaSettings(self): + (self.originalredgamma, self.originalgreengamma, self.originalbluegamma) = ( + self.redgamma, self.greengamma, self.bluegamma) + self.originalallgamma = self.allgamma + self.originalsettingall = self.settingall + + def revertGammaSettings(self): + (self.redgamma, self.greengamma, self.bluegamma) = ( + self.originalredgamma, self.originalgreengamma, self.originalbluegamma) + self.allgamma = self.originalallgamma + self.settingall = self.originalsettingall + + if self.x_live_screen is not None: + if self.settingall: + self.x_live_screen.setGamma( (self.allgamma,self.allgamma,self.allgamma) ) + else: + self.x_live_screen.setGamma( (self.redgamma,self.greengamma,self.bluegamma) ) + + def isGammaSettingsChanged(self): + if self.settingall: + return self.originalallgamma != self.allgamma + else: + return self.originalredgamma != self.redgamma or \ + self.originalgreengamma != self.greengamma or \ + self.originalbluegamma != self.bluegamma + + def reset(self): + if self.isLive(): + self.revertGammaSettings() + self.resetResolutionSettings() + + self.monitor_model = self.original_monitor_model + self.monitor_aspect = self.original_monitor_aspect + + def getRestartHint(self): + if self.original_monitor_model is not self.monitor_model \ + or self.original_monitor_aspect != self.monitor_aspect \ + or (self.isResolutionSettingsChanged() and not self.isResolutionLive()): + return XSetup.RESTART_X + return XSetup.RESTART_NONE + + def _syncXorgConfig(self,x_device): + layout = self.gfx_card.getLayout() + + if self.x_config_screen is None: + self.x_config_screen = self.x_config.makeSection('',['section','screen']) + self.x_config.append(self.x_config_screen) + self.x_config_screen.identifier = self.x_config.createUniqueIdentifier("screen") + self.x_config_screen.device = x_device.identifier + + bit_depth = self.gfx_card.setup._getColorDepth() + self.x_config_screen.defaultdepth = bit_depth + + # Maybe we don't have a X config monitor section. + if self.x_config_monitor is None: + # Make a monitor section. + self.x_config_monitor = self.x_config.makeSection('',['section','monitor']) + self.x_config.append(self.x_config_monitor) + self.x_config_monitor.identifier = self.x_config.createUniqueIdentifier("monitor") + self.x_config_screen.monitor = self.x_config_monitor.identifier + + # Empty the monitor section and fill it in again. + monitor_identifier = self.x_config_monitor.identifier + self.x_config_monitor.clear() + self.x_config_monitor.identifier = monitor_identifier + + if self.monitor_model is not None: + if self.monitor_model.getManufacturer() is not None: + self.x_config_monitor.vendorname = self.monitor_model.getManufacturer() + + self.x_config_monitor.modelname = self.monitor_model.getName() + + if self.monitor_model.getType()!=MonitorModel.TYPE_PLUGNPLAY: + if self.monitor_model.getHorizontalSync() is not None: + hsyncline = self.x_config_monitor.makeLine(None,['HorizSync',self.monitor_model.getHorizontalSync()]) + self.x_config_monitor.append(hsyncline) + + if self.monitor_model.getVerticalSync() is not None: + vsyncline = self.x_config_monitor.makeLine(None,['VertRefresh',self.monitor_model.getVerticalSync()]) + self.x_config_monitor.append(vsyncline) + + # Add a bunch of standard mode lines. + mode_list = GetMonitorModeDB().getAvailableModes(self.monitor_model,self.monitor_aspect) + + if mode_list is not None: + + # Filter the mode list by video memory. + color_bytes = bit_depth/8 + if self.gfx_card.getGfxCardModel().getNeedVideoRam(): + video_memory = self.gfx_card.getVideoRam() + else: + video_memory = 65536 # Big enough. + video_memory *= 1024 # Convert to bytes. + mode_list = [mode for mode in mode_list if mode.getWidth()*mode.getHeight()*color_bytes <= video_memory] + + def mode_cmp(a,b): return cmp(a.getWidth(),b.getWidth()) + mode_list.sort(mode_cmp) + + for mode in mode_list: + modeline = self.x_config_monitor.modeline.makeLine(None,mode.getXorgModeLineList()) + self.x_config_monitor.modeline.append(modeline) + + # Specify the preferred resolution. + + # Get rid of any old display subsections. + for display_section in self.x_config_screen.getSections('display'): + self.x_config_screen.remove(display_section) + + try: + (preferred_width, preferred_height) = self.getAvailableResolutions()[self.currentsizeindex] + preferred_rate = self.getAvailableRefreshRates()[self.getRefreshRateIndex()] + except IndexError, errmsg: + # This is presumed to be better than a crash: + print "Failed to get preferred width, height, or rate - Assuming none. IndexError: ", errmsg + preferred_width = 0 + preferred_height = 0 + preferred_rate = 0 + + # Find the monitor supported mode that best matches what the user has selected. + best_score = 2000000 # big number. + best_index = 0 + for i in range(len(mode_list)): + mode = mode_list[i] + new_score = abs(mode.getWidth()-preferred_width) + \ + abs(mode.getHeight()-preferred_height) + \ + abs(mode.getVRefresh()-preferred_rate) + if new_score < best_score: + best_index = i + best_score = new_score + + # This is all about putting the list of resolutions into a + # sensible preferred order starting with what the user has chosen + # and then the rest of the resolutions. + lower = best_index - 1 + higher = best_index + 1 + len_modes = len(mode_list) + + mode_indexes = [] + mode_indexes.append(best_index) + while lower>=0 or higher<len_modes: # interlace the two sets of indexes. + if higher<len_modes: + mode_indexes.append(higher) + higher += 1 + if lower>=0: + mode_indexes.append(lower) + lower -= 1 + + # Convert the list of resolution indexes into monitor mode names and a modes line for xorg.conf. + mode_list_line = ['modes'] + mode_list_line.extend([ mode_list[mode_index].getName() for mode_index in mode_indexes ]) + + # Create the Display subsections in the Screen section. + display_section = self.x_config_screen.makeSection(None,['SubSection','Display']) + display_section.depth = bit_depth + + # The virtual screen size hack should not be used in combination + # with Xinerama (RandR doesn't work with Xinerama). + if layout!=XSetup.LAYOUT_SINGLE_XINERAMA and self.isLive(): + # Find the largest monitor supported mode. We need this info + # to set the size of the virtual screen. See the big comment + # in displayconfig-restore.py. + virtual_width = max([mode_list[mode_index].getWidth() for mode_index in mode_indexes]) + virtual_height = max([mode_list[mode_index].getHeight() for mode_index in mode_indexes]) + display_section.append(display_section.makeLine(None,["virtual",virtual_width,virtual_height])) + + display_section.append(display_section.makeLine(None,mode_list_line)) + + self.x_config_screen.append(display_section) + + # Set the gamma info too. + if self.settingall: + gamma_row = self.x_config_monitor.makeLine(None,['gamma',str(self.allgamma)]) + else: + gamma_row = self.x_config_monitor.makeLine(None,['gamma',str(self.redgamma),str(self.greengamma),str(self.bluegamma)]) + self.x_config_monitor.append(gamma_row) + + # If resolution changes were not live because the monitor has been changed + # then we also stop them from being live from now on too. + if not self.isResolutionLive(): + self.x_live_screen = None + self.acceptResolutionSettings() + + # The orignal monitor model is now the selected one. => no changes need to be applied now. + self.original_monitor_model = self.monitor_model + self.original_monitor_aspect = self.monitor_aspect + + def _getXorgScreenSection(self): + return self.x_config_screen + + def _getGfxCard(self): + return self.gfx_card + + def __str__(self): + # FIXME string = str(self.getIdentifier()) + " {" + string = " {" + if self.isLive(): + string += "Size: " + string += str(self.getAvailableResolutions()[self.getResolutionIndex()]) + string += " " + else: + string += "Not live, " + if self.monitor_model is not None: + string += "Monitor:" + str(self.monitor_model) + string += "}" + return string + +############################################################################ +class GfxCardModel(object): + """Describes the properties of a particular model of graphics card. + + """ + def __init__(self,name): + self.name = name + self.vendor = None + self.server = None + self.driver = None + self.proprietarydriver = None + self.lines = [] + self.seeobj = None + self.noclockprobe = None + self.unsupported = None + self.driglx = None + self.utahglx = None + self.driglxexperimental = None + self.utahglxexperimental = None + self.badfbrestore = None + self.badfbrestoreexf3 = None + self.multihead = None + self.fbtvout = None + self.needvideoram = None + + def getName(self): return self.name + + def setVendor(self,vendor): self.vendor = vendor + def getVendor(self): return self._get(self.vendor,"getVendor",None) + def setServer(self,server): self.server = server + def getServer(self): return self._get(self.server,"getServer",None) + def setDriver(self,driver): self.driver = driver + def getDriver(self): return self._get(self.driver,"getDriver",None) + def setProprietaryDriver(self,proprietarydriver): self.proprietarydriver = proprietarydriver + def getProprietaryDriver(self): return self._get(self.proprietarydriver,"getProprietaryDriver",None) + def addLine(self,line): self.lines.append(line) + def getLines(self): + if (len(self.lines)==0) and (self.seeobj is not None): + return self.seeobj.getLines() + else: + return self.lines[:] # Copy + + def setNoClockProbe(self,noprobe): self.noclockprobe = noprobe + def getNoClockProbe(self): return self._get(self.noclockprobe,"getNoClockProbe",False) + def setUnsupported(self,unsupported): self.unsupported = unsupported + def getUnsupported(self): return self._get(self.unsupported,"getUnsupported",False) + def setDriGlx(self,on): self.driglx = on + def getDriGlx(self): return self._get(self.driglx,"getDriGlx",False) + def setUtahGlx(self,on): self.utahglx = on + def getUtahGlx(self): return self._get(self.utahglx,"getUtahGlx",False) + def setDriGlxExperimental(self,on): self.driglxexperimental = on + def getDriGlxExperimental(self): return self._get(self.driglxexperimental,"getDriGlxExperimental",False) + def setUtahGlxExperimental(self,on): self.utahglxexperimental = on + def getUtahGlxExperimental(self): return self._get(self.utahglxexperimental,"getUtahGlxExperimental",False) + def setBadFbRestore(self,on): self.badfbrestore = on + def getBadFbRestore(self,proprietary=False): + if proprietary: + driver = self.getProprietaryDriver() + else: + driver = self.getDriver() + if driver in ['i810','intel','fbdev','nvidia','vmware']: + return True + if self.badfbrestore is not None: + return self.badfbrestore + if self.seeobj is not None: + return self.seeobj.getBadFbRestore(proprietary) + return False + def setBadFbRestoreXF3(self,on): self.badfbrestoreexf3 = on + def getBadFbRestoreXF3(self): return self._get(self.badfbrestoreexf3,"getBadFbRestoreXF3",False) + def setMultiHead(self,n): self.multihead = n + def getMultiHead(self): return self._get(self.multihead,"getMultiHead",1) + def setFbTvOut(self,on): self.fbtvout = on + def getFbTvOut(self): return self._get(self.fbtvout,"getFbTvOut",False) + def setNeedVideoRam(self,on): self.needvideoram = on + def getNeedVideoRam(self): return self._get(self.needvideoram,"getNeedVideoRam",False) + def setSee(self,seeobj): self.seeobj = seeobj + + # If the seeobj is set, then all attributes that are not filled in for this + # instance are inheritted from seeobj. + def _get(self,attr,meth,default): + if attr is not None: + return attr + if self.seeobj is not None: + return getattr(self.seeobj,meth)() + else: + return default + + def __str__(self): + return self.getName() + +############################################################################ +gfxcard_model_db_instance = None # Singleton. + +def GetGfxCardModelDB(): + """Returns a GfxCardModelDB instance. + """ + global gfxcard_model_db_instance + # Lazy instantiation. + if gfxcard_model_db_instance is None: + gfxcard_model_db_instance = GfxCardModelDB() + return gfxcard_model_db_instance + +############################################################################ +class GfxCardModelDB(object): + def __init__(self): + # List of gfx card databases, if order of preference. + filename = '/usr/share/ldetect-lst/Cards+' + if not os.path.exists(filename): + filename = os.path.join(data_file_dir,"Cards+") + + # The card DB. A dict mapping card names to card objects. + self.db = {} + # The keys in this dict will be vendor names, values are dicts mapping card names to objects. + self.vendordb = {} + self.driverdb = {} + + self.drivers = self._getAvailableDrivers() + + self.proprietary_drivers = [] + + self._checkProprietaryDrivers() + self._loadDrivers(self.drivers, self.proprietary_drivers) + self._loadDB(filename) + + def getGfxCardModelByName(self,name): + return self.db[name] + + def getGfxCardModelByDriverName(self,driver_name): + return self.driverdb[driver_name] + + def getAllGfxCardModelNames(self): + return self.db.keys() + + def _getDriverDirs(self): + "Returns a list of directories where X driver files may be located" + + # Fallback dir: + defaultDirs = ["/usr/lib/xorg/modules/drivers/"] + + # Get display number: + display_number = 0 + if "DISPLAY" in os.environ: + display_name = os.environ["DISPLAY"] + displayRE = re.compile("^.*:(\d+)\.\d+$") + m = displayRE.match(display_name) + if m: + display_number = int(m.group(1)) + else: + print "failed to parse display number from '%s' - falling back to default (%d)" % (display_name, display_number) + else: + print "$DISPLAY not set - falling back to default number (%d)" % display_number + + # Get the list of module paths from the Xorg log file: + XLogfile = "/var/log/Xorg.%d.log" % display_number + cmd = "awk -F \" ModulePath set to \" '/^\(..\) ModulePath set to (.*)/ {print $2}' %s" % XLogfile + + baseList = os.popen(cmd).readline().strip().strip('"') + if baseList == "": + print "warning: failed to get module paths from '%s' - falling back to default" % XLogfile + return defaultDirs + + pathList = [] + for basePath in baseList.split(","): + pathList.append("%s/drivers/" % basePath) + + return pathList + + def _getAvailableDrivers(self): + """ + Returns the list of available X graphics drivers. + Algorithm taken from Xorg source (see GenerateDriverlist() in xf86Config.C). + """ + + # These are drivers that cannot actually be used in xorg.conf, hence they are hidden: + hiddenDrivers = ( + "atimisc", # seems to be just the internal implementation for ati driver + "dummy", # dummy driver without any output + "v4l", # not an actual video device driver, but just the v4l module + "ztv" # seems to be the TV output module for AMD Geode + ) + + drivers = [] + driverDirectories = self._getDriverDirs() + + driverNameRE = re.compile("^(.+)_drv.(s)?o$") + for ddir in driverDirectories: + try: + driverFiles = os.listdir(ddir) + except OSError: + print "error reading directory '%s'" % ddir + continue + for f in driverFiles: + m = driverNameRE.match(f) + if m: + driverName = m.group(1) + if driverName in drivers: + print "ignoring duplicate driver '%s/%s'" % (ddir, f) + else: + if driverName in hiddenDrivers: + #print "ignoring hidden driver '%s'" % driverName + pass + else: + drivers.append(driverName) + else: + #print "ignoring driver file with invalid name '%s'" % f + pass + #print "found %d drivers" % len(drivers) + return drivers + + def _checkProprietaryDrivers(self): + # Check for the NVidia driver. + # FIXME x86_64 => 'lib64' + + if (os.path.exists("/usr/X11R6/lib/modules/drivers/nvidia_drv.o") and \ + os.path.exists("/usr/X11R6/lib/modules/extensions/libglx.so")) \ + or \ + (os.path.exists("/usr/lib/xorg/modules/drivers/nvidia_drv.o") and \ + os.path.exists("/usr/lib/xorg/modules/libglx.so")) \ + or \ + (os.path.exists("/usr/lib/xorg/modules/drivers/nvidia_drv.so") and \ + os.path.exists("/usr/lib/xorg/modules/extensions/libglx.so")): + self.proprietary_drivers.append("nvidia") + + # Check for the ATI driver + if (os.path.exists("/usr/X11R6/lib/modules/dri/fglrx_dri.so") and \ + os.path.exists("/usr/X11R6/lib/modules/drivers/fglrx_drv.o")) or \ + (os.path.exists("/usr/lib/dri/fglrx_dri.so") and \ + os.path.exists("/usr/lib/xorg/modules/drivers/fglrx_drv.so")): + self.proprietary_drivers.append("fglrx") + + # FIXME MATROX_HAL? + + def _loadDrivers(self, drivers, proprietary_drivers): + # Insert the Driver entries. + for drivername in drivers: + cardobj = GfxCardModel(drivername) + cardobj.setDriver(drivername) + self.db[drivername] = cardobj + self.driverdb[drivername] = cardobj + + if drivername=="nv" and "nvidia" in proprietary_drivers: + cardobj.setProprietaryDriver("nvidia") + self.driverdb["nvidia"] = cardobj + elif drivername=="ati" and "fglrx" in proprietary_drivers: + cardobj.setProprietaryDriver("fglrx") + self.driverdb["fglrx"] = cardobj + + def _loadDB(self,filename): + vendors = ['3Dlabs', 'AOpen', 'ASUS', 'ATI', 'Ark Logic', 'Avance Logic', + 'Cardex', 'Chaintech', 'Chips & Technologies', 'Cirrus Logic', 'Compaq', + 'Creative Labs', 'Dell', 'Diamond', 'Digital', 'ET', 'Elsa', 'Genoa', + 'Guillemot', 'Hercules', 'Intel', 'Leadtek', 'Matrox', 'Miro', 'NVIDIA', + 'NeoMagic', 'Number Nine', 'Oak', 'Orchid', 'RIVA', 'Rendition Verite', + 'S3', 'Silicon Motion', 'STB', 'SiS', 'Sun', 'Toshiba', 'Trident', + 'VideoLogic'] + + cardobj = None + # FIXME the file might be compressed. + fhandle = open(filename,'r') + for line in fhandle.readlines(): + line = line.strip() + if len(line)!=0: + if not line.startswith("#"): + if line.startswith("NAME"): + cardobj = GfxCardModel(line[4:].strip()) + cardname = cardobj.getName() + self.db[cardname] = cardobj + + # Try to extract a vendor name from the card's name. + for vendor in vendors: + if vendor in cardname: + cardobj.setVendor(vendor) + if vendor not in self.vendordb: + self.vendordb[vendor] = {} + self.vendordb[vendor][cardname] = cardobj + break + else: + if "Other" not in self.vendordb: + self.vendordb["Other"] = {} + self.vendordb["Other"][cardname] = cardobj + + elif line.startswith("SERVER"): + cardobj.setServer(line[6:].strip()) + elif line.startswith("DRIVER2"): + driver = line[7:].strip() + if driver in self.proprietary_drivers: + cardobj.setProprietaryDriver(driver) + elif line.startswith("DRIVER"): + cardobj.setDriver(line[6:].strip()) + elif line.startswith("LINE"): + cardobj.addLine(line[4:].strip()) + elif line.startswith("SEE"): + try: + cardobj.setSee(self.db[line[3:].strip()]) + except KeyError: + pass + elif line.startswith("NOCLOCKPROBE"): + cardobj.setNoClockProbe(True) + elif line.startswith("UNSUPPORTED"): + cardobj.setUnsupported(True) + elif line.startswith("DRI_GLX"): + cardobj.setDriGlx(True) + elif line.startswith("UTAH_GLX"): + cardobj.setUtahGlx(True) + elif line.startswith("DRI_GLX_EXPERIMENTAL"): + cardobj.setDriGlxExperimental(True) + elif line.startswith("UTAH_GLX_EXPERIMENTAL"): + cardobj.setUtahGlxExperimental(True) + elif line.startswith("BAD_FB_RESTORE"): + cardobj.setBadFbRestore(True) + elif line.startswith("BAD_FB_RESTORE_XF3"): + cardobj.setBadFbRestoreXF3(True) + elif line.startswith("MULTI_HEAD"): + cardobj.setMultiHead(int(line[10:].strip())) + elif line.startswith("FB_TVOUT"): + cardobj.setFbTvOut(True) + elif line.startswith("NEEDVIDEORAM"): + cardobj.setNeedVideoRam(True) + fhandle.close() + +############################################################################ +class MonitorModel(object): + TYPE_NORMAL = 0 + TYPE_PLUGNPLAY = 1 + TYPE_CUSTOM = 2 + + def __init__(self): + self.name = None + self.manufacturer = None + self.eisaid = None + self.horizontalsync = None + self.verticalsync = None + self.dpms = False + self.type = MonitorModel.TYPE_NORMAL + + def copy(self): + newmonitor = MonitorModel() + newmonitor.name = self.name + newmonitor.manufacturer = self.manufacturer + newmonitor.eisaid = self.eisaid + newmonitor.horizontalsync = self.horizontalsync + newmonitor.verticalsync = self.verticalsync + newmonitor.dpms = self.dpms + return newmonitor + + def getName(self): return self.name + def setName(self,name): self.name = name + def getManufacturer(self): return self.manufacturer + def setManufacturer(self,manufacturer): self.manufacturer = manufacturer + def setEisaId(self,eisaid): self.eisaid = eisaid + def getEisaId(self): return self.eisaid + def setDpms(self,on): self.dpms = on + def getDpms(self): return self.dpms + def getHorizontalSync(self): return self.horizontalsync + def setHorizontalSync(self,horizontalsync): self.horizontalsync = horizontalsync + def getVerticalSync(self): return self.verticalsync + def setVerticalSync(self,verticalsync): self.verticalsync = verticalsync + def setType(self,flag): self.type = flag + def getType(self): return self.type + def __str__(self): + return "{Name:"+self.name+"}" + +############################################################################ +class PlugNPlayMonitorModel(MonitorModel): + def __init__(self,monitor_model_db): + MonitorModel.__init__(self) + self.monitor_detected = False + self.monitor_model_db = monitor_model_db + + def getEisaId(self): + self._detectMonitor() + return self.eisaid + + def getHorizontalSync(self): + self._detectMonitor() + return self.horizontalsync + + def getVerticalSync(self): + self._detectMonitor() + return self.verticalsync + + def _detectMonitor(self): + if not self.monitor_detected: + (eisaid, horizontalsync, verticalsync) = self.monitor_model_db._detectMonitor() + if eisaid is not None: + self.eisaid = eisaid + if horizontalsync is not None: + self.horizontalsync = horizontalsync + if verticalsync is not None: + self.verticalsync = verticalsync + + self.monitor_detected = True + +############################################################################ +monitor_model_db_instance = None # Singleton + +def GetMonitorModelDB(force=False): + """Returns a GetMonitorModelDB instance. + """ + global monitor_model_db_instance + if monitor_model_db_instance is None or force == True: + monitor_model_db_instance = MonitorModelDB() + return monitor_model_db_instance + +############################################################################ +class MonitorModelDB(object): + def __init__(self): + self.db = {} + self.vendordb = {} + self.genericdb = {} + self.customdb = {} + self.custom_counter = 1 + self.monitor_detect_run = False + + # Plug'n Play is a kind of fake entry for monitors that are detected but unknown. + # It's frequency info is filled in by hardware detection or from the X server config. + self._plugnplay = PlugNPlayMonitorModel(self) + self._plugnplay.setName("Plug 'n' Play") + self._plugnplay.setManufacturer(self._plugnplay.getName()) + self._plugnplay.setType(MonitorModel.TYPE_PLUGNPLAY) + # This default is what Xorg claims to use when there is no + # horizontal sync info in the a monitor section. + self._plugnplay.setHorizontalSync("28.0-33.0") + # This default is what Xorg claims to use when there is no + # vertical sync info in the a monitor section. + self._plugnplay.setVerticalSync("43-72") + self.customdb[self._plugnplay.getName()] = self._plugnplay + self.db[self._plugnplay.getName()] = self._plugnplay + + # Load monitors from the shipped database + filename = "/usr/share/ldetect-lst/MonitorsDB" + if not os.path.exists(filename): + filename = os.path.join(data_file_dir,"MonitorsDB") + self.load(filename) + # Load monitors from the custom database + filename = os.path.join(var_data_dir, "CustomMonitorsDB") + if os.path.exists(filename): + self.load(filename) + + def load(self,filename,split=";"): + fhandle = open(filename,'r') + for line in fhandle.readlines(): + line = line.strip() + if len(line)!=0: + if not line.startswith("#"): + try: + parts = line.split(split) + monitorobj = MonitorModel() + monitorobj.setManufacturer(parts[0].strip()) + monitorobj.setName(parts[1].strip()) + monitorobj.setEisaId(parts[2].strip().upper()) + monitorobj.setHorizontalSync(parts[3].strip()) + monitorobj.setVerticalSync(parts[4].strip()) + if len(parts)>=6: + monitorobj.setDpms(parts[5].strip()=='1') + self.db[monitorobj.getName()] = monitorobj + + if monitorobj.getManufacturer() in \ + ["Generic LCD Display", "Generic CRT Display"]: + self.genericdb[monitorobj.getName()] = monitorobj + else: + if monitorobj.getManufacturer() not in self.vendordb: + self.vendordb[monitorobj.getManufacturer()] = {} + self.vendordb[monitorobj.getManufacturer()]\ + [monitorobj.getName()] = monitorobj + + except IndexError: + print "Bad monitor line:",line + fhandle.close() + + def getMonitorByName(self,name): + return self.db.get(name,None) + + def newCustomMonitor(self,name=None): + custom_model = MonitorModel() + if name is None: + name = "Custom %i" % self.custom_counter + custom_model.setName(name) + self.db[custom_model.getName()] = custom_model + self.customdb[name] = custom_model + self.custom_counter += 1 + return custom_model + + def getCustomMonitors(self): + return self.customdb + + def detect(self): + """Detect the attached monitor. + + Returns a 'monitor' object on success, else None. + """ + (eisaid,hrange,vrange) = self._detectMonitor() + + # Look up the EISAID in our database. + if eisaid is not None: + for monitor in self.db: + if eisaid==self.db[monitor].getEisaId(): + return self.db[monitor] + + return self._plugnplay + + def _detectMonitor(self): + if not self.monitor_detect_run: + eisaid = None + hrange = None + vrange = None + + if os.path.isfile("/usr/sbin/monitor-edid"): + # This utility appeared in Mandriva 2005 LE + output = ExecWithCapture("/usr/sbin/monitor-edid",["monitor-edid","-v"]) + for line in output.split("\n"): + if "HorizSync" in line: + hrange = line.split()[1] + elif "VertRefresh" in line: + vrange = line.split()[1] + elif line.startswith("EISA ID:"): + eisaid = line[9:].upper() + + elif os.path.isfile("/usr/sbin/ddcxinfos"): + # This utility _was_ standard on Mandrake 10.1 and earlier. + output = ExecWithCapture("/usr/sbin/ddcxinfos",["ddcxinfos"]) + for line in output.split("\n"): + if "HorizSync" in line: + hrange = line.split()[0] + elif "VertRefresh" in line: + vrange = line.split()[0] + elif "EISA ID=" in line: + eisaid = line[line.find("EISA ID=")+8:].upper() + + elif os.path.isfile("/usr/sbin/ddcprobe"): + # on Debian + """ + ddcprobe's output looks like this: + + ... + eisa: SAM00b1 + ... + monitorrange: 30-81, 56-75 + ... + """ + output = ExecWithCapture("/usr/sbin/ddcprobe",["ddcprobe"]) + for line in output.split("\n"): + if line.startswith("eisa:"): + parts = line.split(":") + if len(parts)>=2: + eisaid = parts[1].strip().upper() + elif line.startswith("monitorrange:"): + parts = line.replace(',','').split() + if len(parts)==3: + hrange = parts[1].strip() + vrange = parts[2].strip() + + self.detected_eisa_id = eisaid + self.detected_h_range = hrange + self.detected_v_range = vrange + self.monitor_detect_run = True + + return (self.detected_eisa_id, self.detected_h_range, self.detected_v_range) + +############################################################################ + +SYNC_TOLERANCE = 0.01 # 1 percent +class ModeLine(object): + ASPECT_4_3 = 0 + ASPECT_16_9 = 1 + + XF86CONF_PHSYNC = 0x0001 + XF86CONF_NHSYNC = 0x0002 + XF86CONF_PVSYNC = 0x0004 + XF86CONF_NVSYNC = 0x0008 + XF86CONF_INTERLACE = 0x0010 + XF86CONF_DBLSCAN = 0x0020 + XF86CONF_CSYNC = 0x0040 + XF86CONF_PCSYNC = 0x0080 + XF86CONF_NCSYNC = 0x0100 + XF86CONF_HSKEW = 0x0200 # hskew provided + XF86CONF_BCAST = 0x0400 + XF86CONF_CUSTOM = 0x0800 # timing numbers customized by editor + XF86CONF_VSCAN = 0x1000 + flags = {"interlace": XF86CONF_INTERLACE, + "doublescan": XF86CONF_DBLSCAN, + "+hsync": XF86CONF_PHSYNC, + "-hsync": XF86CONF_NHSYNC, + "+vsync": XF86CONF_PVSYNC, + "-vsync": XF86CONF_NVSYNC, + "composite": XF86CONF_CSYNC, + "+csync": XF86CONF_PCSYNC, + "-csync": XF86CONF_NCSYNC } + + # Thanks go out to Redhat for this code donation. =) + def __init__(self, elements): + self.name = elements[1].strip('"') + self.clock = float(elements[2]) + self.hdisp = int(elements[3]) + self.hsyncstart = int(elements[4]) + self.hsyncend = int(elements[5]) + self.htotal = int(elements[6]) + self.vdisp = int(elements[7]) + self.vsyncstart = int(elements[8]) + self.vsyncend = int(elements[9]) + self.vtotal = int(elements[10]) + + self.flags = 0 + for i in range(11, len(elements)): + try: + self.flags |= ModeLine.flags[string.lower(elements[i])] + except KeyError: + pass + + def getWidth(self): + return self.hdisp + + def getHeight(self): + return self.vdisp + + def getName(self): + return self.name + + def getVRefresh(self): + vrefresh = self.clock * 1000000.0 / float(self.htotal * self.vtotal) + if self.flags & ModeLine.XF86CONF_INTERLACE: + vrefresh = vrefresh * 2.0 + if self.flags & ModeLine.XF86CONF_DBLSCAN: + vrefresh = vrefresh / 2.0 + return int(round(vrefresh)) + + # Basically copied from xf86CheckModeForMonitor + def supports(self, monitor_hsync, monitor_vsync): + hsync = self.clock * 1000 / self.htotal + for freq in monitor_hsync: + if hsync > freq[0] * (1.0 - SYNC_TOLERANCE) and hsync < freq[1] * (1.0 + SYNC_TOLERANCE): + break + else: + return False + + vrefresh = self.getVRefresh() + for freq in monitor_vsync: + if vrefresh > freq[0] * (1.0 - SYNC_TOLERANCE) and vrefresh < freq[1] * (1.0 + SYNC_TOLERANCE): + return True + return False + + def getXorgModeLineList(self): + row = [self.name, str(self.clock), str(self.hdisp), str(self.hsyncstart), str(self.hsyncend), + str(self.htotal), str(self.vdisp), str(self.vsyncstart), str(self.vsyncend), str(self.vtotal)] + + for (flag_name,flag_bit) in ModeLine.flags.iteritems(): + if self.flags & flag_bit: + row.append(flag_name) + return row + + def __str__(self): + return "ModeLine:"+self.name + +############################################################################ +monitor_mode_db_instance = None # Singleton + + +def GetMonitorModeDB(): + """Returns a GetMonitorModeDB instance. + """ + global monitor_mode_db_instance + if monitor_mode_db_instance is None: + monitor_mode_db_instance = MonitorModeDB() + return monitor_mode_db_instance + +############################################################################ +class MonitorModeDB(object): + def __init__(self): + self.db = {} + self.db169 = {} + + module_dir = os.path.dirname(os.path.join(os.getcwd(),__file__)) + self.load(os.path.join(data_file_dir,"vesamodes")) + self.load(os.path.join(data_file_dir,"extramodes")) + self.load(os.path.join(data_file_dir,"widescreenmodes")) + + # Make a list of screen sizes for the getAllResolutions() method. + self.all_resolutions = [] + for mode in self.db.values()+self.db169.values(): + size = (mode.getWidth(),mode.getHeight()) + if size not in self.all_resolutions: + self.all_resolutions.append(size) + + self.all_resolutions.sort() + + def load(self,filename): + fd = open(filename, 'r') + lines = fd.readlines() + fd.close() + + for line in lines: + if line[0] != "#" and line[0] != '/': + line = line.strip() + elements = line.split() + if line!="": + if len(elements) < 11 or string.lower(elements[0]) != "modeline": + print "Bad modeline found:",line + continue + name = elements[1][1:-1] + new_mode = ModeLine(elements) + + width = new_mode.getWidth() + height = new_mode.getHeight() + if self.aspectRatio(width, height)==ModeLine.ASPECT_4_3: + self.db[name] = new_mode + else: + self.db169[name] = new_mode + + if (width,height)==FALLBACK_RESOLUTION: + # We grab these modes and use them a fallbacks in the widescreen list. + self.db169[name] = new_mode + + @staticmethod + def aspectRatio(width,height): + ratio = float(width)/float(height) + # 4/3 is 1.333333 + # 16/9 is 1.777777 + # We will just consider anything below 1.45 to be standard. + if ratio < 1.45: + return ModeLine.ASPECT_4_3 + else: + return ModeLine.ASPECT_16_9 + + def getAvailableModes(self,monitor,aspect): + """ + Get the list of video modes that this monitor supports. + + Returns a list of modeline objects or None if the available modes for this monitor are unknown. + """ + if monitor.horizontalsync is None or monitor.verticalsync is None: + return None + + result = [] + + hsync_list = self._list_from_string(monitor.getHorizontalSync()) + vsync_list = self._list_from_string(monitor.getVerticalSync()) + + if aspect==ModeLine.ASPECT_4_3: + db = self.db + else: + db = self.db169 + + for modeline in db.values(): + if modeline.supports(hsync_list, vsync_list): + result.append(modeline) + return result + + def getAllResolutions(self): + return self.all_resolutions + + def _list_from_string(self,src): + l = [] + pieces = src.split(",") + for piece in pieces: + tmp = string.split(piece, "-") + if len(tmp) == 1: + l.append( (float(tmp[0].strip()), float(tmp[0].strip())) ) + else: + l.append( (float(tmp[0].strip()), float(tmp[1].strip())) ) + return l + +############################################################################ + +def ranges_to_string(array, length): + stringobj = "" + for i in range(length): + r = array[i] + if stringobj != "": + stringobj = stringobj + "," + if r[0] == r[1]: + stringobj = stringobj + repr(r[0]) + else: + stringobj = stringobj + repr(r[0]) + "-" + repr(r[1]) + return stringobj + + +def main(): + # FIXME: turns this into a real set of unit tests. + SetDataFileDir("ldetect-lst") + + #xs = XSetup() + #xs = XSetup('xorg.conf.test') + xs = XSetup(xorg_config_filename='bug_data/tonio_intel/xorg.conf', + debug_scan_pci_filename="bug_data/tonio_intel/PCIbus.txt") + print str(xs) + return + + #screen1 = xs.getGfxCards()[0].getScreens()[0] + #monitor_db = GetMonitorModelDB() + #new_model = monitor_db.getMonitorByName('Samsung SyncMaster 15GL') + #print new_model + #screen1.setMonitorModel(new_model) + + #screen2 = xs.getGfxCards()[0].getScreens()[1] + #screen2.setMonitorModel(new_model) + + print "getAvailableLayouts(): ",xs.getAvailableLayouts() + xs.getGfxCards()[0].setProprietaryDriver(True) + print str(xs) + xs.setLayout(XSetup.LAYOUT_CLONE) # XSetup.LAYOUT_DUAL. + print "getAvailableLayouts(): ",xs.getAvailableLayouts() + print str(xs) + + #gfxcard_db = GetGfxCardModelDB() + #new_gfxcard_model = gfxcard_db.getGfxCardModelByName('NVIDIA GeForce FX (generic)') + ##'ATI Radeon 8500' + ##'NVIDIA GeForce FX (generic)' + #print new_gfxcard_model + #gfx_card = xs.getGfxCards()[0] + #gfx_card.setProprietaryDriver(False) + #gfx_card.setGfxCardModel(new_gfxcard_model) + xs.writeXorgConfig('xorg.conf.test') + +if __name__=='__main__': + main() |