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.
- data/CHANGES +243 -0
- data/LICENSE +1 -0
- data/README +42 -0
- data/TODO +31 -0
- data/doc/aem-manual/01_introduction.html +48 -0
- data/doc/aem-manual/02_apioverview.html +89 -0
- data/doc/aem-manual/03_packingandunpackingdata.html +98 -0
- data/doc/aem-manual/04_references.html +401 -0
- data/doc/aem-manual/05_targettingapplications.html +133 -0
- data/doc/aem-manual/06_buildingandsendingevents.html +175 -0
- data/doc/aem-manual/07_findapp.html +54 -0
- data/doc/aem-manual/08_examples.html +85 -0
- data/doc/aem-manual/09_notes.html +41 -0
- data/doc/aem-manual/aemreferenceinheritance.gif +0 -0
- data/doc/aem-manual/full.css +21 -0
- data/doc/aem-manual/index.html +43 -0
- data/doc/appscript-manual/01_introduction.html +82 -0
- data/doc/appscript-manual/02_aboutappscripting.html +244 -0
- data/doc/appscript-manual/03_quicktutorial.html +154 -0
- data/doc/appscript-manual/04_gettinghelp.html +101 -0
- data/doc/appscript-manual/05_keywordconversion.html +91 -0
- data/doc/appscript-manual/06_classesandenums.html +174 -0
- data/doc/appscript-manual/07_applicationobjects.html +181 -0
- data/doc/appscript-manual/08_realvsgenericreferences.html +86 -0
- data/doc/appscript-manual/09_referenceforms.html +232 -0
- data/doc/appscript-manual/10_referenceexamples.html +142 -0
- data/doc/appscript-manual/11_applicationcommands.html +204 -0
- data/doc/appscript-manual/12_commandexamples.html +129 -0
- data/doc/appscript-manual/13_performanceissues.html +115 -0
- data/doc/appscript-manual/14_problemapps.html +193 -0
- data/doc/appscript-manual/15_notes.html +84 -0
- data/doc/appscript-manual/application_architecture.gif +0 -0
- data/doc/appscript-manual/application_architecture2.gif +0 -0
- data/doc/appscript-manual/finder_to_textedit_event.gif +0 -0
- data/doc/appscript-manual/full.css +21 -0
- data/doc/appscript-manual/index.html +49 -0
- data/doc/appscript-manual/relationships_example.gif +0 -0
- data/doc/appscript-manual/ruby_to_itunes_event.gif +0 -0
- data/doc/index.html +30 -0
- data/doc/mactypes-manual/index.html +216 -0
- data/doc/osax-manual/index.html +169 -0
- data/extconf.rb +54 -0
- data/misc/adobeunittypes.rb +14 -0
- data/misc/dump.rb +72 -0
- data/rb-appscript.gemspec +20 -0
- data/sample/AB_list_people_with_emails.rb +8 -0
- data/sample/Create_daily_iCal_todos.rb +72 -0
- data/sample/Hello_world.rb +9 -0
- data/sample/List_iTunes_playlist_names.rb +7 -0
- data/sample/Make_Mail_message.rb +29 -0
- data/sample/Open_file_in_TextEdit.rb +9 -0
- data/sample/Organize_Mail_messages.rb +57 -0
- data/sample/Print_folder_tree.rb +12 -0
- data/sample/Select_all_HTML_files.rb +8 -0
- data/sample/Set_iChat_status.rb +20 -0
- data/sample/Simple_Finder_GUI_Scripting.rb +14 -0
- data/sample/Stagger_Finder_windows.rb +21 -0
- data/sample/TextEdit_demo.rb +126 -0
- data/sample/iTunes_top40_to_html.rb +64 -0
- data/src/lib/_aem/aemreference.rb +1006 -0
- data/src/lib/_aem/codecs.rb +617 -0
- data/src/lib/_aem/connect.rb +100 -0
- data/src/lib/_aem/findapp.rb +83 -0
- data/src/lib/_aem/mactypes.rb +228 -0
- data/src/lib/_aem/send.rb +257 -0
- data/src/lib/_aem/typewrappers.rb +57 -0
- data/src/lib/_appscript/defaultterminology.rb +245 -0
- data/src/lib/_appscript/referencerenderer.rb +132 -0
- data/src/lib/_appscript/reservedkeywords.rb +107 -0
- data/src/lib/_appscript/terminology.rb +314 -0
- data/src/lib/aem.rb +216 -0
- data/src/lib/appscript.rb +830 -0
- data/src/lib/kae.rb +1484 -0
- data/src/lib/osax.rb +171 -0
- data/src/rbae.c +766 -0
- data/test/README +1 -0
- data/test/test_aemreference.rb +112 -0
- data/test/test_appscriptreference.rb +102 -0
- data/test/test_codecs.rb +159 -0
- data/test/test_findapp.rb +24 -0
- data/test/test_mactypes.rb +67 -0
- data/test/testall.sh +9 -0
- 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
|