rb-appscript 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/CHANGES +243 -0
  2. data/LICENSE +1 -0
  3. data/README +42 -0
  4. data/TODO +31 -0
  5. data/doc/aem-manual/01_introduction.html +48 -0
  6. data/doc/aem-manual/02_apioverview.html +89 -0
  7. data/doc/aem-manual/03_packingandunpackingdata.html +98 -0
  8. data/doc/aem-manual/04_references.html +401 -0
  9. data/doc/aem-manual/05_targettingapplications.html +133 -0
  10. data/doc/aem-manual/06_buildingandsendingevents.html +175 -0
  11. data/doc/aem-manual/07_findapp.html +54 -0
  12. data/doc/aem-manual/08_examples.html +85 -0
  13. data/doc/aem-manual/09_notes.html +41 -0
  14. data/doc/aem-manual/aemreferenceinheritance.gif +0 -0
  15. data/doc/aem-manual/full.css +21 -0
  16. data/doc/aem-manual/index.html +43 -0
  17. data/doc/appscript-manual/01_introduction.html +82 -0
  18. data/doc/appscript-manual/02_aboutappscripting.html +244 -0
  19. data/doc/appscript-manual/03_quicktutorial.html +154 -0
  20. data/doc/appscript-manual/04_gettinghelp.html +101 -0
  21. data/doc/appscript-manual/05_keywordconversion.html +91 -0
  22. data/doc/appscript-manual/06_classesandenums.html +174 -0
  23. data/doc/appscript-manual/07_applicationobjects.html +181 -0
  24. data/doc/appscript-manual/08_realvsgenericreferences.html +86 -0
  25. data/doc/appscript-manual/09_referenceforms.html +232 -0
  26. data/doc/appscript-manual/10_referenceexamples.html +142 -0
  27. data/doc/appscript-manual/11_applicationcommands.html +204 -0
  28. data/doc/appscript-manual/12_commandexamples.html +129 -0
  29. data/doc/appscript-manual/13_performanceissues.html +115 -0
  30. data/doc/appscript-manual/14_problemapps.html +193 -0
  31. data/doc/appscript-manual/15_notes.html +84 -0
  32. data/doc/appscript-manual/application_architecture.gif +0 -0
  33. data/doc/appscript-manual/application_architecture2.gif +0 -0
  34. data/doc/appscript-manual/finder_to_textedit_event.gif +0 -0
  35. data/doc/appscript-manual/full.css +21 -0
  36. data/doc/appscript-manual/index.html +49 -0
  37. data/doc/appscript-manual/relationships_example.gif +0 -0
  38. data/doc/appscript-manual/ruby_to_itunes_event.gif +0 -0
  39. data/doc/index.html +30 -0
  40. data/doc/mactypes-manual/index.html +216 -0
  41. data/doc/osax-manual/index.html +169 -0
  42. data/extconf.rb +54 -0
  43. data/misc/adobeunittypes.rb +14 -0
  44. data/misc/dump.rb +72 -0
  45. data/rb-appscript.gemspec +20 -0
  46. data/sample/AB_list_people_with_emails.rb +8 -0
  47. data/sample/Create_daily_iCal_todos.rb +72 -0
  48. data/sample/Hello_world.rb +9 -0
  49. data/sample/List_iTunes_playlist_names.rb +7 -0
  50. data/sample/Make_Mail_message.rb +29 -0
  51. data/sample/Open_file_in_TextEdit.rb +9 -0
  52. data/sample/Organize_Mail_messages.rb +57 -0
  53. data/sample/Print_folder_tree.rb +12 -0
  54. data/sample/Select_all_HTML_files.rb +8 -0
  55. data/sample/Set_iChat_status.rb +20 -0
  56. data/sample/Simple_Finder_GUI_Scripting.rb +14 -0
  57. data/sample/Stagger_Finder_windows.rb +21 -0
  58. data/sample/TextEdit_demo.rb +126 -0
  59. data/sample/iTunes_top40_to_html.rb +64 -0
  60. data/src/lib/_aem/aemreference.rb +1006 -0
  61. data/src/lib/_aem/codecs.rb +617 -0
  62. data/src/lib/_aem/connect.rb +100 -0
  63. data/src/lib/_aem/findapp.rb +83 -0
  64. data/src/lib/_aem/mactypes.rb +228 -0
  65. data/src/lib/_aem/send.rb +257 -0
  66. data/src/lib/_aem/typewrappers.rb +57 -0
  67. data/src/lib/_appscript/defaultterminology.rb +245 -0
  68. data/src/lib/_appscript/referencerenderer.rb +132 -0
  69. data/src/lib/_appscript/reservedkeywords.rb +107 -0
  70. data/src/lib/_appscript/terminology.rb +314 -0
  71. data/src/lib/aem.rb +216 -0
  72. data/src/lib/appscript.rb +830 -0
  73. data/src/lib/kae.rb +1484 -0
  74. data/src/lib/osax.rb +171 -0
  75. data/src/rbae.c +766 -0
  76. data/test/README +1 -0
  77. data/test/test_aemreference.rb +112 -0
  78. data/test/test_appscriptreference.rb +102 -0
  79. data/test/test_codecs.rb +159 -0
  80. data/test/test_findapp.rb +24 -0
  81. data/test/test_mactypes.rb +67 -0
  82. data/test/testall.sh +9 -0
  83. metadata +143 -0
