rb-scpt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +497 -0
  3. data/doc/aem-manual/01_introduction.html +60 -0
  4. data/doc/aem-manual/02_apioverview.html +107 -0
  5. data/doc/aem-manual/03_packingandunpackingdata.html +135 -0
  6. data/doc/aem-manual/04_references.html +409 -0
  7. data/doc/aem-manual/05_targetingapplications.html +164 -0
  8. data/doc/aem-manual/06_buildingandsendingevents.html +229 -0
  9. data/doc/aem-manual/07_findapp.html +63 -0
  10. data/doc/aem-manual/08_examples.html +94 -0
  11. data/doc/aem-manual/aemreferenceinheritance.gif +0 -0
  12. data/doc/aem-manual/index.html +56 -0
  13. data/doc/appscript-manual/01_introduction.html +94 -0
  14. data/doc/appscript-manual/02_aboutappscripting.html +247 -0
  15. data/doc/appscript-manual/03_quicktutorial.html +167 -0
  16. data/doc/appscript-manual/04_gettinghelp.html +188 -0
  17. data/doc/appscript-manual/05_keywordconversion.html +106 -0
  18. data/doc/appscript-manual/06_classesandenums.html +192 -0
  19. data/doc/appscript-manual/07_applicationobjects.html +211 -0
  20. data/doc/appscript-manual/08_realvsgenericreferences.html +96 -0
  21. data/doc/appscript-manual/09_referenceforms.html +241 -0
  22. data/doc/appscript-manual/10_referenceexamples.html +154 -0
  23. data/doc/appscript-manual/11_applicationcommands.html +245 -0
  24. data/doc/appscript-manual/12_commandexamples.html +138 -0
  25. data/doc/appscript-manual/13_performanceissues.html +142 -0
  26. data/doc/appscript-manual/14_notes.html +80 -0
  27. data/doc/appscript-manual/application_architecture.gif +0 -0
  28. data/doc/appscript-manual/application_architecture2.gif +0 -0
  29. data/doc/appscript-manual/finder_to_textedit_event.gif +0 -0
  30. data/doc/appscript-manual/index.html +62 -0
  31. data/doc/appscript-manual/relationships_example.gif +0 -0
  32. data/doc/appscript-manual/ruby_to_itunes_event.gif +0 -0
  33. data/doc/full.css +106 -0
  34. data/doc/index.html +45 -0
  35. data/doc/mactypes-manual/01_introduction.html +54 -0
  36. data/doc/mactypes-manual/02_aliasclass.html +124 -0
  37. data/doc/mactypes-manual/03_fileurlclass.html +126 -0
  38. data/doc/mactypes-manual/04_unitsclass.html +100 -0
  39. data/doc/mactypes-manual/index.html +53 -0
  40. data/doc/osax-manual/01_introduction.html +67 -0
  41. data/doc/osax-manual/02_interface.html +147 -0
  42. data/doc/osax-manual/03_examples.html +73 -0
  43. data/doc/osax-manual/04_notes.html +61 -0
  44. data/doc/osax-manual/index.html +53 -0
  45. data/doc/rb-appscript-logo.png +0 -0
  46. data/extconf.rb +65 -0
  47. data/rb-scpt.gemspec +14 -0
  48. data/sample/AB_export_vcard.rb +31 -0
  49. data/sample/AB_list_people_with_emails.rb +13 -0
  50. data/sample/Add_iCal_event.rb +21 -0
  51. data/sample/Create_daily_iCal_todos.rb +75 -0
  52. data/sample/Export_Address_Book_phone_numbers.rb +59 -0
  53. data/sample/Hello_world.rb +21 -0
  54. data/sample/List_iTunes_playlist_names.rb +11 -0
  55. data/sample/Make_Mail_message.rb +33 -0
  56. data/sample/Open_file_in_TextEdit.rb +13 -0
  57. data/sample/Organize_Mail_messages.rb +61 -0
  58. data/sample/Print_folder_tree.rb +16 -0
  59. data/sample/Select_all_HTML_files.rb +14 -0
  60. data/sample/Set_iChat_status.rb +24 -0
  61. data/sample/Simple_Finder_GUI_Scripting.rb +18 -0
  62. data/sample/Stagger_Finder_windows.rb +25 -0
  63. data/sample/TextEdit_demo.rb +130 -0
  64. data/sample/iTunes_top40_to_html.rb +71 -0
  65. data/src/SendThreadSafe.c +380 -0
  66. data/src/SendThreadSafe.h +139 -0
  67. data/src/lib/_aem/aemreference.rb +1022 -0
  68. data/src/lib/_aem/codecs.rb +662 -0
  69. data/src/lib/_aem/connect.rb +205 -0
  70. data/src/lib/_aem/encodingsupport.rb +77 -0
  71. data/src/lib/_aem/findapp.rb +85 -0
  72. data/src/lib/_aem/mactypes.rb +251 -0
  73. data/src/lib/_aem/send.rb +279 -0
  74. data/src/lib/_aem/typewrappers.rb +59 -0
  75. data/src/lib/_appscript/defaultterminology.rb +277 -0
  76. data/src/lib/_appscript/referencerenderer.rb +245 -0
  77. data/src/lib/_appscript/reservedkeywords.rb +116 -0
  78. data/src/lib/_appscript/safeobject.rb +249 -0
  79. data/src/lib/_appscript/terminology.rb +471 -0
  80. data/src/lib/aem.rb +253 -0
  81. data/src/lib/appscript.rb +1075 -0
  82. data/src/lib/kae.rb +1489 -0
  83. data/src/lib/osax.rb +659 -0
  84. data/src/rbae.c +979 -0
  85. data/test/README +3 -0
  86. data/test/test_aemreference.rb +118 -0
  87. data/test/test_appscriptcommands.rb +152 -0
  88. data/test/test_appscriptreference.rb +106 -0
  89. data/test/test_codecs.rb +186 -0
  90. data/test/test_findapp.rb +26 -0
  91. data/test/test_mactypes.rb +79 -0
  92. data/test/test_osax.rb +54 -0
  93. data/test/testall.sh +10 -0
  94. metadata +145 -0
