rb-appscript 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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