| Jack Jansen | 40775ba | 1995-07-17 11:42:23 +0000 | [diff] [blame] | 1 | """Tools for use in AppleEvent clients and servers: | 
 | 2 | conversion between AE types and python types | 
 | 3 |  | 
 | 4 | pack(x) converts a Python object to an AEDesc object | 
 | 5 | unpack(desc) does the reverse | 
 | 6 | coerce(x, wanted_sample) coerces a python object to another python object | 
 | 7 | """ | 
 | 8 |  | 
 | 9 | # | 
 | 10 | # This code was originally written by Guido, and modified/extended by Jack | 
 | 11 | # to include the various types that were missing. The reference used is | 
 | 12 | # Apple Event Registry, chapter 9. | 
 | 13 | # | 
 | 14 |  | 
 | 15 | import struct | 
 | 16 | import string | 
 | 17 | import types | 
 | 18 | from string import strip | 
 | 19 | from types import * | 
 | 20 | import AE | 
 | 21 | from AppleEvents import * | 
| Jack Jansen | 40775ba | 1995-07-17 11:42:23 +0000 | [diff] [blame] | 22 | import MacOS | 
 | 23 | import macfs | 
 | 24 | import StringIO | 
 | 25 | import aetypes | 
 | 26 | from aetypes import mkenum, mktype | 
 | 27 |  | 
 | 28 | # These ones seem to be missing from AppleEvents | 
 | 29 | # (they're in AERegistry.h) | 
 | 30 |  | 
 | 31 | #typeColorTable = 'clrt' | 
 | 32 | #typeDrawingArea = 'cdrw' | 
 | 33 | #typePixelMap = 'cpix' | 
 | 34 | #typePixelMapMinus = 'tpmm' | 
 | 35 | #typeRotation = 'trot' | 
 | 36 | #typeTextStyles = 'tsty' | 
 | 37 | #typeStyledText = 'STXT' | 
 | 38 | #typeAEText = 'tTXT' | 
 | 39 | #typeEnumeration = 'enum' | 
 | 40 |  | 
 | 41 | # | 
 | 42 | # Some AE types are immedeately coerced into something | 
 | 43 | # we like better (and which is equivalent) | 
 | 44 | # | 
 | 45 | unpacker_coercions = { | 
| Jack Jansen | 49fc666 | 1999-02-10 09:51:35 +0000 | [diff] [blame] | 46 | 	typeComp : typeFloat, | 
| Jack Jansen | 40775ba | 1995-07-17 11:42:23 +0000 | [diff] [blame] | 47 | 	typeColorTable : typeAEList, | 
 | 48 | 	typeDrawingArea : typeAERecord, | 
| Jack Jansen | 49fc666 | 1999-02-10 09:51:35 +0000 | [diff] [blame] | 49 | 	typeFixed : typeFloat, | 
 | 50 | 	typeExtended : typeFloat, | 
| Jack Jansen | 40775ba | 1995-07-17 11:42:23 +0000 | [diff] [blame] | 51 | 	typePixelMap : typeAERecord, | 
 | 52 | 	typeRotation : typeAERecord, | 
 | 53 | 	typeStyledText : typeAERecord, | 
 | 54 | 	typeTextStyles : typeAERecord, | 
 | 55 | }; | 
 | 56 |  | 
 | 57 | # | 
 | 58 | # Some python types we need in the packer: | 
 | 59 | # | 
 | 60 | AEDescType = type(AE.AECreateDesc('TEXT', '')) | 
 | 61 | _sample_fss = macfs.FSSpec(':') | 
 | 62 | _sample_alias = _sample_fss.NewAliasMinimal() | 
 | 63 | FSSType = type(_sample_fss) | 
 | 64 | AliasType = type(_sample_alias) | 
 | 65 |  | 
 | 66 | def pack(x, forcetype = None): | 
 | 67 | 	"""Pack a python object into an AE descriptor""" | 
 | 68 | 	 | 
 | 69 | 	if forcetype: | 
 | 70 | 		if type(x) is StringType: | 
 | 71 | 			return AE.AECreateDesc(forcetype, x) | 
 | 72 | 		else: | 
 | 73 | 			return pack(x).AECoerceDesc(forcetype) | 
 | 74 | 			 | 
 | 75 | 	if x == None: | 
 | 76 | 		return AE.AECreateDesc('null', '') | 
 | 77 | 		 | 
 | 78 | 	t = type(x) | 
 | 79 | 	if t == AEDescType: | 
 | 80 | 		return x | 
 | 81 | 	if t == FSSType: | 
 | 82 | 		return AE.AECreateDesc('fss ', x.data) | 
 | 83 | 	if t == AliasType: | 
 | 84 | 		return AE.AECreateDesc('alis', x.data) | 
 | 85 | 	if t == IntType: | 
 | 86 | 		return AE.AECreateDesc('long', struct.pack('l', x)) | 
 | 87 | 	if t == FloatType: | 