@@ -0,0 +1,979 @@
1
+ /*
2
+ * rb-appscript
3
+ *
4
+ * ae -- a low-level API providing a basic Ruby wrapper around the various
5
+ * Apple Event Manager, Process Manager and Launch Services APIs used by aem
6
+ *
7
+ * Thanks to:
8
+ * - FUJIMOTO Hisakuni, author of RubyAEOSA
9
+ * - Jordan Breeding (64-bit support patch)
10
+ */
11
+
12
+ #include "osx_ruby.h"
13
+ #include <Carbon/Carbon.h>
14
+ #include <CoreFoundation/CoreFoundation.h>
15
+ #include "SendThreadSafe.h"
16
+
17
+ // AE module and classes
18
+ static VALUE mAE;
19
+ static VALUE cAEDesc;
20
+ static VALUE cMacOSError;
21
+
22
+ // Note: AEDescs need extra wrapping to avoid nasty problems with Ruby's Data_Wrap_Struct.
23
+ struct rbAE_AEDescWrapper {
24
+ AEDesc desc;
25
+ };
26
+
27
+ // (these two macros are basically cribbed from RubyAEOSA's aedesc.c)
28
+ #define AEDESC_DATA_PTR(o) ((struct rbAE_AEDescWrapper*)(DATA_PTR(o)))
29
+ #define AEDESC_OF(o) (AEDESC_DATA_PTR(o)->desc)
30
+
31
+ // Event handling
32
+ #if __LP64__
33
+ // SRefCon typedefed as void * by system headers
34
+ #else
35
+ typedef long SRefCon;
36
+ #endif
37
+
38
+ AEEventHandlerUPP upp_GenericEventHandler;
39
+ AECoercionHandlerUPP upp_GenericCoercionHandler;
40
+
41
+
42
+ // these macros were added in 1.8.6
43
+
44
+ #if !defined(RSTRING_LEN)
45
+ #define RSTRING_LEN(x) (RSTRING(x)->len)
46
+ #define RSTRING_PTR(x) (RSTRING(x)->ptr)
47
+ #endif
48
+
49
+
50
+ /**********************************************************************/
51
+ // Raise MacOS error
52
+
53
+ /*
54
+ * Note: MacOSError should only be raised by AE module; attempting to raise it from Ruby
55
+ * just results in unexpected errors. (I've not quite figured out how to implement an
56
+ * Exception class in C that constructs correctly in both C and Ruby. Not serious, since
57
+ * nobody else needs to raise MacOSErrors - just a bit irritating.)
58
+ */
59
+
60
+ static void
61
+ rbAE_raiseMacOSError(const char *description, OSErr number)
62
+ {
63
+ VALUE errObj;
64
+
65
+ errObj = rb_funcall(cMacOSError, rb_intern("new"), 0);
66
+ rb_iv_set(errObj, "@number", INT2NUM(number)); // returns the OS error number
67
+ rb_iv_set(errObj, "@description", rb_str_new2(description)); // troubleshooting info
68
+ rb_exc_raise(errObj);
69
+ }
70
+
71
+
72
+ /**********************************************************************/
73
+ // MacOSError methods
74
+
75
+ static VALUE
76
+ rbAE_MacOSError_inspect(VALUE self)
77
+ {
78
+ char s[32];
79
+
80
+ sprintf(s, "#<AE::MacOSError %li>", (long)NUM2INT(rb_iv_get(self, "@number")));
81
+ return rb_str_new2(s);
82
+ }
83
+
84
+
85
+ /**********************************************************************/
86
+ // AEDesc support functions
87
+
88
+ static DescType
89
+ rbStringToDescType(VALUE obj)
90
+ {
91
+ if (rb_obj_is_kind_of(obj, rb_cString) && RSTRING_LEN(obj) == 4) {
92
+ return CFSwapInt32HostToBig(*(DescType *)(RSTRING_PTR(obj)));
93
+ } else {
94
+ rb_raise(rb_eArgError, "Not a four-char-code string.");
95
+ }
96
+ }
97
+
98
+ static VALUE
99
+ rbDescTypeToString(DescType descType)
100
+ {
101
+ char s[4];
102
+
103
+ *(DescType*)s = CFSwapInt32HostToBig(descType);
104
+ return rb_str_new(s, 4);
105
+ }
106
+
107
+ /*******/
108
+
109
+ static void
110
+ rbAE_freeAEDesc(struct rbAE_AEDescWrapper *p)
111
+ {
112
+ AEDisposeDesc(&(p->desc));
113
+ free(p);
114
+ }
115
+
116
+ static VALUE
117
+ rbAE_wrapAEDesc(const AEDesc *desc)
118
+ {
119
+ struct rbAE_AEDescWrapper *wrapper;
120
+
121
+ // Found out how to wrap AEDescs so Ruby wouldn't crash by reading RubyAEOSA's aedesc.c
122
+ wrapper = malloc(sizeof(struct rbAE_AEDescWrapper));
123
+ wrapper->desc = *desc;
124
+ return Data_Wrap_Struct(cAEDesc, 0, rbAE_freeAEDesc, wrapper);
125
+ }
126
+
127
+ /*******/
128
+ // Note: clients should not attempt to use retain/use borrowed AE::AEDesc instances after handler callbacks return,
129
+ // as AEM will have disposed of the underlying AEDesc objects by then
130
+
131
+ static void
132
+ rbAE_freeBorrowedAEDesc(struct rbAE_AEDescWrapper *p)
133
+ {
134
+ free(p);
135
+ }
136
+
137
+ static VALUE
138
+ rbAE_wrapBorrowedAEDesc(const AEDesc *desc)
139
+ {
140
+ struct rbAE_AEDescWrapper *wrapper;
141
+
142
+ wrapper = malloc(sizeof(struct rbAE_AEDescWrapper));
143
+ wrapper->desc = *desc;
144
+ return Data_Wrap_Struct(cAEDesc, 0, rbAE_freeBorrowedAEDesc, wrapper);
145
+ }
146
+
147
+ /**********************************************************************/
148
+ // AEDesc constructors
149
+
150
+ static VALUE
151
+ rbAE_AEDesc_new(VALUE class, VALUE type, VALUE data)
152
+ {
153
+ OSErr err = noErr;
154
+ AEDesc desc;
155
+
156
+ Check_Type(data, T_STRING);
157
+ err = AECreateDesc(rbStringToDescType(type),
158
+ RSTRING_PTR(data), RSTRING_LEN(data),
159
+ &desc);
160
+ if (err != noErr) rbAE_raiseMacOSError("Can't create AEDesc.", err);
161
+ return rbAE_wrapAEDesc(&desc);
162
+ }
163
+
164
+
165
+ static VALUE
166
+ rbAE_AEDesc_newList(VALUE class, VALUE isRecord)
167
+ {
168
+ OSErr err = noErr;
169
+ AEDesc desc;
170
+
171
+ err = AECreateList(NULL, 0, RTEST(isRecord), &desc);
172
+ if (err != noErr) rbAE_raiseMacOSError("Can't create AEDescList.", err);
173
+ return rbAE_wrapAEDesc(&desc);
174
+ }
175
+
176
+
177
+ static VALUE
178
+ rbAE_AEDesc_newAppleEvent(VALUE class, VALUE eventClassValue, VALUE eventIDValue,
179
+ VALUE targetValue, VALUE returnIDValue, VALUE transactionIDValue)
180
+ {
181
+ OSErr err = noErr;
182
+ AEEventClass theAEEventClass = rbStringToDescType(eventClassValue);
183
+ AEEventID theAEEventID = rbStringToDescType(eventIDValue);
184
+ AEAddressDesc target = AEDESC_OF(targetValue);
185
+ AEReturnID returnID = NUM2INT(returnIDValue);
186
+ AETransactionID transactionID = NUM2LONG(transactionIDValue);
187
+ AppleEvent result;
188
+
189
+ err = AECreateAppleEvent(theAEEventClass,
190
+ theAEEventID,
191
+ &target,
192
+ returnID,
193
+ transactionID,
194
+ &result);
195
+ if (err != noErr) rbAE_raiseMacOSError("Can't create AppleEvent.", err);
196
+ // workaround for return ID bug in 10.6
197
+ AEDesc returnIDDesc;
198
+ if (returnID == kAutoGenerateReturnID) {
199
+ err = AEGetAttributeDesc(&result, keyReturnIDAttr, typeSInt32, &returnIDDesc);
200
+ if (err != noErr) rbAE_raiseMacOSError("Can't create AppleEvent.", err);
201
+ err = AEGetDescData(&returnIDDesc, &returnID, sizeof(returnID));
202
+ if (err != noErr) rbAE_raiseMacOSError("Can't create AppleEvent.", err);
203
+ if (returnID == -1) {
204
+ AEDisposeDesc(&result);
205
+ err = AECreateAppleEvent(theAEEventClass,
206
+ theAEEventID,
207
+ &target,
208
+ returnID,
209
+ transactionID,
210
+ &result);
211
+ if (err != noErr) rbAE_raiseMacOSError("Can't create AppleEvent.", err);
212
+ }
213
+ }
214
+ return rbAE_wrapAEDesc(&result);
215
+ }
216
+
217
+
218
+ static VALUE
219
+ rbAE_AEDesc_newUnflatten(VALUE class, VALUE data)
220
+ {
221
+ OSErr err = noErr;
222
+ AEDesc desc;
223
+
224
+ Check_Type(data, T_STRING);
225
+ err = AEUnflattenDesc(RSTRING_PTR(data), &desc);
226
+ if (err != noErr) rbAE_raiseMacOSError("Can't create AEDesc.", err);
227
+ return rbAE_wrapAEDesc(&desc);
228
+ }
229
+
230
+
231
+ /**********************************************************************/
232
+ // AEDesc methods
233
+
234
+ static VALUE
235
+ rbAE_AEDesc_inspect(VALUE self)
236
+ {
237
+ VALUE s, type;
238
+ Size dataSize;
239
+
240
+ s = rb_str_new2("#<AE::AEDesc type=%s size=%i>");
241
+ type = rb_funcall(self, rb_intern("type"), 0);
242
+ dataSize = AEGetDescDataSize(&(AEDESC_OF(self)));
243
+ return rb_funcall(s,
244
+ rb_intern("%"),
245
+ 1,
246
+ rb_ary_new3(2,
247
+ rb_funcall(type, rb_intern("inspect"), 0),
248
+ INT2NUM(dataSize)
249
+ )
250
+ );
251
+ }
252
+
253
+
254
+ /*******/
255
+
256
+ static VALUE
257
+ rbAE_AEDesc_type(VALUE self)
258
+ {
259
+ return rbDescTypeToString(AEDESC_OF(self).descriptorType);
260
+ }
261
+
262
+
263
+ static VALUE
264
+ rbAE_AEDesc_data(VALUE self)
265
+ {
266
+ OSErr err = noErr;
267
+ Size dataSize;
268
+ void *data;
269
+ VALUE result;
270
+
271
+ dataSize = AEGetDescDataSize(&(AEDESC_OF(self)));
272
+ data = malloc(dataSize);
273
+ err = AEGetDescData(&(AEDESC_OF(self)), data, dataSize);
274
+ if (err != noErr) rbAE_raiseMacOSError("Can't get AEDesc data.", err);
275
+ result = rb_str_new(data, dataSize);
276
+ free(data);
277
+ return result;
278
+ }
279
+
280
+
281
+ static VALUE
282
+ rbAE_AEDesc_flatten(VALUE self)
283
+ {
284
+ OSErr err = noErr;
285
+ Size dataSize;
286
+ void *data;
287
+ VALUE result;
288
+
289
+ dataSize = AESizeOfFlattenedDesc(&(AEDESC_OF(self)));
290
+ data = malloc(dataSize);
291
+ err = AEFlattenDesc(&(AEDESC_OF(self)), data, dataSize, NULL);
292
+ if (err != noErr) rbAE_raiseMacOSError("Can't flatten AEDesc.", err);
293
+ result = rb_str_new(data, dataSize);
294
+ free(data);
295
+ return result;
296
+ }
297
+
298
+
299
+ /*******/
300
+
301
+ static VALUE
302
+ rbAE_AEDesc_isRecord(VALUE self)
303
+ {
304
+ return AECheckIsRecord(&(AEDESC_OF(self))) ? Qtrue : Qfalse;
305
+ }
306
+
307
+
308
+ static VALUE
309
+ rbAE_AEDesc_coerce(VALUE self, VALUE type)
310
+ {
311
+ OSErr err = noErr;
312
+ AEDesc desc;
313
+
314
+ err = AECoerceDesc(&(AEDESC_OF(self)), rbStringToDescType(type), &desc);
315
+ if (err != noErr) rbAE_raiseMacOSError("Can't coerce AEDesc.", err);
316
+ return rbAE_wrapAEDesc(&desc);
317
+ }
318
+
319
+
320
+ static VALUE
321
+ rbAE_AEDesc_length(VALUE self)
322
+ {
323
+ OSErr err = noErr;
324
+ long length;
325
+
326
+ err = AECountItems(&(AEDESC_OF(self)), &length);
327
+ if (err != noErr) rbAE_raiseMacOSError("Can't get length of AEDesc.", err);
328
+ return INT2NUM(length);
329
+ }
330
+
331
+
332
+ /*******/
333
+
334
+ static VALUE
335
+ rbAE_AEDesc_putItem(VALUE self, VALUE index, VALUE desc)
336
+ {
337
+ OSErr err = noErr;
338
+
339
+ if (rb_obj_is_instance_of(desc, cAEDesc) == Qfalse)
340
+ rb_raise(rb_eTypeError, "Can't put non-AEDesc item into AEDesc.");
341
+ err = AEPutDesc(&(AEDESC_OF(self)), NUM2LONG(index), &(AEDESC_OF(desc)));
342
+ if (err != noErr) rbAE_raiseMacOSError("Can't put item into AEDesc.", err);
343
+ return Qnil;
344
+ }
345
+
346
+
347
+ static VALUE
348
+ rbAE_AEDesc_putParam(VALUE self, VALUE key, VALUE desc)
349
+ {
350
+ OSErr err = noErr;
351
+
352
+ if (rb_obj_is_instance_of(desc, cAEDesc) == Qfalse)
353
+ rb_raise(rb_eTypeError, "Can't put non-AEDesc parameter into AEDesc.");
354
+ err = AEPutParamDesc(&(AEDESC_OF(self)), rbStringToDescType(key), &(AEDESC_OF(desc)));
355
+ if (err != noErr) rbAE_raiseMacOSError("Can't put parameter into AEDesc.", err);
356
+ return Qnil;
357
+ }
358
+
359
+
360
+ static VALUE
361
+ rbAE_AEDesc_putAttr(VALUE self, VALUE key, VALUE desc)
362
+ {
363
+ OSErr err = noErr;
364
+
365
+ if (rb_obj_is_instance_of(desc, cAEDesc) == Qfalse)
366
+ rb_raise(rb_eTypeError, "Can't put non-AEDesc attribute into AEDesc.");
367
+ err = AEPutAttributeDesc(&(AEDESC_OF(self)), rbStringToDescType(key), &(AEDESC_OF(desc)));
368
+ if (err != noErr) rbAE_raiseMacOSError("Can't put attribute into AEDesc.", err);
369
+ return Qnil;
370
+ }
371
+
372
+
373
+ /*******/
374
+
375
+ static VALUE
376
+ rbAE_AEDesc_getItem(VALUE self, VALUE index, VALUE type)
377
+ {
378
+ OSErr err = noErr;
379
+ AEKeyword key;
380
+ AEDesc desc;
381
+
382
+ err = AEGetNthDesc(&(AEDESC_OF(self)),
383
+ NUM2LONG(index),
384
+ rbStringToDescType(type),
385
+ &key,
386
+ &desc);
387
+ if (err != noErr) rbAE_raiseMacOSError("Can't get item from AEDesc.", err);
388
+ return rb_ary_new3(2,
389
+ rbDescTypeToString(key),
390
+ rbAE_wrapAEDesc(&desc));
391
+ }
392
+
393
+
394
+ static VALUE
395
+ rbAE_AEDesc_getParam(VALUE self, VALUE key, VALUE type)
396
+ {
397
+ OSErr err = noErr;
398
+ AEDesc desc;
399
+
400
+ err = AEGetParamDesc(&(AEDESC_OF(self)),
401
+ rbStringToDescType(key),
402
+ rbStringToDescType(type),
403
+ &desc);
404
+ if (err != noErr) rbAE_raiseMacOSError("Can't get parameter from AEDesc.", err);
405
+ return rbAE_wrapAEDesc(&desc);
406
+ }
407
+
408
+
409
+ static VALUE
410
+ rbAE_AEDesc_getAttr(VALUE self, VALUE key, VALUE type)
411
+ {
412
+ OSErr err = noErr;
413
+ AEDesc desc;
414
+
415
+ err = AEGetAttributeDesc(&(AEDESC_OF(self)),
416
+ rbStringToDescType(key),
417
+ rbStringToDescType(type),
418
+ &desc);
419
+ if (err != noErr) rbAE_raiseMacOSError("Can't get attribute from AEDesc.", err);
420
+ return rbAE_wrapAEDesc(&desc);
421
+ }
422
+
423
+
424
+ /*******/
425
+
426
+ static VALUE
427
+ rbAE_AEDesc_send(VALUE self, VALUE sendMode, VALUE timeout)
428
+ {
429
+ OSErr err = noErr;
430
+ AppleEvent reply;
431
+
432
+ err = AESendMessage(&(AEDESC_OF(self)),
433
+ &reply,
434
+ (AESendMode)NUM2LONG(sendMode),
435
+ NUM2LONG(timeout));
436
+ if (err != noErr) rbAE_raiseMacOSError("Can't send Apple event.", err);
437
+ return rbAE_wrapAEDesc(&reply);
438
+ }
439
+
440
+ static VALUE
441
+ rbAE_AEDesc_sendThreadSafe(VALUE self, VALUE sendMode, VALUE timeout)
442
+ {
443
+ OSErr err = noErr;
444
+ AppleEvent reply;
445
+
446
+ err = SendMessageThreadSafe(&(AEDESC_OF(self)),
447
+ &reply,
448
+ (AESendMode)NUM2LONG(sendMode),
449
+ NUM2LONG(timeout));
450
+ if (err != noErr) rbAE_raiseMacOSError("Can't send Apple event.", err);
451
+ return rbAE_wrapAEDesc(&reply);
452
+ }
453
+
454
+
455
+ /**********************************************************************/
456
+ // Find and launch applications
457
+
458
+ static VALUE
459
+ rbAE_findApplication(VALUE self, VALUE creator, VALUE bundleID, VALUE name)
460
+ {
461
+ OSStatus err = 0;
462
+
463
+ OSType inCreator;
464
+ CFStringRef inName;
465
+ CFStringRef inBundleID;
466
+ FSRef outAppRef;
467
+ UInt8 path[PATH_MAX];
468
+
469
+ inCreator = (creator == Qnil) ? kLSUnknownCreator : rbStringToDescType(creator);
470
+ if (bundleID != Qnil) {
471
+ inBundleID = CFStringCreateWithBytes(NULL,
472
+ (UInt8 *)(RSTRING_PTR(bundleID)),
473
+ (CFIndex)(RSTRING_LEN(bundleID)),
474
+ kCFStringEncodingUTF8,
475
+ false);
476
+ if (inBundleID == NULL) rb_raise(rb_eRuntimeError, "Invalid bundle ID string.");
477
+ } else {
478
+ inBundleID = NULL;
479
+ }
480
+ if (name != Qnil) {
481
+ inName = CFStringCreateWithBytes(NULL,
482
+ (UInt8 *)(RSTRING_PTR(name)),
483
+ (CFIndex)(RSTRING_LEN(name)),
484
+ kCFStringEncodingUTF8,
485
+ false);
486
+ if (inName == NULL) {
487
+ if (inBundleID != NULL) CFRelease(inBundleID);
488
+ rb_raise(rb_eRuntimeError, "Invalid name string.");
489
+ }
490
+ } else {
491
+ inName = NULL;
492
+ }
493
+ err = LSFindApplicationForInfo(inCreator,
494
+ inBundleID,
495
+ inName,
496
+ &outAppRef,
497
+ NULL);
498
+ if (inBundleID != NULL) CFRelease(inBundleID);
499
+ if (inName != NULL) CFRelease(inName);
500
+ if (err != 0) rbAE_raiseMacOSError("Couldn't find application.", err);
501
+ err = FSRefMakePath(&outAppRef, path, PATH_MAX);
502
+ if (err != 0) rbAE_raiseMacOSError("Couldn't get application path.", err);
503
+ return rb_str_new2((char *)path);
504
+ }
505
+
506
+
507
+ static VALUE
508
+ rbAE_psnForApplicationPath(VALUE self, VALUE path)
509
+ {
510
+ OSStatus err = noErr;
511
+ ProcessSerialNumber psn = {0, kNoProcess};
512
+ FSRef appRef, foundRef;
513
+
514
+ err = FSPathMakeRef((UInt8 *)StringValuePtr(path), &appRef, NULL);
515
+ if (err != 0) rbAE_raiseMacOSError("Couldn't make FSRef for application.", err);
516
+ while (1) {
517
+ err = GetNextProcess(&psn);
518
+ if (err != 0) rbAE_raiseMacOSError("Can't get next process.", err); // -600 if no more processes left
519
+ err = GetProcessBundleLocation(&psn, &foundRef);
520
+ if (err != 0) continue;
521
+ if (FSCompareFSRefs(&appRef, &foundRef) == noErr)
522
+ return rb_ary_new3(2, INT2NUM(psn.highLongOfPSN), INT2NUM(psn.lowLongOfPSN));
523
+ }
524
+ }
525
+
526
+
527
+ static VALUE
528
+ rbAE_psnForPID(VALUE self, VALUE pid)
529
+ {
530
+ OSStatus err = noErr;
531
+ ProcessSerialNumber psn = {0, kNoProcess};
532
+
533
+ err = GetProcessForPID(NUM2INT(pid), &psn);
534
+ if (err != 0) rbAE_raiseMacOSError("Can't get next process.", err); // -600 if process not found
535
+ return rb_ary_new3(2, INT2NUM(psn.highLongOfPSN), INT2NUM(psn.lowLongOfPSN));
536
+ }
537
+
538
+
539
+ static VALUE
540
+ rbAE_launchApplication(VALUE self, VALUE path, VALUE firstEvent, VALUE flags)
541
+ {
542
+ FSRef appRef;
543
+ ProcessSerialNumber psn;
544
+ OSStatus err = noErr;
545
+
546
+ err = FSPathMakeRef((UInt8 *)StringValuePtr(path), &appRef, NULL);
547
+ if (err != noErr) rbAE_raiseMacOSError("Couldn't make FSRef for application.", err);
548
+ LSApplicationParameters appParams = {0,
549
+ (LSLaunchFlags)NUM2UINT(flags),
550
+ &appRef,
551
+ NULL, NULL, NULL,
552
+ &(AEDESC_OF(firstEvent))};
553
+ err = LSOpenApplication(&appParams, &psn);
554
+ if (err != noErr) rbAE_raiseMacOSError("Can't launch application.", err);
555
+ return rb_ary_new3(2, INT2NUM(psn.highLongOfPSN), INT2NUM(psn.lowLongOfPSN));
556
+ }
557
+
558
+ /**********************************************************************/
559
+ // HFS/POSIX path conversions
560
+
561
+ static VALUE
562
+ rbAE_convertPathToURL(VALUE self, VALUE path, VALUE pathStyle)
563
+ {
564
+ CFStringRef str;
565
+ CFURLRef url;
566
+ UInt8 buffer[PATH_MAX];
567
+
568
+ str = CFStringCreateWithBytes(NULL,
569
+ (UInt8 *)(RSTRING_PTR(path)),
570
+ (CFIndex)(RSTRING_LEN(path)),
571
+ kCFStringEncodingUTF8,
572
+ false);
573
+ if (str == NULL) rb_raise(rb_eRuntimeError, "Bad path string.");
574
+ url = CFURLCreateWithFileSystemPath(NULL,
575
+ str,
576
+ NUM2LONG(pathStyle),
577
+ false);
578
+ CFRelease(str);
579
+ if (url == NULL) rb_raise(rb_eRuntimeError, "Invalid path.");
580
+ buffer[CFURLGetBytes(url, buffer, PATH_MAX - 1)] = '\0';
581
+ CFRelease(url);
582
+ return rb_str_new2((char *)buffer);
583
+ }
584
+
585
+ static VALUE
586
+ rbAE_convertURLToPath(VALUE self, VALUE urlStr, VALUE pathStyle)
587
+ {
588
+ Boolean err;
589
+ CFURLRef url;
590
+ CFStringRef str;
591
+ char buffer[PATH_MAX];
592
+
593
+ url = CFURLCreateWithBytes(NULL,
594
+ (UInt8 *)(RSTRING_PTR(urlStr)),
595
+ (CFIndex)(RSTRING_LEN(urlStr)),
596
+ kCFStringEncodingUTF8,
597
+ NULL);
598
+ if (url == NULL) rb_raise(rb_eRuntimeError, "Bad URL string.");
599
+ str = CFURLCopyFileSystemPath(url, NUM2LONG(pathStyle));
600
+ CFRelease(url);
601
+ if (str == NULL) rb_raise(rb_eRuntimeError, "Can't get path.");
602
+ err = CFStringGetCString(str,
603
+ buffer,
604
+ PATH_MAX,
605
+ kCFStringEncodingUTF8);
606
+ CFRelease(str);
607
+ if (!err) rb_raise(rb_eRuntimeError, "Can't get path.");
608
+ return rb_str_new2(buffer);
609
+ }
610
+
611
+
612
+ /**********************************************************************/
613
+ // Date conversion
614
+
615
+ static VALUE
616
+ rbAE_convertLongDateTimeToString(VALUE self, VALUE ldt)
617
+ {
618
+ Boolean bErr;
619
+ OSStatus err = 0;
620
+ CFAbsoluteTime cfTime;
621
+ CFDateFormatterRef formatter;
622
+ CFStringRef str;
623
+ char buffer[20]; // size of format string + nul
624
+
625
+ err = UCConvertLongDateTimeToCFAbsoluteTime(NUM2LL(ldt), &cfTime);
626
+ if (err != noErr) rbAE_raiseMacOSError("Can't convert LongDateTime to seconds.", err);
627
+ formatter = CFDateFormatterCreate(NULL, NULL, kCFDateFormatterNoStyle, kCFDateFormatterNoStyle);
628
+ if (!formatter) rbAE_raiseMacOSError("Can't create date formatter.", err);
629
+ CFDateFormatterSetFormat(formatter, CFSTR("yyyy-MM-dd HH:mm:ss"));
630
+ str = CFDateFormatterCreateStringWithAbsoluteTime(NULL, formatter, cfTime);
631
+ CFRelease(formatter);
632
+ if (!str) rbAE_raiseMacOSError("Can't create date string.", err);
633
+ bErr = CFStringGetCString(str,
634
+ buffer,
635
+ sizeof(buffer),
636
+ kCFStringEncodingUTF8);
637
+ CFRelease(str);
638
+ if (!bErr) rb_raise(rb_eRuntimeError, "Can't convert date string.");
639
+ return rb_str_new2(buffer);
640
+ }
641
+
642
+
643
+ static VALUE
644
+ rbAE_convertStringToLongDateTime(VALUE self, VALUE datetime)
645
+ {
646
+ CFStringRef str;
647
+ CFAbsoluteTime cfTime;
648
+ CFDateFormatterRef formatter;
649
+ OSStatus err = 0;
650
+ Boolean bErr;
651
+ SInt64 ldt;
652
+
653
+ str = CFStringCreateWithBytes(NULL,
654
+ (UInt8 *)(RSTRING_PTR(datetime)),
655
+ (CFIndex)(RSTRING_LEN(datetime)),
656
+ kCFStringEncodingUTF8,
657
+ false);
658
+ if (str == NULL || CFStringGetLength(str) != 19) rb_raise(rb_eRuntimeError, "Bad datetime string.");
659
+ formatter = CFDateFormatterCreate(NULL, NULL, kCFDateFormatterNoStyle, kCFDateFormatterNoStyle);
660
+ if (!formatter) rbAE_raiseMacOSError("Can't create date formatter.", err);
661
+ CFDateFormatterSetFormat(formatter, CFSTR("yyyy-MM-dd HH:mm:ss"));
662
+ bErr = CFDateFormatterGetAbsoluteTimeFromString(formatter, str, NULL, &cfTime);
663
+ CFRelease(formatter);
664
+ CFRelease(str);
665
+ if (!bErr) rb_raise(rb_eRuntimeError, "Can't convert date string.");
666
+ err = UCConvertCFAbsoluteTimeToLongDateTime(cfTime, &ldt);
667
+ if (err != noErr) rbAE_raiseMacOSError("Can't convert seconds to LongDateTime.", err);
668
+ return LL2NUM(ldt);
669
+ }
670
+
671
+
672
+ static VALUE
673
+ rbAE_convertLongDateTimeToUnixSeconds(VALUE self, VALUE ldt)
674
+ {
675
+ OSStatus err = 0;
676
+ CFAbsoluteTime cfTime;
677
+
678
+ err = UCConvertLongDateTimeToCFAbsoluteTime(NUM2LL(ldt), &cfTime);
679
+ if (err != noErr) rbAE_raiseMacOSError("Can't convert LongDateTime to seconds.", err);
680
+ return rb_float_new(cfTime + kCFAbsoluteTimeIntervalSince1970);
681
+ }
682
+
683
+
684
+ static VALUE
685
+ rbAE_convertUnixSecondsToLongDateTime(VALUE self, VALUE secs)
686
+ {
687
+ OSStatus err = 0;
688
+ SInt64 ldt;
689
+
690
+ err = UCConvertCFAbsoluteTimeToLongDateTime(NUM2DBL(secs) - kCFAbsoluteTimeIntervalSince1970, &ldt);
691
+ if (err != noErr) rbAE_raiseMacOSError("Can't convert seconds to LongDateTime.", err);
692
+ return LL2NUM(ldt);
693
+ }
694
+
695
+
696
+ /**********************************************************************/
697
+ // Get aete
698
+
699
+ static VALUE
700
+ rbAE_OSACopyScriptingDefinition(VALUE self, VALUE path)
701
+ {
702
+ FSRef fsRef;
703
+ CFDataRef sdef;
704
+ CFIndex dataSize;
705
+ char *data;
706
+ VALUE res;
707
+ OSErr err = noErr;
708
+
709
+ err = FSPathMakeRef((UInt8 *)StringValuePtr(path), &fsRef, NULL);
710
+ if (err != 0) rbAE_raiseMacOSError("Couldn't make FSRef for path.", err);
711
+ err = OSACopyScriptingDefinition(&fsRef, 0, &sdef);
712
+ if (err) rbAE_raiseMacOSError("Couldn't get sdef.", err);
713
+ dataSize = CFDataGetLength(sdef);
714
+ data = (char *)CFDataGetBytePtr(sdef);
715
+ if (data != NULL) {
716
+ res = rb_str_new(data, dataSize);
717
+ } else {
718
+ data = malloc(dataSize);
719
+ CFDataGetBytes(sdef, CFRangeMake(0, dataSize), (UInt8 *)data);
720
+ res = rb_str_new(data, dataSize);
721
+ free(data);
722
+ }
723
+ CFRelease(sdef);
724
+ return res;
725
+ }
726
+
727
+
728
+ /**********************************************************************/
729
+ // Install event handlers
730
+
731
+ // Based on Python's CarbonX.AE extension
732
+
733
+ static pascal OSErr
734
+ rbAE_GenericEventHandler(const AppleEvent *request, AppleEvent *reply, SRefCon refcon)
735
+ {
736
+ VALUE err;
737
+
738
+ err = rb_funcall((VALUE)refcon,
739
+ rb_intern("handle_event"),
740
+ 2,
741
+ rbAE_wrapBorrowedAEDesc(request),
742
+ rbAE_wrapBorrowedAEDesc(reply));
743
+ return NUM2INT(err);
744
+ }
745
+
746
+ /*******/
747
+
748
+ static VALUE
749
+ rbAE_AEInstallEventHandler(VALUE self, VALUE eventClass, VALUE eventID, SRefCon handler)
750
+ {
751
+ /*
752
+ * eventClass and eventID must be four-character code strings
753
+ *
754
+ * handler must be a Ruby object containing a method named 'handle_event' that takes two
755
+ * AppleEvent descriptors (request and reply) as arguments, and returns an integer.
756
+ * Note that this object is responsible for trapping any unhandled exceptions and returning
757
+ * an OS error number as appropriate (or 0 if no error), otherwise the program will exit.
758
+ */
759
+ OSErr err = noErr;
760
+
761
+ err = AEInstallEventHandler(rbStringToDescType(eventClass),
762
+ rbStringToDescType(eventID),
763
+ upp_GenericEventHandler, handler,
764
+ 0);
765
+ if (err != noErr) rbAE_raiseMacOSError("Can't install event handler.", err);
766
+ return Qnil;
767
+ }
768
+
769
+
770
+ static VALUE
771
+ rbAE_AERemoveEventHandler(VALUE self, VALUE eventClass, VALUE eventID)
772
+ {
773
+ OSErr err = noErr;
774
+
775
+ err = AERemoveEventHandler(rbStringToDescType(eventClass),
776
+ rbStringToDescType(eventID),
777
+ upp_GenericEventHandler,
778
+ 0);
779
+ if (err != noErr) rbAE_raiseMacOSError("Can't remove event handler.", err);
780
+ return Qnil;
781
+ }
782
+
783
+
784
+ static VALUE
785
+ rbAE_AEGetEventHandler(VALUE self, VALUE eventClass, VALUE eventID)
786
+ {
787
+ OSErr err = noErr;
788
+ AEEventHandlerUPP handlerUPP;
789
+ SRefCon handler;
790
+
791
+ err = AEGetEventHandler(rbStringToDescType(eventClass),
792
+ rbStringToDescType(eventID),
793
+ &handlerUPP, &handler,
794
+ 0);
795
+ if (err != noErr) rbAE_raiseMacOSError("Can't get event handler.", err);
796
+ return (VALUE)handler;
797
+ }
798
+
799
+
800
+ /**********************************************************************/
801
+ // Install coercion handlers
802
+
803
+ static pascal OSErr
804
+ rbAE_GenericCoercionHandler(const AEDesc *fromDesc, DescType toType, SRefCon refcon, AEDesc *toDesc)
805
+ {
806
+ // handle_coercion method should return an AE::AEDesc, or nil if an error occurred
807
+ OSErr err = noErr;
808
+ VALUE res;
809
+
810
+ res = rb_funcall((VALUE)refcon,
811
+ rb_intern("handle_coercion"),
812
+ 2,
813
+ rbAE_wrapBorrowedAEDesc(fromDesc),
814
+ rbDescTypeToString(toType));
815
+ if (rb_obj_is_instance_of(res, cAEDesc) != Qtrue) return errAECoercionFail;
816
+ err = AEDuplicateDesc(&AEDESC_OF(res), toDesc);
817
+ return err;
818
+ }
819
+
820
+
821
+ /*******/
822
+
823
+ static VALUE
824
+ rbAE_AEInstallCoercionHandler(VALUE self, VALUE fromType, VALUE toType, SRefCon handler)
825
+ {
826
+ /*
827
+ * fromType and toType must be four-character code strings
828
+ *
829
+ * handler must be a Ruby object containing a method named 'handle_coercion' that takes an
830
+ * AEDesc and a four-character code (original value, desired type) as arguments, and returns an
831
+ * AEDesc of the desired type.Note that this object is responsible for trapping any unhandled
832
+ * exceptions and returning nil (or any other non-AEDesc value) as appropriate, otherwise the
833
+ * program will exit.
834
+ */
835
+ OSErr err = noErr;
836
+
837
+ err = AEInstallCoercionHandler(rbStringToDescType(fromType),
838
+ rbStringToDescType(toType),
839
+ upp_GenericCoercionHandler, handler,
840
+ 1, 0);
841
+ if (err != noErr) rbAE_raiseMacOSError("Can't install coercion handler.", err);
842
+ return Qnil;
843
+ }
844
+
845
+
846
+ static VALUE
847
+ rbAE_AERemoveCoercionHandler(VALUE self, VALUE fromType, VALUE toType)
848
+ {
849
+ OSErr err = noErr;
850
+
851
+ err = AERemoveCoercionHandler(rbStringToDescType(fromType),
852
+ rbStringToDescType(toType),
853
+ upp_GenericCoercionHandler,
854
+ 0);
855
+ if (err != noErr) rbAE_raiseMacOSError("Can't remove coercion handler.", err);
856
+ return Qnil;
857
+ }
858
+
859
+
860
+ static VALUE
861
+ rbAE_AEGetCoercionHandler(VALUE self, VALUE fromType, VALUE toType)
862
+ {
863
+ OSErr err = noErr;
864
+ AECoercionHandlerUPP handlerUPP;
865
+ SRefCon handler;
866
+ Boolean fromTypeIsDesc;
867
+
868
+ err = AEGetCoercionHandler(rbStringToDescType(fromType),
869
+ rbStringToDescType(toType),
870
+ &handlerUPP, &handler,
871
+ &fromTypeIsDesc,
872
+ 0);
873
+ if (err != noErr) rbAE_raiseMacOSError("Can't get coercion handler.", err);
874
+ return rb_ary_new3(2, handler, fromTypeIsDesc ? Qtrue : Qfalse);
875
+ }
876
+
877
+
878
+
879
+
880
+ /**********************************************************************/
881
+ // Process management
882
+ static VALUE
883
+ rbAE_transformProcessToForegroundApplication(VALUE self)
884
+ {
885
+ OSStatus err = 0;
886
+ ProcessSerialNumber psn = {0, kCurrentProcess};
887
+
888
+ err = TransformProcessType(& psn, kProcessTransformToForegroundApplication);
889
+ if( err != 0) rbAE_raiseMacOSError("Can't transform process.", err);
890
+ return Qnil;
891
+ }
892
+
893
+
894
+ /**********************************************************************/
895
+ // Initialisation
896
+
897
+ void
898
+ Init_ae (void)
899
+ {
900
+
901
+ mAE = rb_define_module("AE");
902
+
903
+ // AE::AEDesc
904
+
905
+ cAEDesc = rb_define_class_under(mAE, "AEDesc", rb_cObject);
906
+
907
+ rb_define_singleton_method(cAEDesc, "new", rbAE_AEDesc_new, 2);
908
+ rb_define_singleton_method(cAEDesc, "new_list", rbAE_AEDesc_newList, 1);
909
+ rb_define_singleton_method(cAEDesc, "new_apple_event", rbAE_AEDesc_newAppleEvent, 5);
910
+ rb_define_singleton_method(cAEDesc, "unflatten", rbAE_AEDesc_newUnflatten, 1);
911
+
912
+ rb_define_method(cAEDesc, "to_s", rbAE_AEDesc_inspect, 0);
913
+ rb_define_method(cAEDesc, "inspect", rbAE_AEDesc_inspect, 0);
914
+ rb_define_method(cAEDesc, "type", rbAE_AEDesc_type, 0);
915
+ rb_define_method(cAEDesc, "data", rbAE_AEDesc_data, 0);
916
+ rb_define_method(cAEDesc, "flatten", rbAE_AEDesc_flatten, 0);
917
+ rb_define_method(cAEDesc, "is_record?", rbAE_AEDesc_isRecord, 0);
918
+ rb_define_method(cAEDesc, "coerce", rbAE_AEDesc_coerce, 1);
919
+ rb_define_method(cAEDesc, "length", rbAE_AEDesc_length, 0);
920
+ rb_define_method(cAEDesc, "put_item", rbAE_AEDesc_putItem, 2);
921
+ rb_define_method(cAEDesc, "put_param", rbAE_AEDesc_putParam, 2);
922
+ rb_define_method(cAEDesc, "put_attr", rbAE_AEDesc_putAttr, 2);
923
+ rb_define_method(cAEDesc, "get_item", rbAE_AEDesc_getItem, 2);
924
+ rb_define_method(cAEDesc, "get_param", rbAE_AEDesc_getParam, 2);
925
+ rb_define_method(cAEDesc, "get_attr", rbAE_AEDesc_getAttr, 2);
926
+ rb_define_method(cAEDesc, "send", rbAE_AEDesc_send, 2);
927
+ rb_define_method(cAEDesc, "send_thread_safe", rbAE_AEDesc_sendThreadSafe, 2);
928
+
929
+ // AE::MacOSError
930
+
931
+ cMacOSError = rb_define_class_under(mAE, "MacOSError", rb_eStandardError);
932
+
933
+ rb_define_attr(cMacOSError, "number", Qtrue, Qfalse);
934
+ rb_define_attr(cMacOSError, "description", Qtrue, Qfalse);
935
+
936
+ rb_define_alias(cMacOSError, "to_i", "number");
937
+
938
+ rb_define_method(cMacOSError, "to_s", rbAE_MacOSError_inspect, 0);
939
+ rb_define_method(cMacOSError, "inspect", rbAE_MacOSError_inspect, 0);
940
+
941
+ // Support functions
942
+
943
+ rb_define_module_function(mAE, "find_application", rbAE_findApplication, 3);
944
+ rb_define_module_function(mAE, "psn_for_application_path", rbAE_psnForApplicationPath, 1);
945
+ rb_define_module_function(mAE, "psn_for_process_id", rbAE_psnForPID, 1);
946
+ rb_define_module_function(mAE, "launch_application", rbAE_launchApplication, 3);
947
+
948
+ rb_define_module_function(mAE, "convert_path_to_url",
949
+ rbAE_convertPathToURL, 2);
950
+ rb_define_module_function(mAE, "convert_url_to_path",
951
+ rbAE_convertURLToPath, 2);
952
+
953
+ rb_define_module_function(mAE, "convert_long_date_time_to_string",
954
+ rbAE_convertLongDateTimeToString, 1);
955
+ rb_define_module_function(mAE, "convert_string_to_long_date_time",
956
+ rbAE_convertStringToLongDateTime, 1);
957
+ rb_define_module_function(mAE, "convert_long_date_time_to_unix_seconds",
958
+ rbAE_convertLongDateTimeToUnixSeconds, 1);
959
+ rb_define_module_function(mAE, "convert_unix_seconds_to_long_date_time",
960
+ rbAE_convertUnixSecondsToLongDateTime, 1);
961
+
962
+ rb_define_module_function(mAE, "copy_scripting_definition", rbAE_OSACopyScriptingDefinition, 1);
963
+
964
+ // Event handling
965
+
966
+ upp_GenericEventHandler = NewAEEventHandlerUPP(rbAE_GenericEventHandler);
967
+ upp_GenericCoercionHandler = NewAECoerceDescUPP(rbAE_GenericCoercionHandler);
968
+
969
+ rb_define_module_function(mAE, "install_event_handler", rbAE_AEInstallEventHandler, 3);
970
+ rb_define_module_function(mAE, "remove_event_handler", rbAE_AERemoveEventHandler, 2);
971
+ rb_define_module_function(mAE, "get_event_handler", rbAE_AEGetEventHandler, 2);
972
+
973
+ rb_define_module_function(mAE, "install_coercion_handler", rbAE_AEInstallCoercionHandler, 3);
974
+ rb_define_module_function(mAE, "remove_coercion_handler", rbAE_AERemoveCoercionHandler, 2);
975
+ rb_define_module_function(mAE, "get_coercion_handler", rbAE_AEGetCoercionHandler, 2);
976
+
977
+ rb_define_module_function(mAE, "transform_process_to_foreground_application",
978
+ rbAE_transformProcessToForegroundApplication, 0);
979
+ }