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,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Selects all .htm/.html files in the top Finder window.
4
+
5
+ # Note: if using the appscript gem, rubygems must be required first:
6
+ begin; require 'rubygems'; rescue LoadError; end
7
+
8
+ require "appscript"
9
+ include Appscript
10
+
11
+ finder = app('Finder')
12
+ finder.activate
13
+ folder = finder.Finder_windows[1].target.get
14
+ folder.files[its.name_extension.is_in(['htm', 'html'])].select
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Set iChat's status message to the name of the currently selected
4
+ # iTunes track.
5
+ #
6
+ # Based on an AppleScript example from:
7
+ # <http://developer.apple.com/cocoa/applescriptforapps.html>
8
+
9
+ # Note: if using the appscript gem, rubygems must be required first:
10
+ begin; require 'rubygems'; rescue LoadError; end
11
+
12
+ require "appscript"
13
+ include Appscript
14
+
15
+ begin
16
+ track_name = app("iTunes").current_track.name.get
17
+ rescue CommandError => e
18
+ if e.to_i == -1728 # Can't get reference.
19
+ track_name = 'No track selected.'
20
+ else
21
+ raise
22
+ end
23
+ end
24
+ app("iChat").status_message.set(track_name)
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Opens a new Finder smart folder that searches for 'ruby', via GUI Scripting
4
+
5
+ # (Note: to use GUI Scripting, 'Enable access for assistive devices' option must
6
+ # be enabled in the Universal Access panel of System Preferences.)
7
+
8
+ # Note: if using the appscript gem, rubygems must be required first:
9
+ begin; require 'rubygems'; rescue LoadError; end
10
+
11
+ require 'appscript'
12
+ include Appscript
13
+
14
+ se = app('System Events')
15
+
16
+ app('Finder').activate
17
+ se.keystroke('n', :using=>[:command_down, :option_down])
18
+ se.keystroke('ruby')
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Rearranges Finder windows diagonally across screen with title bars one above
4
+ # another. (Could easily be adapted to work with any scriptable application
5
+ # that uses standard window class terminology.)
6
+
7
+ # Note: if using the appscript gem, rubygems must be required first:
8
+ begin; require 'rubygems'; rescue LoadError; end
9
+
10
+ require "appscript"
11
+ include Appscript
12
+
13
+ x, y = 0, 44
14
+ offset = 22
15
+
16
+ # Get list of window references, ignoring any minimised windows
17
+ window_list = app('Finder').windows[its.collapsed.not].get
18
+
19
+ # Move windows while preserving their original sizes
20
+ window_list.reverse.each do |window|
21
+ x1, y1, x2, y2 = window.bounds.get
22
+ window.bounds.set([x, y, x2 - x1 + x, y2 - y1 + y])
23
+ x += offset
24
+ y += offset
25
+ end
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Demonstrates various references and commands in action.
4
+
5
+ # Note: if using the appscript gem, rubygems must be required first:
6
+ begin; require 'rubygems'; rescue LoadError; end
7
+
8
+ require "appscript"
9
+ include Appscript
10
+
11
+ textedit = app('TextEdit') # get an application object for TextEdit
12
+
13
+
14
+ # tell application "TextEdit" to activate
15
+ textedit.activate
16
+
17
+
18
+ # tell application "TextEdit" to make new document at end of documents
19
+ textedit.documents.end.make(:new => :document)
20
+
21
+
22
+ # tell application "TextEdit" to set text of document 1 to "Hello World\n"
23
+ textedit.documents[1].text.set('Hello World\n')
24
+
25
+
26
+ # tell application "TextEdit" to get a reference to every window
27
+ p textedit.windows
28
+
29
+
30
+ # tell application "TextEdit" to get a reference to document 1
31
+ p textedit.documents
32
+
33
+
34
+ # tell application "TextEdit" to get every document
35
+ p textedit.documents.get
36
+
37
+
38
+ # tell application "TextEdit" to get a reference to document 1
39
+ p textedit.documents[1]
40
+
41
+
42
+ # tell application "TextEdit" to get document 1
43
+ p textedit.documents[1].get
44
+
45
+
46
+ # tell application "TextEdit" to get window id 3210
47
+ # p textedit.windows.ID(3210).get
48
+
49
+
50
+ # tell application "TextEdit" to get a reference to text of document 1
51
+ p textedit.documents[1].text
52
+
53
+
54
+ # tell application "TextEdit" to get text of document 1
55
+ p textedit.documents[1].text.get
56
+
57
+
58
+ # tell application "TextEdit" to make new document at end of documents
59
+ textedit.documents.end.make(:new => :document)
60
+
61
+
62
+ # tell application "TextEdit" to set text of document 1 to "Happy Happy Joy Joy\n"
63
+ textedit.documents[1].text.set("Happy Happy Joy Joy\n")
64
+
65
+
66
+ # tell application "TextEdit" to get text of every document
67
+ p textedit.documents.text.get
68
+
69
+
70
+ # tell application "TextEdit" to count each word of text of document 1
71
+ p textedit.documents[1].text.count(:each => :word)
72
+
73
+
74
+ # tell application "TextEdit" to get words 3 thru -1 of document 1
75
+ p textedit.documents[1].words[3, -1].get
76
+
77
+
78
+ # tell application "TextEdit" to set size of character 1 of every word
79
+ # of document 1 to 24
80
+ textedit.documents[1].words.characters[1].size.set(24)
81
+
82
+
83
+ # tell application "TextEdit" to set color of any word of document 1 to {65535, 0, 0}
84
+ textedit.documents[1].words.any.color.set([65535, 0, 0])
85
+
86
+
87
+ # tell application "TextEdit" to make new paragraph at
88
+ # (after last paragraph of text of document 1) with data "Silly Rabbit\n"
89
+ textedit.documents[1].text.paragraphs.last.after.make(
90
+ :new => :paragraph, :with_data => "Silly Rabbit\n")
91
+
92
+
93
+ # tell application "TextEdit" to get paragraph after paragraph 1 of document 1
94
+ p textedit.documents[1].paragraphs[1].next(:paragraph).get
95
+
96
+
97
+ # tell application "TextEdit" to make new document at end of documents
98
+ # with properties {text:"foo\nbar\n\n\nbaz\n\nfub\n"}
99
+ textedit.documents.end.make(:new => :document,
100
+ :with_properties => {:text => "foo\nbar\n\n\nbaz\n\nfub\n"})
101
+
102
+
103
+ # tell application "TextEdit" to get every paragraph of text of document 1
104
+ p textedit.documents[1].text.paragraphs.get
105
+
106
+
107
+ # tell application "TextEdit" to get every paragraph of document 1
108
+ # where it is not "\n" -- get non-empty paragraphs
109
+ p textedit.documents[1].paragraphs[its.ne("\n")].get
110
+
111
+
112
+ # tell application "TextEdit" to get text of every document
113
+ # whose text begins with "H"
114
+ p textedit.documents[its.text.begins_with('H')].text.get
115
+
116
+
117
+
118
+ # The following examples don't work in TextEdit but will work in,
119
+ # for example, Tex-Edit Plus:
120
+ #
121
+ # # tell application "Tex-Edit Plus" to get words (character 5)
122
+ # # thru (paragraph 9) of document 1
123
+ # p app('Tex-Edit Plus').documents[1] \
124
+ # .words[con.characters[5], con.paragraphs[9]].get
125
+ #
126
+ #
127
+ # # tell application "Tex-Edit Plus" to get every word of text
128
+ # # of document 1 whose color is {0, 0, 0}
129
+ # p app('Tex-Edit Plus').documents[1].text.paragraphs \
130
+ # [its.color.equals((0, 0, 0))].get
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This script renders the track and album names of the top 40 most played
4
+ # iTunes tracks to an HTML file, then opens it in Safari.
5
+ #
6
+ # Requires the Amrita templating engine: http://amrita.sourceforge.jp
7
+
8
+ # Note: if using the appscript gem, rubygems must be required first:
9
+ begin; require 'rubygems'; rescue LoadError; end
10
+
11
+ require 'appscript'
12
+ include Appscript
13
+ require 'osax'
14
+ require "amrita/template"
15
+ include Amrita
16
+
17
+ # Amrita HTML template
18
+ tmpl = TemplateText.new <<END
19
+ <html>
20
+ <head>
21
+ <title> iTunes Top 40 Tracks</title>
22
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
23
+ <style type="text/css" media="all">
24
+ body {padding:0; margin:0;}
25
+ table {color:black; background-color:#448; width:100%; padding:2px;}
26
+ th, td {padding:2px; border-width:0;}
27
+ th {color:#fff; background-color:#114;}
28
+ td {color:black; background-color:#bbd;}
29
+ </style>
30
+ </head>
31
+ <body>
32
+ <table border="1">
33
+ <thead>
34
+ <tr><th>#</th><th>Track</th><th>Album</th></tr>
35
+ </thead>
36
+ <tbody>
37
+ <tr id="table_row"><td id="table_column"></td></tr>
38
+ </tbody>
39
+ </table>
40
+ </body>
41
+ </html>
42
+ END
43
+
44
+ # Choose the file to write
45
+ sa = OSAX.osax('StandardAdditions')
46
+ out_file = sa.choose_file_name(:default_name=>'My-iTunes-Top-40.html')
47
+
48
+ # Get the played count, name and album for every track in iTunes
49
+ tracks = app('iTunes').library_playlists[1].tracks
50
+ info = tracks.played_count.get.zip(tracks.name.get, tracks.album.get)
51
+ # Extract the top 40 most played entries
52
+ top40 = info.sort.reverse[0, 40]
53
+
54
+ # Assemble input data for Amrita
55
+ data = {
56
+ :table_row=>top40.collect { |row_data| {:table_column=>row_data} }
57
+ }
58
+
59
+ # Render HTML file
60
+ tmpl.prettyprint = true
61
+ File.open(out_file.to_s, 'w') { |f| tmpl.expand(f, data) }
62
+
63
+ # Open file in Safari for viewing
64
+ # safari = app('Safari')
65
+ # safari.activate
66
+ # safari.open(out_file)
67
+
68
+ # Open file in default web browser for viewing
69
+ sa.open_location(out_file.url)
70
+
71
+
@@ -0,0 +1,380 @@
1
+ /*
2
+ File: AESendThreadSafe.c
3
+
4
+ Contains: Code to send Apple events in a thread-safe manner.
5
+
6
+ Written by: DTS
7
+
8
+ Copyright: Copyright (c) 2007 Apple Inc. All Rights Reserved.
9
+
10
+ Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
11
+ ("Apple") in consideration of your agreement to the following
12
+ terms, and your use, installation, modification or
13
+ redistribution of this Apple software constitutes acceptance of
14
+ these terms. If you do not agree with these terms, please do
15
+ not use, install, modify or redistribute this Apple software.
16
+
17
+ In consideration of your agreement to abide by the following
18
+ terms, and subject to these terms, Apple grants you a personal,
19
+ non-exclusive license, under Apple's copyrights in this
20
+ original Apple software (the "Apple Software"), to use,
21
+ reproduce, modify and redistribute the Apple Software, with or
22
+ without modifications, in source and/or binary forms; provided
23
+ that if you redistribute the Apple Software in its entirety and
24
+ without modifications, you must retain this notice and the
25
+ following text and disclaimers in all such redistributions of
26
+ the Apple Software. Neither the name, trademarks, service marks
27
+ or logos of Apple Inc. may be used to endorse or promote
28
+ products derived from the Apple Software without specific prior
29
+ written permission from Apple. Except as expressly stated in
30
+ this notice, no other rights or licenses, express or implied,
31
+ are granted by Apple herein, including but not limited to any
32
+ patent rights that may be infringed by your derivative works or
33
+ by other works in which the Apple Software may be incorporated.
34
+
35
+ The Apple Software is provided by Apple on an "AS IS" basis.
36
+ APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
37
+ WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
38
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
39
+ THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
40
+ COMBINATION WITH YOUR PRODUCTS.
41
+
42
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
43
+ INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
44
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
45
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY
46
+ OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
47
+ OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY
48
+ OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
49
+ OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
50
+ SUCH DAMAGE.
51
+
52
+ Change History (most recent first):
53
+
54
+ $Log: AESendThreadSafe.c,v $
55
+ Revision 1.3 2007/02/27 10:45:15
56
+ In the destructor, add an assert that the thread-local storage has been set to NULL. Also fixed a comment typo.
57
+
58
+ Revision 1.2 2007/02/12 11:59:09
59
+ Added a type cast for the malloc result.
60
+
61
+ Revision 1.1 2007/02/09 10:55:24
62
+ First checked in.
63
+
64
+
65
+ */
66
+
67
+ /*
68
+
69
+ 2007/06/24 -- Modified by HAS to make AESendMessageThreadSafeSynchronous API-compatible with AESendMessage; renamed SendMessageThreadSafe.
70
+
71
+ */
72
+
73
+ /////////////////////////////////////////////////////////////////
74
+
75
+ #include "SendThreadSafe.h"
76
+
77
+ #include <pthread.h>
78
+ #include <mach/mach.h>
79
+
80
+ /////////////////////////////////////////////////////////////////
81
+
82
+ /*
83
+ How It Works
84
+ ------------
85
+ The basic idea behind this module is that it uses per-thread storage to keep
86
+ track of an Apple event reply for any given thread. The first time that the
87
+ thread calls AESendMessageThreadSafeSynchronous, the per-thread storage will
88
+ not be initialised and the code will grab an Apple event reply port and
89
+ assign it to the per-thread storage. Subsequent calls to AESendMessageThreadSafeSynchronous
90
+ will continue to use that port. When the thread dies, pthreads will automatically
91
+ call the destructor for the per-thread storage, and that will clean up the port.
92
+
93
+ Because we can't dispose of the reply port (without triggering the Apple
94
+ Event Manager bug that's the reason we wrote this code in the first place),
95
+ the destructor doesn't actually dispose of the port. Rather, it adds the
96
+ port to a pool of ports that are available for reuse. The next time a thread
97
+ needs to allocate a port, it will grab it from the pool rather than allocating
98
+ it from scratch.
99
+
100
+ This technique means that the code still 'leaks' Apple event reply ports, but
101
+ the size of the leak is limited to the maximum number of threads that you run
102
+ simultaneously. This isn't a problem in practice.
103
+ */
104
+
105
+ /////////////////////////////////////////////////////////////////
106
+
107
+ // The PerThreadStorage structure is a trivial wrapper around the Mach port.
108
+ // I added this because I need to attach this structure a thread using
109
+ // per-thread storage. The API for that (<x-man-page://3/pthread_setspecific>)
110
+ // is pointer based. I could've just cast the Mach port to a (void *), but
111
+ // that's ugly because of a) pointer size issues (are pointers always bigger than
112
+ // ints?), and b) because it implies an equivalent between NULL and MACH_PORT_NULL.
113
+ // Given this, I simply decided to create a structure to wrap the Mach port.
114
+
115
+ enum {
116
+ kPerThreadStorageMagic = 'PTSm'
117
+ };
118
+
119
+ struct PerThreadStorage {
120
+ OSType magic; // must be kPerThreadStorageMagic
121
+ mach_port_t port;
122
+ };
123
+ typedef struct PerThreadStorage PerThreadStorage;
124
+
125
+ // The following static variables manage the per-thread storage key
126
+ // (sPerThreadStorageKey) and the pool of Mach ports (wrapped in
127
+ // PerThreadStorage structures) that are not currently attached to a thread.
128
+
129
+ static pthread_once_t sInited = PTHREAD_ONCE_INIT; // covers initialisation of all of the
130
+ // following static variables
131
+
132
+ static OSStatus sPerThreadStorageKeyInitErrNum; // latches result of initialisation
133
+
134
+ static pthread_key_t sPerThreadStorageKey = 0; // key for our per-thread storage
135
+
136
+ static pthread_mutex_t sPoolMutex; // protects sPool
137
+ static CFMutableArrayRef sPool; // array of (PerThreadStorage *), holds
138
+ // the ports that aren't currently bound to
139
+ // a thread
140
+
141
+ static void PerThreadStorageDestructor(void *keyValue); // forward declaration
142
+
143
+ static void InitRoutine(void)
144
+ // Call once (via pthread_once) to initialise various static variables.
145
+ {
146
+ OSStatus err;
147
+
148
+ // Create the per-thread storage key. Note that we assign a destructor to this key;
149
+ // pthreads call the destructor to clean up that item of per-thread storage whenever
150
+ // a thread terminates.
151
+
152
+ err = (OSStatus) pthread_key_create(&sPerThreadStorageKey, PerThreadStorageDestructor);
153
+
154
+ // Create the pool of Mach ports that aren't bound to any thread, and its associated
155
+ // lock. The pool starts out empty.
156
+
157
+ if (err == noErr) {
158
+ err = (OSStatus) pthread_mutex_init(&sPoolMutex, NULL);
159
+ }
160
+ if (err == noErr) {
161
+ sPool = CFArrayCreateMutable(NULL, 0, NULL);
162
+ if (sPool == NULL) {
163
+ err = coreFoundationUnknownErr;
164
+ }
165
+ }
166
+ assert(err == 0);
167
+
168
+ sPerThreadStorageKeyInitErrNum = err;
169
+ }
170
+
171
+ static OSStatus AllocatePortFromPool(PerThreadStorage **storagePtr)
172
+ // Grab a Mach port from sPool; if sPool is empty, create one.
173
+ {
174
+ OSStatus err;
175
+ OSStatus junk;
176
+ PerThreadStorage * storage;
177
+
178
+ assert( storagePtr != NULL);
179
+ assert(*storagePtr == NULL);
180
+
181
+ storage = NULL;
182
+
183
+ // First try to get an entry from pool. We try to grab the last one because
184
+ // that minimises the amount of copying that CFArrayRemoveValueAtIndex has to
185
+ // do.
186
+
187
+ err = (OSStatus) pthread_mutex_lock(&sPoolMutex);
188
+ if (err == noErr) {
189
+ CFIndex poolCount;
190
+
191
+ poolCount = CFArrayGetCount(sPool);
192
+ if (poolCount > 0) {
193
+ storage = (PerThreadStorage *) CFArrayGetValueAtIndex(sPool, poolCount - 1);
194
+ CFArrayRemoveValueAtIndex(sPool, poolCount - 1);
195
+ }
196
+
197
+ junk = (OSStatus) pthread_mutex_unlock(&sPoolMutex);
198
+ assert(junk == noErr);
199
+ }
200
+
201
+ // If we failed to find an entry in the pool, create a new one.
202
+
203
+ if ( (err == noErr) && (storage == NULL) ) {
204
+ storage = (PerThreadStorage *) malloc(sizeof(*storage));
205
+ if (storage == NULL) {
206
+ err = memFullErr;
207
+ } else {
208
+ storage->magic = kPerThreadStorageMagic;
209
+ storage->port = MACH_PORT_NULL;
210
+
211
+ err = (OSStatus) mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &storage->port);
212
+ if (err != noErr) {
213
+ assert(storage->port == MACH_PORT_NULL);
214
+ free(storage);
215
+ storage = NULL;
216
+ }
217
+ }
218
+ }
219
+ if (err == noErr) {
220
+ *storagePtr = storage;
221
+ }
222
+
223
+ assert( (err == noErr) == (*storagePtr != NULL) );
224
+ assert( (*storagePtr == NULL) || ((*storagePtr)->magic == kPerThreadStorageMagic) );
225
+ assert( (*storagePtr == NULL) || ((*storagePtr)->port != MACH_PORT_NULL) );
226
+
227
+ return err;
228
+ }
229
+
230
+ static void ReturnPortToPool(PerThreadStorage * storage)
231
+ // Returns a port to sPool.
232
+ {
233
+ OSStatus err;
234
+
235
+ assert(storage != NULL);
236
+ assert(storage->magic == kPerThreadStorageMagic);
237
+ assert(storage->port != MACH_PORT_NULL);
238
+
239
+ err = (OSStatus) pthread_mutex_lock(&sPoolMutex);
240
+ if (err == noErr) {
241
+ CFArrayAppendValue(sPool, storage);
242
+
243
+ err = (OSStatus) pthread_mutex_unlock(&sPoolMutex);
244
+ }
245
+ assert(err == noErr);
246
+ }
247
+
248
+ // Main Thread Notes
249
+ // -----------------
250
+ // There are two reasons why we don't assign a reply port to the main thread.
251
+ // First, the main thread already has a reply port created for it by Apple
252
+ // Event Manager. Thus, we don't need a specific reply port. Also, the
253
+ // destructor for per-thread storage isn't called for the main thread, so
254
+ // we wouldn't get a chance to clean up (although that's not really a problem
255
+ // in practice).
256
+
257
+ static OSStatus BindReplyMachPortToThread(mach_port_t *replyPortPtr)
258
+ // Get a reply port for this thread, remembering that we've done this
259
+ // in per-thread storage.
260
+ //
261
+ // On success, *replyPortPtr is the port to use for this thread's reply
262
+ // port. It will be MACH_PORT_NULL if you call it from the main thread.
263
+ {
264
+ OSStatus err;
265
+
266
+ assert( replyPortPtr != NULL);
267
+ assert(*replyPortPtr == MACH_PORT_NULL);
268
+
269
+ // Initialise ourselves the first time that we're called.
270
+
271
+ err = (OSStatus) pthread_once(&sInited, InitRoutine);
272
+
273
+ // If something went wrong, return the latched error.
274
+
275
+ if ( (err == noErr) && (sPerThreadStorageKeyInitErrNum != noErr) ) {
276
+ err = sPerThreadStorageKeyInitErrNum;
277
+ }
278
+
279
+ // Now do the real work.
280
+
281
+ if (err == noErr) {
282
+ if ( pthread_main_np() ) {
283
+ // This is the main thread, so do nothing; leave *replyPortPtr set
284
+ // to MACH_PORT_NULL.
285
+ assert(*replyPortPtr == MACH_PORT_NULL);
286
+ } else {
287
+ PerThreadStorage * storage;
288
+
289
+ // Get the per-thread storage for this thread.
290
+
291
+ storage = (PerThreadStorage *) pthread_getspecific(sPerThreadStorageKey);
292
+ if (storage == NULL) {
293
+
294
+ // The per-thread storage hasn't been allocated yet for this specific
295
+ // thread. Let's go allocate it and attach it to this thread.
296
+
297
+ err = AllocatePortFromPool(&storage);
298
+ if (err == noErr) {
299
+ err = (OSStatus) pthread_setspecific(sPerThreadStorageKey, (void *) storage);
300
+ if (err != noErr) {
301
+ ReturnPortToPool(storage);
302
+ storage = NULL;
303
+ }
304
+ }
305
+ }
306
+ assert( (err == noErr) == (storage != NULL) );
307
+
308
+ // If all went well, copy the port out to our client.
309
+
310
+ if (err == noErr) {
311
+ assert(storage->magic == kPerThreadStorageMagic);
312
+ assert(storage->port != MACH_PORT_NULL);
313
+ *replyPortPtr = storage->port;
314
+ }
315
+ }
316
+ }
317
+
318
+ // no error + MACH_PORT_NULL is a valid response if we're on the main
319
+ // thread.
320
+ //
321
+ // assert( (err == noErr) == (*replyPortPtr != MACH_PORT_NULL) );
322
+ assert( (*replyPortPtr == MACH_PORT_NULL) || (err == noErr) );
323
+
324
+ return err;
325
+ }
326
+
327
+ static void PerThreadStorageDestructor(void *keyValue)
328
+ // Called by pthreads when a thread dies and it has a non-null value for our
329
+ // per-thread storage key. We use this callback to return the thread's
330
+ // Apple event reply port to the pool.
331
+ {
332
+ PerThreadStorage * storage;
333
+
334
+ storage = (PerThreadStorage *) keyValue;
335
+ assert(storage != NULL); // pthread won't call us if it's NULL
336
+ assert(storage->magic == kPerThreadStorageMagic);
337
+ assert(storage->port != MACH_PORT_NULL);
338
+
339
+ // Return the port associated with this thread to the pool.
340
+
341
+ ReturnPortToPool(storage);
342
+
343
+ // pthreads has already set this thread's per-thread storage for our key to
344
+ // NULL before calling us. So we don't need to do anything to remove it.
345
+
346
+ assert( pthread_getspecific(sPerThreadStorageKey) == NULL );
347
+ }
348
+
349
+ OSStatus SendMessageThreadSafe(
350
+ AppleEvent * eventPtr,
351
+ AppleEvent * replyPtr,
352
+ AESendMode sendMode,
353
+ long timeOutInTicks
354
+ )
355
+ // See comment in header.
356
+ {
357
+ OSStatus err = noErr;
358
+ mach_port_t replyPort;
359
+ assert(eventPtr != NULL);
360
+ assert(replyPtr != NULL);
361
+
362
+ if (sendMode && kAEWaitReply) {
363
+ replyPort = MACH_PORT_NULL;
364
+
365
+ // Set up the reply port if necessary.
366
+
367
+ err = BindReplyMachPortToThread(&replyPort);
368
+ if ( (err == noErr) && (replyPort != MACH_PORT_NULL) ) {
369
+ err = AEPutAttributePtr(eventPtr, keyReplyPortAttr, typeMachPort, &replyPort, sizeof(replyPort));
370
+ }
371
+ }
372
+
373
+ // Call through to AESendMessage.
374
+
375
+ if (err == noErr) {
376
+ err = AESendMessage(eventPtr, replyPtr, sendMode, timeOutInTicks);
377
+ }
378
+
379
+ return err;
380
+ }