@@ -0,0 +1,617 @@
1
+ #!/usr/local/bin/ruby
2
+ # Copyright (C) 2006 HAS.
3
+ # Released under MIT License.
4
+
5
+ require "ae"
6
+ require "kae"
7
+ require "_aem/typewrappers"
8
+ require "_aem/aemreference"
9
+ require "_aem/mactypes"
10
+
11
+ # Note that AE strings (typeChar, typeUnicodeText, etc.) are unpacked as UTF8-encoded Ruby strings, and UTF8-encoded Ruby strings are packed as typeUnicodeText. Using UTF8 on the Ruby side avoids data loss; using typeUnicodeText on the AEM side provides compatibility with all [reasonably well designed] applications. To change this behaviour (e.g. to support legacy apps that demand typeChar and break on typeUnicodeText), subclass Codecs and override pack and/or unpack methods to provide alternative packing/unpacking of string values. Users can also pack data manually using AE::AEDesc.new(type, data).
12
+
13
+
14
+ ######################################################################
15
+ # UNIT TYPE CODECS
16
+ ######################################################################
17
+
18
+
19
+ class UnitTypeCodecs
20
+ # Provides pack and unpack methods for converting between MacTypes::Units instances
21
+ # and AE unit types. Each Codecs instance is allocated its own UnitTypeCodecs instance,
22
+ #
23
+
24
+ DefaultUnitTypes = [
25
+ [:centimeters, KAE::TypeCentimeters],
26
+ [:meters, KAE::TypeMeters],
27
+ [:kilometers, KAE::TypeKilometers],
28
+ [:inches, KAE::TypeInches],
29
+ [:feet, KAE::TypeFeet],
30
+ [:yards, KAE::TypeYards],
31
+ [:miles, KAE::TypeMiles],
32
+
33
+ [:square_meters, KAE::TypeSquareMeters],
34
+ [:square_kilometers, KAE::TypeSquareKilometers],
35
+ [:square_feet, KAE::TypeSquareFeet],
36
+ [:square_yards, KAE::TypeSquareYards],
37
+ [:square_miles, KAE::TypeSquareMiles],
38
+
39
+ [:cubic_centimeters, KAE::TypeCubicCentimeter],
40
+ [:cubic_meters, KAE::TypeCubicMeters],
41
+ [:cubic_inches, KAE::TypeCubicInches],
42
+ [:cubic_feet, KAE::TypeCubicFeet],
43
+ [:cubic_yards, KAE::TypeCubicYards],
44
+
45
+ [:liters, KAE::TypeLiters],
46
+ [:quarts, KAE::TypeQuarts],
47
+ [:gallons, KAE::TypeGallons],
48
+
49
+ [:grams, KAE::TypeGrams],
50
+ [:kilograms, KAE::TypeKilograms],
51
+ [:ounces, KAE::TypeOunces],
52
+ [:pounds, KAE::TypePounds],
53
+
54
+ [:degrees_Celsius, KAE::TypeDegreesC],
55
+ [:degrees_Fahrenheit, KAE::TypeDegreesF],
56
+ [:degrees_Kelvin, KAE::TypeDegreesK],
57
+ ]
58
+
59
+ DefaultPacker = proc { |value, code| AE::AEDesc.new(code, [value].pack('d')) }
60
+ DefaultUnpacker = proc { |desc, name| MacTypes::Units.new(desc.data.unpack('d')[0], name) }
61
+
62
+ def initialize
63
+ @type_by_name = {}
64
+ @type_by_code = {}
65
+ add_types(DefaultUnitTypes)
66
+ end
67
+
68
+ def add_types(type_defs)
69
+ # type_defs is a list of lists, where each sublist is of form:
70
+ # [typename, typecode, packproc, unpackproc]
71
+ # or:
72
+ # [typename, typecode, packproc, unpackproc]
73
+ # If optional packproc and unpackproc are omitted, default pack/unpack procs
74
+ # are used instead; these pack/unpack AEDesc data as a double precision float.
75
+ type_defs.each do |name, code, packer, unpacker|
76
+ @type_by_name[name] = [code, (packer or DefaultPacker)]
77
+ @type_by_code[code] = [name, (unpacker or DefaultUnpacker)]
78
+ end
79
+ end
80
+
81
+ def pack(val)
82
+ if val.is_a?(MacTypes::Units)
83
+ code, packer = @type_by_name.fetch(val.type) { |val| raise IndexError, "Unknown unit type: #{val.inspect}" }
84
+ return [true, packer.call(val.value, code)]
85
+ else
86
+ return [false, val]
87
+ end
88
+ end
89
+
90
+ def unpack(desc)
91
+ name, unpacker = @type_by_code.fetch(desc.type) { |desc| return [false, desc] }
92
+ return [true, unpacker.call(desc, name)]
93
+ end
94
+ end
95
+
96
+
97
+ ######################################################################
98
+ # CODECS
99
+ ######################################################################
100
+ # Endianness support
101
+
102
+ module BigEndianConverters
103
+
104
+ def four_char_code(code)
105
+ return code
106
+ end
107
+
108
+ end
109
+
110
+
111
+ module SmallEndianConverters
112
+
113
+ def four_char_code(code)
114
+ return code.reverse
115
+ end
116
+
117
+ end
118
+
119
+
120
+ ######################################################################
121
+
122
+
123
+ class Codecs
124
+ # Provides pack and unpack methods for converting data between Ruby and AE types.
125
+ #
126
+ # May be subclassed to extend/alter its behaviour (e.g. the appscript layer does this).
127
+ # Conversions that are most likely to be modified (e.g. for packing and and unpacking
128
+ # references, records, types and enums) are exposed as overrideable hook methods.
129
+
130
+ extend([1].pack('s') == "\001\000" ? SmallEndianConverters : BigEndianConverters)
131
+
132
+ def initialize
133
+ @unit_type_codecs = UnitTypeCodecs.new
134
+ end
135
+
136
+ def add_unit_types(type_defs)
137
+ # register custom unit type definitions with this Codecs instance
138
+ # e.g. Adobe apps define additional unit types (ciceros, pixels, etc.)
139
+ @unit_type_codecs.add_types(type_defs)
140
+ end
141
+
142
+ ######################################################################
143
+ # Subclasses could override these to provide their own reference roots if needed
144
+
145
+ App = AEMReference::App
146
+ Con = AEMReference::Con
147
+ Its = AEMReference::Its
148
+
149
+ ######################################################################
150
+ # Pack
151
+
152
+ SInt32Bounds = (-2**31)..(2**31-1)
153
+ SInt64Bounds = (-2**63)..(2**63-1)
154
+
155
+ NullDesc = AE::AEDesc.new(KAE::TypeNull, '')
156
+ TrueDesc = AE::AEDesc.new(KAE::TypeTrue, '')
157
+ FalseDesc = AE::AEDesc.new(KAE::TypeFalse, '')
158
+
159
+ ##
160
+
161
+ def pack_unknown(val) # clients may override this to provide additional packers
162
+ raise TypeError, "Can't pack data into an AEDesc (unsupported type): #{val.inspect}"
163
+ end
164
+
165
+
166
+ def pack(val) # clients may override this to replace existing packers
167
+ case val
168
+ when AEMReference::Base then val.AEM_pack_self(self)
169
+ when NilClass then NullDesc
170
+ when TrueClass then TrueDesc
171
+ when FalseClass then FalseDesc
172
+ when Fixnum then AE::AEDesc.new(KAE::TypeSInt32, [val].pack('l'))
173
+ when Bignum
174
+ if SInt32Bounds === val
175
+ AE::AEDesc.new(KAE::TypeSInt32, [val].pack('l'))
176
+ elsif SInt64Bounds === val
177
+ AE::AEDesc.new(KAE::TypeSInt64, [val].pack('q'))
178
+ else
179
+ AE::AEDesc.new(KAE::TypeFloat, [val.to_f].pack('d'))
180
+ end
181
+ when Float then AE::AEDesc.new(KAE::TypeFloat, [val].pack('d'))
182
+ when String then
183
+ begin
184
+ # Note: while typeUnicodeText is deprecated (see AEDataModel.h), it's still the
185
+ # most commonly used Unicode type so is used here for compatibility's sake.
186
+ # typeUTF8Text was initially tried, but existing applications had problems with it; i.e.
187
+ # some apps make unsafe assumptions on what to expect based on AS's behaviour.
188
+ # Once AppleScript is using typeUTF8Text/typeUTF16ExternalRepresentation
189
+ # and existing applications don't choke, this code can be similarly upgraded.
190
+ # Note: while the BOM is optional in typeUnicodeText, it's not included by AS
191
+ # and some apps, e.g. iTunes 7, will handle it incorrectly, so it's omitted here.)
192
+ AE::AEDesc.new(KAE::TypeUTF8Text, val).coerce(KAE::TypeUnicodeText)
193
+ rescue AE::MacOSError => e
194
+ if e.to_i == -1700 # couldn't coerce to TypeUnicodeText
195
+ raise TypeError, "Not valid UTF8 data: #{val.inspect}"
196
+ else
197
+ raise
198
+ end
199
+ end
200
+ when Time
201
+ AE::AEDesc.new(KAE::TypeLongDateTime,
202
+ [AE.convert_unix_seconds_to_long_date_time(val.to_i)].pack('q'))
203
+ when Array then pack_array(val)
204
+ when Hash then pack_hash(val)
205
+ when MacTypes::FileBase then val.desc
206
+ when TypeWrappers::AEType then
207
+ AE::AEDesc.new(KAE::TypeType, Codecs.four_char_code(val.code))
208
+ when TypeWrappers::AEEnum then
209
+ AE::AEDesc.new(KAE::TypeEnumerated, Codecs.four_char_code(val.code))
210
+ when TypeWrappers::AEProp then
211
+ AE::AEDesc.new(KAE::TypeProperty, Codecs.four_char_code(val.code))
212
+ when TypeWrappers::AEKey then
213
+ AE::AEDesc.new(KAE::TypeKeyword, Codecs.four_char_code(val.code))
214
+ when AE::AEDesc then val
215
+ else
216
+ did_pack, desc = @unit_type_codecs.pack(val)
217
+ if did_pack
218
+ desc
219
+ else
220
+ pack_unknown(val)
221
+ end
222
+ end
223
+ end
224
+
225
+ #######
226
+
227
+ def pack_array(val)
228
+ lst = AE::AEDesc.new_list(false)
229
+ val.each do |item|
230
+ lst.put_item(0, pack(item))
231
+ end
232
+ return lst
233
+ end
234
+
235
+ def pack_hash(val)
236
+ record = AE::AEDesc.new_list(true)
237
+ usrf = nil
238
+ val.each do | key, value |
239
+ if key.is_a?(TypeWrappers::AETypeBase)
240
+ if key.code == 'pcls' # AS packs records that contain a 'class' property by coercing the packed record to that type at the end
241
+ begin
242
+ record = record.coerce(value.code)
243
+ rescue
244
+ record.put_param(key.code, pack(value))
245
+ end
246
+ else
247
+ record.put_param(key.code, pack(value))
248
+ end
249
+ else
250
+ if usrf == nil
251
+ usrf = AE::AEDesc.new_list(false)
252
+ end
253
+ usrf.put_item(0, pack(key))
254
+ usrf.put_item(0, pack(value))
255
+ end
256
+ end
257
+ if usrf
258
+ record.put_param('usrf', usrf)
259
+ end
260
+ return record
261
+ end
262
+
263
+ ######################################################################
264
+ # Unpack
265
+
266
+ def unpack_unknown(desc) # clients may override this to provide additional unpackers
267
+ if desc.is_record? # if it's a record-like structure with an unknown/unsupported type then unpack it as a hash, including the original type info as a 'class' property
268
+ rec = desc.coerce(KAE::TypeAERecord)
269
+ rec.put_param('pcls', pack(TypeWrappers::AEType.new(desc.type)))
270
+ unpack(rec)
271
+ else # else return unchanged
272
+ desc
273
+ end
274
+ end
275
+
276
+
277
+ def unpack(desc) # clients may override this to replace existing unpackers
278
+ return case desc.type
279
+
280
+ when KAE::TypeNull then nil
281
+ when KAE::TypeBoolean then desc.data != "\000"
282
+ when KAE::TypeFalse then false
283
+ when KAE::TypeTrue then true
284
+
285
+ when KAE::TypeSInt16 then desc.data.unpack('s')[0]
286
+ when KAE::TypeSInt32 then desc.data.unpack('l')[0]
287
+ when KAE::TypeUInt32 then desc.data.unpack('L')[0]
288
+ when KAE::TypeSInt64 then desc.data.unpack('q')[0]
289
+ when KAE::TypeIEEE32BitFloatingPoint then desc.data.unpack('f')[0]
290
+ when KAE::TypeIEEE64BitFloatingPoint then desc.data.unpack('d')[0]
291
+ when KAE::Type128BitFloatingPoint then
292
+ desc.coerce(KAE::TypeIEEE64BitFloatingPoint).data.unpack('d')[0]
293
+
294
+ when KAE::TypeUTF8Text then desc.data
295
+ when
296
+ KAE::TypeUnicodeText,
297
+ KAE::TypeChar,
298
+ KAE::TypeIntlText,
299
+ KAE::TypeUTF16ExternalRepresentation,
300
+ KAE::TypeStyledText,
301
+ KAE::TypeStyledUnicodeText
302
+ desc.coerce(KAE::TypeUTF8Text).data
303
+
304
+ when KAE::TypeLongDateTime then
305
+ Time.at(AE.convert_long_date_time_to_unix_seconds(desc.data.unpack('q')[0]))
306
+
307
+ when KAE::TypeVersion
308
+ vers, lo = desc.data.unpack('CC')
309
+ subvers, patch = lo.divmod(16)
310
+ "#{vers}.#{subvers}.#{patch}"
311
+
312
+ when KAE::TypeAEList then unpack_aelist(desc)
313
+ when KAE::TypeAERecord then unpack_aerecord(desc)
314
+
315
+ when KAE::TypeAlias then MacTypes::Alias.desc(desc)
316
+ when
317
+ KAE::TypeFileURL,
318
+ KAE::TypeFSRef,
319
+ KAE::TypeFSS
320
+ MacTypes::FileURL.desc(desc)
321
+
322
+ when KAE::TypeQDPoint then desc.data.unpack('ss').reverse
323
+ when KAE::TypeQDRectangle then
324
+ x1, y1, x2, y2 = desc.data.unpack('ssss')
325
+ [y1, x1, y2, x2]
326
+ when KAE::TypeRGBColor then desc.data.unpack('SSS')
327
+
328
+ when KAE::TypeType then unpack_type(desc)
329
+ when KAE::TypeEnumerated then unpack_enumerated(desc)
330
+ when KAE::TypeProperty then unpack_property(desc)
331
+ when KAE::TypeKeyword then unpack_keyword(desc)
332
+
333
+ when KAE::TypeInsertionLoc then unpack_insertion_loc(desc)
334
+ when KAE::TypeObjectSpecifier then unpack_object_specifier(desc)
335
+ when KAE::TypeAbsoluteOrdinal then unpack_absolute_ordinal(desc)
336
+ when KAE::TypeCurrentContainer then unpack_current_container(desc)
337
+ when KAE::TypeObjectBeingExamined then unpack_object_being_examined(desc)
338
+ when KAE::TypeCompDescriptor then unpack_comp_descriptor(desc)
339
+ when KAE::TypeLogicalDescriptor then unpack_logical_descriptor(desc)
340
+ when KAE::TypeRangeDescriptor then unpack_range_descriptor(desc)
341
+
342
+ else
343
+ did_unpack, val = @unit_type_codecs.unpack(desc)
344
+ if did_unpack
345
+ val
346
+ else
347
+ unpack_unknown(desc)
348
+ end
349
+ end
350
+ end
351
+
352
+ #######
353
+
354
+ def unpack_aelist(desc)
355
+ lst = []
356
+ desc.length().times do |i|
357
+ lst.push(unpack(desc.get(i + 1, KAE::TypeWildCard)[1]))
358
+ end
359
+ return lst
360
+ end
361
+
362
+ def unpack_aerecord(desc)
363
+ dct = {}
364
+ desc.length().times do |i|
365
+ key, value = desc.get(i + 1, KAE::TypeWildCard)
366
+ if key == 'usrf'
367
+ lst = unpack_aelist(value)
368
+ (lst.length / 2).times do |i|
369
+ dct[lst[i * 2]] = lst[i * 2 + 1]
370
+ end
371
+ else
372
+ dct[TypeWrappers::AEType.new(key)] = unpack(value)
373
+ end
374
+ end
375
+ return dct
376
+ end
377
+
378
+ #######
379
+
380
+ def unpack_type(desc)
381
+ return TypeWrappers::AEType.new(Codecs.four_char_code(desc.data))
382
+ end
383
+
384
+ def unpack_enumerated(desc)
385
+ return TypeWrappers::AEEnum.new(Codecs.four_char_code(desc.data))
386
+ end
387
+
388
+ def unpack_property(desc)
389
+ return TypeWrappers::AEProp.new(Codecs.four_char_code(desc.data))
390
+ end
391
+
392
+ def unpack_keyword(desc)
393
+ return TypeWrappers::AEKey.new(Codecs.four_char_code(desc.data))
394
+ end
395
+
396
+ #######
397
+
398
+ class Range
399
+ attr_reader :range
400
+
401
+ def initialize(range)
402
+ @range = range
403
+ end
404
+ end
405
+
406
+ class Ordinal
407
+ attr_reader :code
408
+
409
+ def initialize(code)
410
+ @code = code
411
+ end
412
+ end
413
+
414
+ def _desc_to_hash(desc)
415
+ desc = desc.coerce(KAE::TypeAERecord)
416
+ h = {}
417
+ desc.length.times do |i|
418
+ k, v = desc.get(i + 1, KAE::TypeWildCard)
419
+ h[k] = v
420
+ end
421
+ return h
422
+ end
423
+
424
+ #######
425
+
426
+ FullUnpackOrdinals = {
427
+ KAE::KAEFirst => 'first',
428
+ KAE::KAELast => 'last',
429
+ KAE::KAEMiddle => 'middle',
430
+ KAE::KAEAny => 'any',
431
+ }
432
+
433
+ DeferredUnpackOrdinals = {
434
+ KAE::KAEFirst => ['first', AEMReference::MultipleElements::First],
435
+ KAE::KAELast => ['last', AEMReference::MultipleElements::Last],
436
+ KAE::KAEMiddle => ['middle', AEMReference::MultipleElements::Middle],
437
+ KAE::KAEAny => ['any', AEMReference::MultipleElements::Any],
438
+ }
439
+
440
+ # InsertionLoc keys and comparison and logic comparison operators aren't unpacked before use,
441
+ # so need to call four_char_codes to swap bytes here.
442
+
443
+ InsertionLocEnums = {
444
+ Codecs.four_char_code(KAE::KAEBefore) => 'before',
445
+ Codecs.four_char_code(KAE::KAEAfter) => 'after',
446
+ Codecs.four_char_code(KAE::KAEBeginning) => 'start',
447
+ Codecs.four_char_code(KAE::KAEEnd) => 'end',
448
+ }
449
+
450
+ ComparisonEnums = {
451
+ Codecs.four_char_code(KAE::KAEGreaterThan) => 'gt',
452
+ Codecs.four_char_code(KAE::KAEGreaterThanEquals) => 'ge',
453
+ Codecs.four_char_code(KAE::KAEEquals) => 'eq',
454
+ Codecs.four_char_code(KAE::KAELessThan) => 'lt',
455
+ Codecs.four_char_code(KAE::KAELessThanEquals) => 'le',
456
+ Codecs.four_char_code(KAE::KAEBeginsWith) => 'starts_with',
457
+ Codecs.four_char_code(KAE::KAEEndsWith) => 'ends_with',
458
+ Codecs.four_char_code(KAE::KAEContains) => 'contains',
459
+ }
460
+
461
+ LogicalEnums = {
462
+ Codecs.four_char_code(KAE::KAEAND) => 'and',
463
+ Codecs.four_char_code(KAE::KAEOR) => 'or',
464
+ Codecs.four_char_code(KAE::KAENOT) => 'not',
465
+ }
466
+
467
+ #######
468
+
469
+ def fully_unpack_object_specifier(desc)
470
+ # Codecs.unpack_object_specifier and DeferredSpecifier._real_ref will call this when needed
471
+ case desc.type
472
+ when KAE::TypeNull then return self.class::App
473
+ when KAE::TypeCurrentContainer then return self.class::Con
474
+ when KAE::TypeObjectBeingExamined then return self.class::Its
475
+ end
476
+ rec = _desc_to_hash(desc)
477
+ want = unpack(rec[KAE::KeyAEDesiredClass]).code
478
+ key_form = unpack(rec[KAE::KeyAEKeyForm]).code
479
+ key = unpack(rec[KAE::KeyAEKeyData])
480
+ ref = unpack(rec[KAE::KeyAEContainer])
481
+ if ref == nil
482
+ ref = self.class::App
483
+ end
484
+ if key_form == KAE::FormPropertyID
485
+ return ref.property(key.code)
486
+ elsif key_form == 'usrp'
487
+ return ref.userproperty(key)
488
+ elsif key_form == KAE::FormRelativePosition
489
+ if key.code == KAE::KAEPrevious
490
+ return ref.previous(want)
491
+ elsif key.code == KAE::KAENext
492
+ return ref.next(want)
493
+ else
494
+ raise RuntimeError, "Bad relative position selector: #{key}"
495
+ end
496
+ else
497
+ ref = ref.elements(want)
498
+ if key_form == KAE::FormName
499
+ return ref.by_name(key)
500
+ elsif key_form == KAE::FormAbsolutePosition
501
+ if key.is_a?(Ordinal)
502
+ if key.code == KAE::KAEAll
503
+ return ref
504
+ else
505
+ return ref.send(FullUnpackOrdinals[key.code])
506
+ end
507
+ else
508
+ return ref.by_index(key)
509
+ end
510
+ elsif key_form == KAE::FormUniqueID
511
+ return ref.by_id(key)
512
+ elsif key_form == KAE::FormRange
513
+ return ref.by_range(*key.range)
514
+ elsif key_form == KAE::FormTest
515
+ return ref.by_filter(key)
516
+ end
517
+ end
518
+ raise TypeError
519
+ end
520
+
521
+ ##
522
+
523
+ def unpack_object_specifier(desc)
524
+ # defers full unpacking of [most] object specifiers for efficiency
525
+ rec = _desc_to_hash(desc)
526
+ key_form = unpack(rec[KAE::KeyAEKeyForm]).code
527
+ if [KAE::FormPropertyID, KAE::FormAbsolutePosition, KAE::FormName, KAE::FormUniqueID].include?(key_form)
528
+ want = unpack(rec[KAE::KeyAEDesiredClass]).code
529
+ key = unpack(rec[KAE::KeyAEKeyData])
530
+ container = AEMReference::DeferredSpecifier.new(rec[KAE::KeyAEContainer], self)
531
+ if key_form == KAE::FormPropertyID
532
+ ref = AEMReference::Property.new(want, container, key.code)
533
+ elsif key_form == KAE::FormAbsolutePosition
534
+ if key.is_a?(Ordinal)
535
+ if key.code == KAE::KAEAll
536
+ ref = AEMReference::AllElements.new(want, container)
537
+ else
538
+ keyname, key = DeferredUnpackOrdinals[key.code]
539
+ ref = AEMReference::ElementByOrdinal.new(want, AEMReference::UnkeyedElements.new(want, container), key, keyname)
540
+ end
541
+ else
542
+ ref = AEMReference::ElementByIndex.new(want, AEMReference::UnkeyedElements.new(want, container), key)
543
+ end
544
+ elsif key_form == KAE::FormName
545
+ ref = AEMReference::ElementByName.new(want, AEMReference::UnkeyedElements.new(want, container), key)
546
+ elsif key_form == KAE::FormUniqueID
547
+ ref = AEMReference::ElementByID.new(want, AEMReference::UnkeyedElements.new(want, container), key)
548
+ end
549
+ ref.AEM_set_desc(desc) # retain existing AEDesc for efficiency
550
+ return ref
551
+ else # do full unpack of more complex, rarely returned reference forms
552
+ return fully_unpack_object_specifier(desc)
553
+ end
554
+ end
555
+
556
+
557
+ def unpack_insertion_loc(desc)
558
+ rec = _desc_to_hash(desc)
559
+ return unpack_object_specifier(rec[KAE::KeyAEObject]).send(InsertionLocEnums[rec[KAE::KeyAEPosition].data])
560
+ end
561
+
562
+ ##
563
+
564
+ def unpack_absolute_ordinal(desc)
565
+ return Ordinal.new(Codecs.four_char_code(desc.data))
566
+ end
567
+
568
+ def unpack_current_container(desc)
569
+ return Con
570
+ end
571
+
572
+ def unpack_object_being_examined(desc)
573
+ return Its
574
+ end
575
+
576
+ ##
577
+
578
+ def unpack_contains_comp_descriptor(op1, op2)
579
+ # KAEContains is also used to construct 'is_in' tests, where test value is first operand and
580
+ # reference being tested is second operand, so need to make sure first operand is an its-based ref;
581
+ # if not, rearrange accordingly.
582
+ # Since type-checking is involved, this extra hook is provided so that appscript's AppData subclass can override this method to add its own type checking
583
+ if op1.is_a?(AEMReference::Base) and op1.AEM_root == AEMReference::Its
584
+ return op1.contains(op2)
585
+ else
586
+ return op2.is_in(op1)
587
+ end
588
+ end
589
+
590
+ def unpack_comp_descriptor(desc)
591
+ rec = _desc_to_hash(desc)
592
+ operator = ComparisonEnums[rec[KAE::KeyAECompOperator].data]
593
+ op1 = unpack(rec[KAE::KeyAEObject1])
594
+ op2 = unpack(rec[KAE::KeyAEObject2])
595
+ if operator == 'contains'
596
+ return unpack_contains_comp_descriptor(op1, op2)
597
+ else
598
+ return op1.send(operator, op2)
599
+ end
600
+ end
601
+
602
+ def unpack_logical_descriptor(desc)
603
+ rec = _desc_to_hash(desc)
604
+ operator = LogicalEnums[rec[KAE::KeyAELogicalOperator].data]
605
+ operands = unpack(rec[KAE::KeyAELogicalTerms])
606
+ return operands[0].send(operator, *operands[1, operands.length])
607
+ end
608
+
609
+ def unpack_range_descriptor(desc)
610
+ rec = _desc_to_hash(desc)
611
+ return Range.new([unpack(rec[KAE::KeyAERangeStart]), unpack(rec[KAE::KeyAERangeStop])])
612
+ end
613
+
614
+ end
615
+
616
+ DefaultCodecs = Codecs.new
617
+
@@ -0,0 +1,100 @@
1
+ #!/usr/local/bin/ruby
2
+ # Copyright (C) 2006 HAS.
3
+ # Released under MIT License.
4
+
5
+ # TO DO: this module refers directly to Send::Event instead of going via the AEM::Application::Event hook, which might cause problems when used in an OSA component or other situation where client needs to customise all event creation and/or dispatch.
6
+
7
+ module Connect
8
+ # Creates Apple event descriptor records of typeProcessSerialNumber, typeKernelProcessID and typeApplicationURL, used to specify the target application in Send::Event constructor.
9
+
10
+ require "ae"
11
+ require "kae"
12
+ require "_aem/codecs"
13
+ require "_aem/send"
14
+
15
+ LaunchContinue = 0x4000
16
+ LaunchNoFileFlags = 0x0800
17
+ LaunchDontSwitch = 0x0200
18
+
19
+ KNoProcess = 0
20
+ KCurrentProcess = 2
21
+
22
+ def Connect.make_address_desc(psn)
23
+ return AE::AEDesc.new(KAE::TypeProcessSerialNumber, psn.pack('LL'))
24
+ end
25
+
26
+ NullAddress = make_address_desc([0,KNoProcess])
27
+ LaunchEvent = Send::Event.new(Connect::NullAddress, 'ascrnoop').AEM_event
28
+ RunEvent = Send::Event.new(Connect::NullAddress, 'aevtoapp').AEM_event
29
+
30
+ #######
31
+ # public
32
+
33
+ def Connect.launch_app(path)
34
+ # Send a 'launch' event to an application. If application is not already running, it will be launched in background first.
35
+ begin
36
+ # If app is already running, calling AE.launch_application will send a 'reopen' event, so need to check for this first:
37
+ psn = AE.psn_for_application_path(path)
38
+ rescue AE::MacOSError => err
39
+ if err.to_i == -600 # Application isn't running, so launch it and send it a 'launch' event
40
+ sleep(1)
41
+ AE.launch_application(path, LaunchEvent,
42
+ LaunchContinue + LaunchNoFileFlags + LaunchDontSwitch)
43
+ else
44
+ raise
45
+ end
46
+ else # App is already running, so send it a 'launch' event
47
+ Send::Event.new(make_address_desc(psn), 'ascrnoop').send(60, KAE::KAENoReply)
48
+ end
49
+ end
50
+
51
+ def Connect.is_running?(path)
52
+ # Is a local application running?
53
+ begin
54
+ AE.psn_for_application_path(path)
55
+ return true
56
+ rescue AE::MacOSError => err
57
+ if err.to_i == -600
58
+ return false
59
+ else
60
+ raise
61
+ end
62
+ end
63
+ end
64
+
65
+ CurrentApp = make_address_desc([0, KCurrentProcess])
66
+
67
+ def Connect.local_app(path)
68
+ # Make an AEAddressDesc identifying a local application. (Application will be launched if not already running.)
69
+ # path : string -- full path to application, e.g. '/Applications/TextEdit.app'
70
+ # Result : AEAddressDesc
71
+ #
72
+ # Always creates AEAddressDesc by process serial number; that way there's no confusion if multiple versions of the same app are running.
73
+ begin
74
+ psn = AE.psn_for_application_path(path)
75
+ rescue AE::MacOSError => err
76
+ if err.to_i == -600 # Application isn't running, so launch it in background and send it a standard 'run' event.
77
+ sleep(1)
78
+ psn = AE.launch_application(path, RunEvent,
79
+ LaunchContinue + LaunchNoFileFlags + LaunchDontSwitch)
80
+ else
81
+ raise
82
+ end
83
+ end
84
+ return make_address_desc(psn)
85
+ end
86
+
87
+ def Connect.local_app_by_pid(pid)
88
+ # Make an AEAddressDesc identifying a running application by Unix process id.
89
+ # pid : integer -- unsigned 32-bit integer
90
+ # Result : AEAddressDesc
91
+ return AE::AEDesc.new(KAE::TypeKernelProcessID, [pid].pack('L'))
92
+ end
93
+
94
+ def Connect.remote_app(url)
95
+ # Make an AEAddressDesc identifying a running application on another machine.
96
+ # url : string -- URL for remote application, e.g. 'eppc://user:password@0.0.0.1/TextEdit'
97
+ # Result : AEAddressDesc
98
+ return AE::AEDesc.new(KAE::TypeApplicationURL, url)
99
+ end
100
+ end