blob: 47a47c68a74b7e1affe1b6ccec2ab704a7266d43 [file] [log] [blame]
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +00001# Microsoft Installer Library
2# (C) 2003 Martin v. Loewis
3
4import win32com.client.gencache
5import win32com.client
6import pythoncom, pywintypes
7from win32com.client import constants
Guido van Rossum360e4b82007-05-14 22:51:27 +00008import re, string, os, sets, glob, subprocess, sys, _winreg, struct
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +00009
Martin v. Löwis104c46b2004-09-07 15:37:26 +000010try:
11 basestring
12except NameError:
13 basestring = (str, unicode)
14
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +000015# Partially taken from Wine
16datasizemask= 0x00ff
17type_valid= 0x0100
18type_localizable= 0x0200
19
20typemask= 0x0c00
21type_long= 0x0000
22type_short= 0x0400
23type_string= 0x0c00
24type_binary= 0x0800
25
26type_nullable= 0x1000
27type_key= 0x2000
28# XXX temporary, localizable?
29knownbits = datasizemask | type_valid | type_localizable | \
30 typemask | type_nullable | type_key
31
32# Summary Info Property IDs
33PID_CODEPAGE=1
34PID_TITLE=2
35PID_SUBJECT=3
36PID_AUTHOR=4
37PID_KEYWORDS=5
38PID_COMMENTS=6
39PID_TEMPLATE=7
40PID_LASTAUTHOR=8
41PID_REVNUMBER=9
42PID_LASTPRINTED=11
43PID_CREATE_DTM=12
44PID_LASTSAVE_DTM=13
45PID_PAGECOUNT=14
46PID_WORDCOUNT=15
47PID_CHARCOUNT=16
48PID_APPNAME=18
49PID_SECURITY=19
50
51def reset():
52 global _directories
53 _directories = sets.Set()
54
55def EnsureMSI():
56 win32com.client.gencache.EnsureModule('{000C1092-0000-0000-C000-000000000046}', 1033, 1, 0)
57
58def EnsureMSM():
59 try:
60 win32com.client.gencache.EnsureModule('{0ADDA82F-2C26-11D2-AD65-00A0C9AF11A6}', 0, 1, 0)
61 except pywintypes.com_error:
62 win32com.client.gencache.EnsureModule('{0ADDA82F-2C26-11D2-AD65-00A0C9AF11A6}', 0, 2, 0)
63
64_Installer=None
65def MakeInstaller():
66 global _Installer
67 if _Installer is None:
68 EnsureMSI()
69 _Installer = win32com.client.Dispatch('WindowsInstaller.Installer',
70 resultCLSID='{000C1090-0000-0000-C000-000000000046}')
71 return _Installer
72
73_Merge=None
74def MakeMerge2():
75 global _Merge
76 if _Merge is None:
77 EnsureMSM()
78 _Merge = win32com.client.Dispatch("Msm.Merge2.1")
79 return _Merge
80
81class Table:
82 def __init__(self, name):
83 self.name = name
84 self.fields = []
85
86 def add_field(self, index, name, type):
87 self.fields.append((index,name,type))
88
89 def sql(self):
90 fields = []
91 keys = []
92 self.fields.sort()
93 fields = [None]*len(self.fields)
94 for index, name, type in self.fields:
95 index -= 1
96 unk = type & ~knownbits
97 if unk:
Collin Winter6afaeb72007-08-03 17:06:41 +000098 print("%s.%s unknown bits %x" % (self.name, name, unk))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +000099 size = type & datasizemask
100 dtype = type & typemask
101 if dtype == type_string:
102 if size:
103 tname="CHAR(%d)" % size
104 else:
105 tname="CHAR"
106 elif dtype == type_short:
107 assert size==2
108 tname = "SHORT"
109 elif dtype == type_long:
110 assert size==4
111 tname="LONG"
112 elif dtype == type_binary:
113 assert size==0
114 tname="OBJECT"
115 else:
116 tname="unknown"
Collin Winter6afaeb72007-08-03 17:06:41 +0000117 print("%s.%sunknown integer type %d" % (self.name, name, size))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000118 if type & type_nullable:
119 flags = ""
120 else:
121 flags = " NOT NULL"
122 if type & type_localizable:
123 flags += " LOCALIZABLE"
124 fields[index] = "`%s` %s%s" % (name, tname, flags)
125 if type & type_key:
126 keys.append("`%s`" % name)
127 fields = ", ".join(fields)
128 keys = ", ".join(keys)
129 return "CREATE TABLE %s (%s PRIMARY KEY %s)" % (self.name, fields, keys)
130
131 def create(self, db):
132 v = db.OpenView(self.sql())
133 v.Execute(None)
134 v.Close()
135
136class Binary:
137 def __init__(self, fname):
138 self.name = fname
139 def __repr__(self):
140 return 'msilib.Binary(os.path.join(dirname,"%s"))' % self.name
141
142def gen_schema(destpath, schemapath):
143 d = MakeInstaller()
144 schema = d.OpenDatabase(schemapath,
145 win32com.client.constants.msiOpenDatabaseModeReadOnly)
146
147 # XXX ORBER BY
148 v=schema.OpenView("SELECT * FROM _Columns")
149 curtable=None
150 tables = []
151 v.Execute(None)
152 f = open(destpath, "wt")
153 f.write("from msilib import Table\n")
154 while 1:
155 r=v.Fetch()
156 if not r:break
157 name=r.StringData(1)
158 if curtable != name:
159 f.write("\n%s = Table('%s')\n" % (name,name))
160 curtable = name
161 tables.append(name)
162 f.write("%s.add_field(%d,'%s',%d)\n" %
163 (name, r.IntegerData(2), r.StringData(3), r.IntegerData(4)))
164 v.Close()
165
166 f.write("\ntables=[%s]\n\n" % (", ".join(tables)))
167
168 # Fill the _Validation table
169 f.write("_Validation_records = [\n")
170 v = schema.OpenView("SELECT * FROM _Validation")
171 v.Execute(None)
172 while 1:
173 r = v.Fetch()
174 if not r:break
175 # Table, Column, Nullable
176 f.write("(%s,%s,%s," %
177 (`r.StringData(1)`, `r.StringData(2)`, `r.StringData(3)`))
178 def put_int(i):
179 if r.IsNull(i):f.write("None, ")
180 else:f.write("%d," % r.IntegerData(i))
181 def put_str(i):
182 if r.IsNull(i):f.write("None, ")
183 else:f.write("%s," % `r.StringData(i)`)
184 put_int(4) # MinValue
185 put_int(5) # MaxValue
186 put_str(6) # KeyTable
187 put_int(7) # KeyColumn
188 put_str(8) # Category
189 put_str(9) # Set
190 put_str(10)# Description
191 f.write("),\n")
192 f.write("]\n\n")
193
Tim Peters94607dd2004-08-22 19:42:56 +0000194 f.close()
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000195
196def gen_sequence(destpath, msipath):
197 dir = os.path.dirname(destpath)
198 d = MakeInstaller()
199 seqmsi = d.OpenDatabase(msipath,
200 win32com.client.constants.msiOpenDatabaseModeReadOnly)
201
202 v = seqmsi.OpenView("SELECT * FROM _Tables");
203 v.Execute(None)
204 f = open(destpath, "w")
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000205 f.write("import msilib,os;dirname=os.path.dirname(__file__)\n")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000206 tables = []
207 while 1:
208 r = v.Fetch()
209 if not r:break
210 table = r.StringData(1)
211 tables.append(table)
212 f.write("%s = [\n" % table)
213 v1 = seqmsi.OpenView("SELECT * FROM `%s`" % table)
214 v1.Execute(None)
215 info = v1.ColumnInfo(constants.msiColumnInfoTypes)
216 while 1:
217 r = v1.Fetch()
218 if not r:break
219 rec = []
220 for i in range(1,r.FieldCount+1):
221 if r.IsNull(i):
222 rec.append(None)
223 elif info.StringData(i)[0] in "iI":
224 rec.append(r.IntegerData(i))
225 elif info.StringData(i)[0] in "slSL":
226 rec.append(r.StringData(i))
227 elif info.StringData(i)[0]=="v":
228 size = r.DataSize(i)
229 bytes = r.ReadStream(i, size, constants.msiReadStreamBytes)
230 bytes = bytes.encode("latin-1") # binary data represented "as-is"
231 if table == "Binary":
232 fname = rec[0]+".bin"
233 open(os.path.join(dir,fname),"wb").write(bytes)
234 rec.append(Binary(fname))
235 else:
236 rec.append(bytes)
237 else:
238 raise "Unsupported column type", info.StringData(i)
239 f.write(repr(tuple(rec))+",\n")
240 v1.Close()
241 f.write("]\n\n")
242 v.Close()
243 f.write("tables=%s\n" % repr(map(str,tables)))
244 f.close()
245
246class _Unspecified:pass
247def change_sequence(seq, action, seqno=_Unspecified, cond = _Unspecified):
248 "Change the sequence number of an action in a sequence list"
249 for i in range(len(seq)):
250 if seq[i][0] == action:
251 if cond is _Unspecified:
252 cond = seq[i][1]
253 if seqno is _Unspecified:
254 seqno = seq[i][2]
255 seq[i] = (action, cond, seqno)
256 return
Collin Wintera817e582007-08-22 23:05:06 +0000257 raise ValueError("Action not found in sequence")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000258
259def add_data(db, table, values):
260 d = MakeInstaller()
261 v = db.OpenView("SELECT * FROM `%s`" % table)
262 count = v.ColumnInfo(0).FieldCount
263 r = d.CreateRecord(count)
264 for value in values:
265 assert len(value) == count, value
266 for i in range(count):
267 field = value[i]
268 if isinstance(field, (int, long)):
269 r.SetIntegerData(i+1,field)
270 elif isinstance(field, basestring):
271 r.SetStringData(i+1,field)
272 elif field is None:
273 pass
274 elif isinstance(field, Binary):
275 r.SetStream(i+1, field.name)
276 else:
Collin Wintera817e582007-08-22 23:05:06 +0000277 raise TypeError("Unsupported type %s" % field.__class__.__name__)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000278 v.Modify(win32com.client.constants.msiViewModifyInsert, r)
279 r.ClearData()
280 v.Close()
281
282def add_stream(db, name, path):
283 d = MakeInstaller()
284 v = db.OpenView("INSERT INTO _Streams (Name, Data) VALUES ('%s', ?)" % name)
285 r = d.CreateRecord(1)
286 r.SetStream(1, path)
287 v.Execute(r)
288 v.Close()
289
290def init_database(name, schema,
291 ProductName, ProductCode, ProductVersion,
292 Manufacturer):
293 try:
294 os.unlink(name)
295 except OSError:
296 pass
297 ProductCode = ProductCode.upper()
298 d = MakeInstaller()
299 # Create the database
300 db = d.OpenDatabase(name,
301 win32com.client.constants.msiOpenDatabaseModeCreate)
302 # Create the tables
303 for t in schema.tables:
304 t.create(db)
305 # Fill the validation table
306 add_data(db, "_Validation", schema._Validation_records)
307 # Initialize the summary information, allowing atmost 20 properties
308 si = db.GetSummaryInformation(20)
309 si.SetProperty(PID_TITLE, "Installation Database")
310 si.SetProperty(PID_SUBJECT, ProductName)
311 si.SetProperty(PID_AUTHOR, Manufacturer)
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000312 si.SetProperty(PID_TEMPLATE, msi_type)
Martin v. Löwis1e3a2642004-09-10 11:55:32 +0000313 si.SetProperty(PID_REVNUMBER, gen_uuid())
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000314 si.SetProperty(PID_WORDCOUNT, 2) # long file names, compressed, original media
315 si.SetProperty(PID_PAGECOUNT, 200)
316 si.SetProperty(PID_APPNAME, "Python MSI Library")
317 # XXX more properties
318 si.Persist()
319 add_data(db, "Property", [
320 ("ProductName", ProductName),
321 ("ProductCode", ProductCode),
322 ("ProductVersion", ProductVersion),
323 ("Manufacturer", Manufacturer),
324 ("ProductLanguage", "1033")])
325 db.Commit()
326 return db
327
328def add_tables(db, module):
329 for table in module.tables:
330 add_data(db, table, getattr(module, table))
331
332def make_id(str):
333 #str = str.replace(".", "_") # colons are allowed
334 str = str.replace(" ", "_")
335 str = str.replace("-", "_")
336 if str[0] in string.digits:
337 str = "_"+str
338 assert re.match("^[A-Za-z_][A-Za-z0-9_.]*$", str), "FILE"+str
339 return str
340
341def gen_uuid():
342 return str(pythoncom.CreateGuid())
343
344class CAB:
345 def __init__(self, name):
346 self.name = name
347 self.file = open(name+".txt", "wt")
348 self.filenames = sets.Set()
349 self.index = 0
350
351 def gen_id(self, dir, file):
352 logical = _logical = make_id(file)
353 pos = 1
354 while logical in self.filenames:
355 logical = "%s.%d" % (_logical, pos)
356 pos += 1
357 self.filenames.add(logical)
358 return logical
359
360 def append(self, full, file, logical = None):
361 if os.path.isdir(full):
362 return
363 if not logical:
364 logical = self.gen_id(dir, file)
365 self.index += 1
366 if full.find(" ")!=-1:
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000367 self.file.write('"%s" %s\n' % (full, logical))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000368 else:
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000369 self.file.write('%s %s\n' % (full, logical))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000370 return self.index, logical
371
372 def commit(self, db):
373 self.file.close()
374 try:
375 os.unlink(self.name+".cab")
376 except OSError:
377 pass
378 for k, v in [(r"Software\Microsoft\VisualStudio\7.1\Setup\VS", "VS7CommonBinDir"),
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000379 (r"Software\Microsoft\VisualStudio\8.0\Setup\VS", "VS7CommonBinDir"),
380 (r"Software\Microsoft\VisualStudio\9.0\Setup\VS", "VS7CommonBinDir"),
381 (r"Software\Microsoft\Win32SDK\Directories", "Install Dir"),
382 ]:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000383 try:
384 key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, k)
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000385 dir = _winreg.QueryValueEx(key, v)[0]
386 _winreg.CloseKey(key)
387 except (WindowsError, IndexError):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000388 continue
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000389 cabarc = os.path.join(dir, r"Bin", "cabarc.exe")
390 if not os.path.exists(cabarc):
391 continue
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000392 break
393 else:
Collin Winter6afaeb72007-08-03 17:06:41 +0000394 print("WARNING: cabarc.exe not found in registry")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000395 cabarc = "cabarc.exe"
Guido van Rossum360e4b82007-05-14 22:51:27 +0000396 cmd = r'"%s" -m lzx:21 n %s.cab @%s.txt' % (cabarc, self.name, self.name)
397 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000398 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
399 for line in p.stdout:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000400 if line.startswith(" -- adding "):
401 sys.stdout.write(".")
402 else:
403 sys.stdout.write(line)
404 sys.stdout.flush()
405 if not os.path.exists(self.name+".cab"):
Collin Wintera817e582007-08-22 23:05:06 +0000406 raise IOError("cabarc failed")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000407 add_data(db, "Media",
408 [(1, self.index, None, "#"+self.name, None, None)])
409 add_stream(db, self.name, self.name+".cab")
410 os.unlink(self.name+".txt")
411 os.unlink(self.name+".cab")
412 db.Commit()
413
414_directories = sets.Set()
415class Directory:
416 def __init__(self, db, cab, basedir, physical, _logical, default, componentflags=None):
417 """Create a new directory in the Directory table. There is a current component
418 at each point in time for the directory, which is either explicitly created
419 through start_component, or implicitly when files are added for the first
420 time. Files are added into the current component, and into the cab file.
421 To create a directory, a base directory object needs to be specified (can be
422 None), the path to the physical directory, and a logical directory name.
423 Default specifies the DefaultDir slot in the directory table. componentflags
424 specifies the default flags that new components get."""
425 index = 1
426 _logical = make_id(_logical)
427 logical = _logical
428 while logical in _directories:
429 logical = "%s%d" % (_logical, index)
430 index += 1
431 _directories.add(logical)
432 self.db = db
433 self.cab = cab
434 self.basedir = basedir
435 self.physical = physical
436 self.logical = logical
437 self.component = None
438 self.short_names = sets.Set()
439 self.ids = sets.Set()
440 self.keyfiles = {}
441 self.componentflags = componentflags
442 if basedir:
443 self.absolute = os.path.join(basedir.absolute, physical)
444 blogical = basedir.logical
445 else:
446 self.absolute = physical
447 blogical = None
448 add_data(db, "Directory", [(logical, blogical, default)])
449
Martin v. Löwis141f41a2005-03-15 00:39:40 +0000450 def start_component(self, component = None, feature = None, flags = None, keyfile = None, uuid=None):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000451 """Add an entry to the Component table, and make this component the current for this
452 directory. If no component name is given, the directory name is used. If no feature
453 is given, the current feature is used. If no flags are given, the directory's default
454 flags are used. If no keyfile is given, the KeyPath is left null in the Component
455 table."""
456 if flags is None:
457 flags = self.componentflags
Martin v. Löwis141f41a2005-03-15 00:39:40 +0000458 if uuid is None:
459 uuid = gen_uuid()
460 else:
461 uuid = uuid.upper()
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000462 if component is None:
463 component = self.logical
464 self.component = component
465 if Win64:
466 flags |= 256
467 if keyfile:
468 keyid = self.cab.gen_id(self.absolute, keyfile)
469 self.keyfiles[keyfile] = keyid
470 else:
471 keyid = None
472 add_data(self.db, "Component",
473 [(component, uuid, self.logical, flags, None, keyid)])
474 if feature is None:
475 feature = current_feature
476 add_data(self.db, "FeatureComponents",
477 [(feature.id, component)])
478
479 def make_short(self, file):
480 parts = file.split(".")
481 if len(parts)>1:
482 suffix = parts[-1].upper()
483 else:
484 suffix = None
485 prefix = parts[0].upper()
486 if len(prefix) <= 8 and (not suffix or len(suffix)<=3):
487 if suffix:
488 file = prefix+"."+suffix
489 else:
490 file = prefix
491 assert file not in self.short_names
492 else:
493 prefix = prefix[:6]
494 if suffix:
495 suffix = suffix[:3]
496 pos = 1
497 while 1:
498 if suffix:
499 file = "%s~%d.%s" % (prefix, pos, suffix)
500 else:
501 file = "%s~%d" % (prefix, pos)
502 if file not in self.short_names: break
503 pos += 1
504 assert pos < 10000
505 if pos in (10, 100, 1000):
506 prefix = prefix[:-1]
507 self.short_names.add(file)
508 assert not re.search(r'[\?|><:/*"+,;=\[\]]', file) # restrictions on short names
509 return file
510
511 def add_file(self, file, src=None, version=None, language=None):
512 """Add a file to the current component of the directory, starting a new one
513 one if there is no current component. By default, the file name in the source
514 and the file table will be identical. If the src file is specified, it is
515 interpreted relative to the current directory. Optionally, a version and a
516 language can be specified for the entry in the File table."""
517 if not self.component:
518 self.start_component(self.logical, current_feature)
519 if not src:
520 # Allow relative paths for file if src is not specified
521 src = file
522 file = os.path.basename(file)
523 absolute = os.path.join(self.absolute, src)
524 assert not re.search(r'[\?|><:/*]"', file) # restrictions on long names
525 if self.keyfiles.has_key(file):
526 logical = self.keyfiles[file]
527 else:
528 logical = None
529 sequence, logical = self.cab.append(absolute, file, logical)
530 assert logical not in self.ids
531 self.ids.add(logical)
532 short = self.make_short(file)
533 full = "%s|%s" % (short, file)
534 filesize = os.stat(absolute).st_size
535 # constants.msidbFileAttributesVital
536 # Compressed omitted, since it is the database default
537 # could add r/o, system, hidden
Tim Peters94607dd2004-08-22 19:42:56 +0000538 attributes = 512
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000539 add_data(self.db, "File",
540 [(logical, self.component, full, filesize, version,
541 language, attributes, sequence)])
542 if not version:
543 # Add hash if the file is not versioned
544 filehash = MakeInstaller().FileHash(absolute, 0)
545 add_data(self.db, "MsiFileHash",
546 [(logical, 0, filehash.IntegerData(1),
547 filehash.IntegerData(2), filehash.IntegerData(3),
548 filehash.IntegerData(4))])
549 # Automatically remove .pyc/.pyo files on uninstall (2)
550 # XXX: adding so many RemoveFile entries makes installer unbelievably
551 # slow. So instead, we have to use wildcard remove entries
552 # if file.endswith(".py"):
553 # add_data(self.db, "RemoveFile",
554 # [(logical+"c", self.component, "%sC|%sc" % (short, file),
555 # self.logical, 2),
556 # (logical+"o", self.component, "%sO|%so" % (short, file),
557 # self.logical, 2)])
558
559 def glob(self, pattern, exclude = None):
560 """Add a list of files to the current component as specified in the
561 glob pattern. Individual files can be excluded in the exclude list."""
562 files = glob.glob1(self.absolute, pattern)
563 for f in files:
564 if exclude and f in exclude: continue
565 self.add_file(f)
566 return files
567
568 def remove_pyc(self):
569 "Remove .pyc/.pyo files on uninstall"
570 add_data(self.db, "RemoveFile",
571 [(self.component+"c", self.component, "*.pyc", self.logical, 2),
572 (self.component+"o", self.component, "*.pyo", self.logical, 2)])
573
574class Feature:
575 def __init__(self, db, id, title, desc, display, level = 1,
576 parent=None, directory = None, attributes=0):
577 self.id = id
578 if parent:
579 parent = parent.id
580 add_data(db, "Feature",
581 [(id, parent, title, desc, display,
582 level, directory, attributes)])
583 def set_current(self):
584 global current_feature
585 current_feature = self
586
587class Control:
588 def __init__(self, dlg, name):
589 self.dlg = dlg
590 self.name = name
591
592 def event(self, ev, arg, cond = "1", order = None):
593 add_data(self.dlg.db, "ControlEvent",
594 [(self.dlg.name, self.name, ev, arg, cond, order)])
595
596 def mapping(self, ev, attr):
597 add_data(self.dlg.db, "EventMapping",
598 [(self.dlg.name, self.name, ev, attr)])
599
600 def condition(self, action, condition):
601 add_data(self.dlg.db, "ControlCondition",
602 [(self.dlg.name, self.name, action, condition)])
603
604class RadioButtonGroup(Control):
605 def __init__(self, dlg, name, property):
606 self.dlg = dlg
607 self.name = name
608 self.property = property
609 self.index = 1
610
611 def add(self, name, x, y, w, h, text, value = None):
612 if value is None:
613 value = name
614 add_data(self.dlg.db, "RadioButton",
615 [(self.property, self.index, value,
616 x, y, w, h, text, None)])
617 self.index += 1
618
619class Dialog:
620 def __init__(self, db, name, x, y, w, h, attr, title, first, default, cancel):
621 self.db = db
622 self.name = name
623 self.x, self.y, self.w, self.h = x,y,w,h
624 add_data(db, "Dialog", [(name, x,y,w,h,attr,title,first,default,cancel)])
625
626 def control(self, name, type, x, y, w, h, attr, prop, text, next, help):
627 add_data(self.db, "Control",
628 [(self.name, name, type, x, y, w, h, attr, prop, text, next, help)])
629 return Control(self, name)
630
631 def text(self, name, x, y, w, h, attr, text):
632 return self.control(name, "Text", x, y, w, h, attr, None,
633 text, None, None)
634
635 def bitmap(self, name, x, y, w, h, text):
636 return self.control(name, "Bitmap", x, y, w, h, 1, None, text, None, None)
Tim Peters94607dd2004-08-22 19:42:56 +0000637
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000638 def line(self, name, x, y, w, h):
639 return self.control(name, "Line", x, y, w, h, 1, None, None, None, None)
640
641 def pushbutton(self, name, x, y, w, h, attr, text, next):
642 return self.control(name, "PushButton", x, y, w, h, attr, None, text, next, None)
643
644 def radiogroup(self, name, x, y, w, h, attr, prop, text, next):
645 add_data(self.db, "Control",
646 [(self.name, name, "RadioButtonGroup",
647 x, y, w, h, attr, prop, text, next, None)])
648 return RadioButtonGroup(self, name, prop)
649
650 def checkbox(self, name, x, y, w, h, attr, prop, text, next):
Tim Peters94607dd2004-08-22 19:42:56 +0000651 return self.control(name, "CheckBox", x, y, w, h, attr, prop, text, next, None)
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000652
653def pe_type(path):
Martin v. Löwisd149c212006-03-05 13:52:20 +0000654 header = open(path, "rb").read(1000)
655 # offset of PE header is at offset 0x3c
656 pe_offset = struct.unpack("<i", header[0x3c:0x40])[0]
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000657 assert header[pe_offset:pe_offset+4] == "PE\0\0"
658 machine = struct.unpack("<H", header[pe_offset+4:pe_offset+6])[0]
659 return machine
660
661def set_arch_from_file(path):
662 global msi_type, Win64, arch_ext
663 machine = pe_type(path)
664 if machine == 0x14c:
665 # i386
666 msi_type = "Intel"
667 Win64 = 0
668 arch_ext = ''
669 elif machine == 0x200:
670 # Itanium
671 msi_type = "Intel64"
672 Win64 = 1
673 arch_ext = '.ia64'
674 elif machine == 0x8664:
675 # AMD64
676 msi_type = "x64"
677 Win64 = 1
678 arch_ext = '.amd64'
679 else:
Collin Wintera817e582007-08-22 23:05:06 +0000680 raise ValueError("Unsupported architecture")
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000681 msi_type += ";1033"