summaryrefslogtreecommitdiffstats
path: root/displayconfig/infimport.py
blob: f51475db102bb1ce65c7673321bfbd82927face7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#!/usr/bin/python
#
# Based on inf2mondb.py from RedHat
#
# originally by Matt Wilson <msw@redhat.com>
# option parsing and database comparison by Fred New
# ini parsing completely rewritten by Matt Domsch <Matt_Domsch@dell.com> 2006
#
# Copyright 2002 Red Hat, Inc.
# Copyright 2006 Dell, Inc.
# Copyright 2007 Sebastian Heinlein
#
# This software may be freely redistributed under the terms of the GNU
# library public license.
#
# You should have received a copy of the GNU Library Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""
Provides an importer for Microsoft Windows monitor descriptions

The code can be used as a python module for or as a script to add new monitor
definitions to a monitor database.

In code example: Read the list of monitors from an inf file.

import infimport
monitors = infimport.get_monitors_from_inf(PATH)

Script example: To check for monitors of an inf file that are not yet in the database.

./infimport.py MONITORS.inf /usr/share/hwdata/MonitorsDB
"""

import sys
import string
import re
import ConfigParser
import os

import logging

logging.basicConfig()
log = logging.getLogger("infimport")
#log.setLevel(logging.DEBUG)
log.setLevel(logging.INFO)

# this is a class to deal with various file line endings and leading whitespace
# converts all \r line endings to \n.
# It also strips leading whitespace.
# NOTE: be sure to always return _something_, even if it is just "\n", or we 
# break the file API.  (nothing == eof)
class myFile(object):
    def __init__(self, *args):
        self.fd = open(*args)

    def close(self):
        return self.fd.close()
    
    def readline(self, *args):
        line = self.fd.readline(*args)
        line = line.replace('\r', '\n')
        line = line.replace('\n\n', '\n')
        line = line.lstrip(" \t")
        return line


# we will use this to override default option parsing in ConfigParser to handle
# Microsoft-style "INI" files. (Which do not necessarily have " = value " after
# the option name
OPTCRE = re.compile(
        r'(?P<option>[^:=\s][^:=]*)'          # very permissive!
        r'\s*(?P<vi>[:=]{0,1})\s*'            # any number of space/tab,
                                              # optionally followed by
                                              # separator (either : or =)
                                              # optionally followed
                                              # by any # space/tab
        r'(?P<value>.*)$'                     # everything up to eol
        )

percentSplit = re.compile(r'%(?P<field>.*)%')
def _percent_to_string(ini, strings, name):
    mo = percentSplit.match(name)
    if (mo):
        field = mo.group('field')
        try:
            val = strings[field.lower()]
        except KeyError:
            return ""
        return val.strip(" '\"")
    return ""

def get_monitors_from_database(path):
    """Returns a dictonary of the found monitor models in the given 
       monitor models database"""
    monitors = {}
    try:
        mdb = open(path, 'r')
    except IOError, (errno, str):
        log.error("Unable to open %s: %s" % (path, str))
        return {}
    for line in mdb.readlines():
        if len(line.strip()) == 0 or line.startswith('#'):
            continue
        line_split = line.split(";")
        vendor = line_split[0].strip()
        name = line_split[1].strip()
        id = line_split[2].strip()
        if monitors.has_key((vendor, name, id)):
            log.warn("Duplicated entry: %s" % line)
        else:
            monitors[(vendor, name, id)] = line
    mdb.close()
    return monitors

def get_monitors_from_inf(path):
    """Returns a dictonary of the found monitor models in the given .inf file"""
    monitors = {}
    ini = ConfigParser.ConfigParser()
    # FIXME: perhaps could be done in a nicer way, but __builtins__ is a dict
    #        for imported modules
    #ini.optionxform = __builtins__.str
    ini.optionxform = type("")
    ini.OPTCRE = OPTCRE
    try:
        f = myFile(path)
        ini.readfp(f)
        f.close()
    except IOError, (errno, str):
        log.error("Unable to open %s: %s" % (path, str))
        sys.exit(1)

    # a dictionary of manufacturers we're looking at
    manufacturers = {}
    # a big fat dictionary of strings to use later on.
    strings = {}

    # This RE is for EISA info lines
    # %D5259A%=D5259A, Monitor\HWP0487
    monitor1Re = re.compile(r'.*,.*Monitor\\(?P<id>[^\s]*)')
    # This one is for legacy entries
    # %3020%     =PB3020,   MonID_PB3020
    monitor2Re = re.compile(r'.*,.*MonID_(?P<id>[^\s]*)')

    for section in ini.sections():
        if section.lower() == "manufacturer":
            for mfr in ini.options(section):
                # generate the vendor.arch funny entries
                manufacturer_values = string.split(ini.get(section, mfr), 
                                                       ',')
                manufacturers[manufacturer_values[0]] = mfr
                while len(manufacturer_values) > 1:
                    manufacturers["%s.%s" % (manufacturer_values[0], 
                                             manufacturer_values[-1])] = mfr
                    manufacturer_values = manufacturer_values[0:-1]

        elif section.lower() == "strings":
            for key in ini.options(section):
                strings[key.lower()] = string.strip(ini.get(section, key))

    for mfr in manufacturers.keys():
        if ini.has_section(mfr):
            monitor_vendor_name = manufacturers[mfr]
            for monitor_name in ini.options(mfr):
                v = ini.get(mfr, monitor_name)
                v = v.split(',')
                install_key = v[0].strip()

                line = ini.get(mfr, monitor_name)
                # Find monitor inf IDs and EISA ids

                edid = "0"
                mo = monitor1Re.match(line)
                if mo:
                    edid = mo.group('id')
                else:
                    mo = monitor2Re.match(line)
                    if mo:
                        edid = mo.group('id').strip()

                #if self.monitors.has_key(edid.lower()):
                #   continue

                if ini.has_section(install_key):
                    line = ini.get(install_key, "AddReg")
                    if line:
                        sline = line.split(',')
                        registry = sline[0]
                        try:
                            resolution = sline[1]
                        except IndexError:
                            resolution = ""
                        try:
                            dpms = sline[2]
                        except IndexError:
                            dpms = ""

                        if ini.has_section(registry):
                            for line in ini.options(registry):
                                if string.find(line, 'HKR,"MODES') >= 0:
                                    sline = line.split('"')
                                    try:
                                        syncline = sline[3]
                                    except IndexError:
                                        syncline = ","
                                    syncline = syncline.split(',')
                                    hsync = syncline[0].strip()
                                    vsync = syncline[1].strip()

                                    vendor_clear = _percent_to_string(ini,
                                            strings, monitor_vendor_name)
                                    monitor_clear = _percent_to_string(ini,
                                            strings, monitor_name)

                                    output = "%s; %s; %s; %s; %s" % \
                                             (vendor_clear, monitor_clear,
                                              edid, hsync, vsync)
                                    if dpms.lower().strip() == "dpms":
                                        output = output + "; 1"

                                    if not monitors.has_key((vendor_clear,
                                            monitor_clear, edid.lower())):
                                        log.debug("added %s" % output)
                                        monitors[(vendor_clear,
                                                  monitor_clear,
                                                  edid.lower())] = output
                                    else:
                                        log.warn("duplicated entry %s" % output)
    return monitors

def write_monitors_to_file(monitors, path):
    """Writes monitors as a monitor models database"""
    try:
        if os.path.exists(path):
            os.remove(path)
        mdb = open(path, 'w')
        mdb.writelines(map(lambda l: "%s\n" % l, monitors.values()))
        mdb.close()
    except IOError, (errno, str):
        log.error("Unable to write %s: %s" % (path, str))
        return False

def append_monitors_to_file(monitors, path):
    """Appends monitors to a monitor models database"""
    try:
        if os.path.exists(path):
            os.remove(path)
        mdb = open(path, 'a')
        mdb.writelines(map(lambda l: "%s\n" % l, monitors.values()))
        mdb.close()
    except IOError, (errno, str):
        log.error("Unable to write %s: %s" % (path, str))
        return False

if __name__ == "__main__":
    from optparse import OptionParser
    import sys

    parser = OptionParser()
    parser.add_option("-a", "--append",
                      action="store_true", dest="append",
                      help="Append new models to the database")
    parser.add_option("-o", "--output",
                      default=None,
                      action="store", type="string", dest="output",
                      help="Write changes to an alternative file")
    parser.usage = "%prog [options] INF_FILE [MONITOR_DATABASE]"
    (options, args) = parser.parse_args()

    if len(args) == 2:
        # continue with normal operation
        pass
    elif len(args) == 1:
        # jsut print the monitors from the given inf file
        monitors_inf = get_monitors_from_inf(args[0])
        for mon in monitors_inf.values():
            print "%s" % mon
        sys.exit()
    else:
        parser.error("You have to specify an .inf file that contains the "
                     "monitor models that you want to add and a "
                     "monitor model database")

    monitors_inf = get_monitors_from_inf(args[0])
    monitors_db = get_monitors_from_database(args[1])

    monitors_new = {}
    for mon in monitors_inf.keys():
        if not monitors_db.has_key(mon):
            log.info("New monitor: %s" % monitors_inf[mon])
            monitors_new[mon] = monitors_inf[mon]

    if options.append:
        if options.output:
            append_monitors_to_file(monitors_new, options.output)
        else:
            append_monitors_to_file(new_monitors, args[1])