rb-appscript 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|