| Jack Jansen | 49fc666 | 1999-02-10 09:51:35 +0000 | [diff] [blame] | 88 | 		return AE.AECreateDesc('doub', struct.pack('d', x)) | 
| Jack Jansen | 40775ba | 1995-07-17 11:42:23 +0000 | [diff] [blame] | 89 | 	if t == StringType: | 
 | 90 | 		return AE.AECreateDesc('TEXT', x) | 
 | 91 | 	if t == ListType: | 
 | 92 | 		list = AE.AECreateList('', 0) | 
 | 93 | 		for item in x: | 
 | 94 | 			list.AEPutDesc(0, pack(item)) | 
 | 95 | 		return list | 
 | 96 | 	if t == DictionaryType: | 
 | 97 | 		record = AE.AECreateList('', 1) | 
 | 98 | 		for key, value in x.items(): | 
 | 99 | 			record.AEPutParamDesc(key, pack(value)) | 
 | 100 | 		return record | 
 | 101 | 	if t == InstanceType and hasattr(x, '__aepack__'): | 
 | 102 | 		return x.__aepack__() | 
 | 103 | 	return AE.AECreateDesc('TEXT', repr(x)) # Copout | 
 | 104 |  | 
 | 105 | def unpack(desc): | 
 | 106 | 	"""Unpack an AE descriptor to a python object""" | 
 | 107 | 	t = desc.type | 
 | 108 | 	 | 
 | 109 | 	if unpacker_coercions.has_key(t): | 
 | 110 | 		desc = desc.AECoerceDesc(unpacker_coercions[t]) | 
| Jack Jansen | 756a69f | 1997-08-08 15:00:03 +0000 | [diff] [blame] | 111 | 		t = desc.type # This is a guess by Jack.... | 
| Jack Jansen | 40775ba | 1995-07-17 11:42:23 +0000 | [diff] [blame] | 112 | 	 | 
 | 113 | 	if t == typeAEList: | 
 | 114 | 		l = [] | 
 | 115 | 		for i in range(desc.AECountItems()): | 
 | 116 | 			keyword, item = desc.AEGetNthDesc(i+1, '****') | 
 | 117 | 			l.append(unpack(item)) | 
 | 118 | 		return l | 
 | 119 | 	if t == typeAERecord: | 
 | 120 | 		d = {} | 
 | 121 | 		for i in range(desc.AECountItems()): | 
 | 122 | 			keyword, item = desc.AEGetNthDesc(i+1, '****') | 
 | 123 | 			d[keyword] = unpack(item) | 
 | 124 | 		return d | 
 | 125 | 	if t == typeAEText: | 
 | 126 | 		record = desc.AECoerceDesc('reco') | 
 | 127 | 		return mkaetext(unpack(record)) | 
 | 128 | 	if t == typeAlias: | 
 | 129 | 		return macfs.RawAlias(desc.data) | 
 | 130 | 	# typeAppleEvent returned as unknown | 
 | 131 | 	if t == typeBoolean: | 
 | 132 | 		return struct.unpack('b', desc.data)[0] | 
 | 133 | 	if t == typeChar: | 
 | 134 | 		return desc.data | 
 | 135 | 	# typeColorTable coerced to typeAEList | 
 | 136 | 	# typeComp coerced to extended | 
 | 137 | 	# typeData returned as unknown | 
 | 138 | 	# typeDrawingArea coerced to typeAERecord | 
 | 139 | 	if t == typeEnumeration: | 
 | 140 | 		return mkenum(desc.data) | 
 | 141 | 	# typeEPS returned as unknown | 
| Jack Jansen | 40775ba | 1995-07-17 11:42:23 +0000 | [diff] [blame] | 142 | 	if t == typeFalse: | 
 | 143 | 		return 0 | 
| Jack Jansen | 49fc666 | 1999-02-10 09:51:35 +0000 | [diff] [blame] | 144 | 	if t == typeFloat: | 
 | 145 | 		data = desc.data | 
 | 146 | 		return struct.unpack('d', data)[0] | 
| Jack Jansen | 40775ba | 1995-07-17 11:42:23 +0000 | [diff] [blame] | 147 | 	if t == typeFSS: | 
 | 148 | 		return macfs.RawFSSpec(desc.data) | 
 | 149 | 	if t == typeInsertionLoc: | 
 | 150 | 		record = desc.AECoerceDesc('reco') | 
 | 151 | 		return mkinsertionloc(unpack(record)) | 
 | 152 | 	# typeInteger equal to typeLongInteger | 
 | 153 | 	if t == typeIntlText: | 
 | 154 | 		script, language = struct.unpack('hh', desc.data[:4]) | 
 | 155 | 		return aetypes.IntlText(script, language, desc.data[4:]) | 
 | 156 | 	if t == typeIntlWritingCode: | 
 | 157 | 		script, language = struct.unpack('hh', desc.data) | 
 | 158 | 		return aetypes.IntlWritingCode(script, language) | 
 | 159 | 	if t == typeKeyword: | 
 | 160 | 		return mkkeyword(desc.data) | 
| Jack Jansen | 40775ba | 1995-07-17 11:42:23 +0000 | [diff] [blame] | 161 | 	if t == typeLongInteger: | 
 | 162 | 		return struct.unpack('l', desc.data)[0] | 
| Jack Jansen | 3167788 | 2000-04-18 14:08:31 +0000 | [diff] [blame] | 163 | 	if t == typeLongDateTime: | 
 | 164 | 		a, b = struct.unpack('lL', desc.data) | 
 | 165 | 		return (long(a) << 32) + b | 
| Jack Jansen | 40775ba | 1995-07-17 11:42:23 +0000 | [diff] [blame] | 166 | 	if t == typeNull: | 
 | 167 | 		return None | 
 | 168 | 	if t == typeMagnitude: | 
 | 169 | 		v = struct.unpack('l', desc.data) | 
 | 170 | 		if v < 0: | 
 | 171 | 			v = 0x100000000L + v | 
 | 172 | 		return v | 
 | 173 | 	if t == typeObjectSpecifier: | 
 | 174 | 		record = desc.AECoerceDesc('reco') | 
 | 175 | 		return mkobject(unpack(record)) | 
 | 176 | 	# typePict returned as unknown | 
 | 177 | 	# typePixelMap coerced to typeAERecord | 
 | 178 | 	# typePixelMapMinus returned as unknown | 
 | 179 | 	# typeProcessSerialNumber returned as unknown | 
 | 180 | 	if t == typeQDPoint: | 
 | 181 | 		v, h = struct.unpack('hh', desc.data) | 
 | 182 | 		return aetypes.QDPoint(v, h) | 
 | 183 | 	if t == typeQDRectangle: | 
 | 184 | 		v0, h0, v1, h1 = struct.unpack('hhhh', desc.data) | 
 | 185 | 		return aetypes.QDRectangle(v0, h0, v1, h1) | 
 | 186 | 	if t == typeRGBColor: | 
 | 187 | 		r, g, b = struct.unpack('hhh', desc.data) | 
 | 188 | 		return aetypes.RGBColor(r, g, b) | 
 | 189 | 	# typeRotation coerced to typeAERecord | 
 | 190 | 	# typeScrapStyles returned as unknown | 
 | 191 | 	# typeSessionID returned as unknown | 
 | 192 | 	if t == typeShortFloat: | 
 | 193 | 		return struct.unpack('f', desc.data)[0] | 
 | 194 | 	if t == typeShortInteger: | 
 | 195 | 		return struct.unpack('h', desc.data)[0] | 
 | 196 | 	# typeSMFloat identical to typeShortFloat | 
 | 197 | 	# typeSMInt	indetical to typeShortInt | 
 | 198 | 	# typeStyledText coerced to typeAERecord | 
 | 199 | 	if t == typeTargetID: | 
 | 200 | 		return mktargetid(desc.data) | 
 | 201 | 	# typeTextStyles coerced to typeAERecord | 
 | 202 | 	# typeTIFF returned as unknown | 
 | 203 | 	if t == typeTrue: | 
 | 204 | 		return 1 | 
 | 205 | 	if t == typeType: | 
 | 206 | 		return mktype(desc.data) | 
 | 207 | 	# | 
 | 208 | 	# The following are special | 
 | 209 | 	# | 
 | 210 | 	if t == 'rang': | 
 | 211 | 		record = desc.AECoerceDesc('reco') | 
 | 212 | 		return mkrange(unpack(record)) | 
 | 213 | 	if t == 'cmpd': | 
 | 214 | 		record = desc.AECoerceDesc('reco') | 
 | 215 | 		return mkcomparison(unpack(record)) | 
 | 216 | 	if t == 'logi': | 
 | 217 | 		record = desc.AECoerceDesc('reco') | 
 | 218 | 		return mklogical(unpack(record)) | 
 | 219 | 	return mkunknown(desc.type, desc.data) | 
 | 220 | 	 | 
 | 221 | def coerce(data, egdata): | 
 | 222 | 	"""Coerce a python object to another type using the AE coercers""" | 
 | 223 | 	pdata = pack(data) | 
 | 224 | 	pegdata = pack(egdata) | 
 | 225 | 	pdata = pdata.AECoerceDesc(pegdata.type) | 
 | 226 | 	return unpack(pdata) | 
 | 227 |  | 
 | 228 | # | 
 | 229 | # Helper routines for unpack | 
 | 230 | # | 
 | 231 | def mktargetid(data): | 
 | 232 | 	sessionID = getlong(data[:4]) | 
 | 233 | 	name = mkppcportrec(data[4:4+72]) | 
 | 234 | 	location = mklocationnamerec(data[76:76+36]) | 
 | 235 | 	rcvrName = mkppcportrec(data[112:112+72]) | 
 | 236 | 	return sessionID, name, location, rcvrName | 
 | 237 |  | 
 | 238 | def mkppcportrec(rec): | 
 | 239 | 	namescript = getword(rec[:2]) | 
 | 240 | 	name = getpstr(rec[2:2+33]) | 
 | 241 | 	portkind = getword(rec[36:38]) | 
 | 242 | 	if portkind == 1: | 
 | 243 | 		ctor = rec[38:42] | 
 | 244 | 		type = rec[42:46] | 
 | 245 | 		identity = (ctor, type) | 
 | 246 | 	else: | 
 | 247 | 		identity = getpstr(rec[38:38+33]) | 
 | 248 | 	return namescript, name, portkind, identity | 
 | 249 |  | 
 | 250 | def mklocationnamerec(rec): | 
 | 251 | 	kind = getword(rec[:2]) | 
 | 252 | 	stuff = rec[2:] | 
 | 253 | 	if kind == 0: stuff = None | 
 | 254 | 	if kind == 2: stuff = getpstr(stuff) | 
 | 255 | 	return kind, stuff | 
 | 256 |  | 
 | 257 | def mkunknown(type, data): | 
 | 258 | 	return aetypes.Unknown(type, data) | 
 | 259 |  | 
 | 260 | def getpstr(s): | 
 | 261 | 	return s[1:1+ord(s[0])] | 
 | 262 |  | 
 | 263 | def getlong(s): | 
 | 264 | 	return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3]) | 
 | 265 |  | 
 | 266 | def getword(s): | 
 | 267 | 	return (ord(s[0])<<8) | (ord(s[1])<<0) | 
 | 268 |  | 
 | 269 | def mkkeyword(keyword): | 
 | 270 | 	return aetypes.Keyword(keyword) | 
 | 271 |  | 
 | 272 | def mkrange(dict): | 
 | 273 | 	return aetypes.Range(dict['star'], dict['stop']) | 
 | 274 |  | 
 | 275 | def mkcomparison(dict): | 
 | 276 | 	return aetypes.Comparison(dict['obj1'], dict['relo'].enum, dict['obj2']) | 
 | 277 |  | 
 | 278 | def mklogical(dict): | 
 | 279 | 	return aetypes.Logical(dict['logc'], dict['term']) | 
 | 280 |  | 
 | 281 | def mkstyledtext(dict): | 
 | 282 | 	return aetypes.StyledText(dict['ksty'], dict['ktxt']) | 
 | 283 | 	 | 
 | 284 | def mkaetext(dict): | 
 | 285 | 	return aetypes.AEText(dict[keyAEScriptTag], dict[keyAEStyles], dict[keyAEText]) | 
 | 286 | 	 | 
 | 287 | def mkinsertionloc(dict): | 
 | 288 | 	return aetypes.InsertionLoc(dict[keyAEObject], dict[keyAEPosition]) | 
 | 289 |  | 
 | 290 | def mkobject(dict): | 
 | 291 | 	want = dict['want'].type | 
 | 292 | 	form = dict['form'].enum | 
 | 293 | 	seld = dict['seld'] | 
 | 294 | 	fr   = dict['from'] | 
 | 295 | 	if form in ('name', 'indx', 'rang', 'test'): | 
 | 296 | 		if want == 'text': return aetypes.Text(seld, fr) | 
 | 297 | 		if want == 'cha ': return aetypes.Character(seld, fr) | 
 | 298 | 		if want == 'cwor': return aetypes.Word(seld, fr) | 
 | 299 | 		if want == 'clin': return aetypes.Line(seld, fr) | 
 | 300 | 		if want == 'cpar': return aetypes.Paragraph(seld, fr) | 
 | 301 | 		if want == 'cwin': return aetypes.Window(seld, fr) | 
 | 302 | 		if want == 'docu': return aetypes.Document(seld, fr) | 
 | 303 | 		if want == 'file': return aetypes.File(seld, fr) | 
 | 304 | 		if want == 'cins': return aetypes.InsertionPoint(seld, fr) | 
 | 305 | 	if want == 'prop' and form == 'prop' and aetypes.IsType(seld): | 
 | 306 | 		return aetypes.Property(seld.type, fr) | 
 | 307 | 	return aetypes.ObjectSpecifier(want, form, seld, fr) | 
 | 308 |  | 
 | 309 | def _test(): | 
 | 310 | 	"""Test program. Pack and unpack various things""" | 
 | 311 | 	objs = [ | 
 | 312 | 		'a string', | 
 | 313 | 		12, | 
 | 314 | 		12.0, | 
 | 315 | 		None, | 
 | 316 | 		['a', 'list', 'of', 'strings'], | 
 | 317 | 		{'key1': 'value1', 'key2':'value2'}, | 
 | 318 | 		macfs.FSSpec(':'), | 
 | 319 | 		macfs.FSSpec(':').NewAliasMinimal(), | 
 | 320 | 		aetypes.Enum('enum'), | 
 | 321 | 		aetypes.Type('type'), | 
 | 322 | 		aetypes.Keyword('kwrd'), | 
 | 323 | 		aetypes.Range(1, 10), | 
 | 324 | 		aetypes.Comparison(1, '<   ', 10), | 
 | 325 | 		aetypes.Logical('not ', 1), | 
 | 326 | 		# Cannot do StyledText | 
 | 327 | 		# Cannot do AEText | 
 | 328 | 		aetypes.IntlText(0, 0, 'international text'), | 
 | 329 | 		aetypes.IntlWritingCode(0,0), | 
 | 330 | 		aetypes.QDPoint(50,100), | 
 | 331 | 		aetypes.QDRectangle(50,100,150,200), | 
 | 332 | 		aetypes.RGBColor(0x7000, 0x6000, 0x5000), | 
 | 333 | 		aetypes.Unknown('xxxx', 'unknown type data'), | 
 | 334 | 		aetypes.Character(1), | 
 | 335 | 		aetypes.Character(2, aetypes.Line(2)), | 
 | 336 | 	] | 
 | 337 | 	for o in objs: | 
 | 338 | 		print 'BEFORE', o, `o` | 
 | 339 | 		packed = pack(o) | 
 | 340 | 		unpacked = unpack(packed) | 
 | 341 | 		print 'AFTER ', unpacked, `unpacked` | 
 | 342 | 	import sys | 
 | 343 | 	sys.exit(1) | 
 | 344 | 	 | 
 | 345 | if __name__ == '__main__': | 
 | 346 | 	_test() | 
 | 347 | 	 |