rb-appscript 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/CHANGES +37 -0
  2. data/LICENSE +6 -2
  3. data/README +7 -4
  4. data/TODO +6 -2
  5. data/doc/aem-manual/01_introduction.html +1 -1
  6. data/doc/aem-manual/02_apioverview.html +1 -1
  7. data/doc/aem-manual/03_packingandunpackingdata.html +3 -3
  8. data/doc/aem-manual/04_references.html +18 -13
  9. data/doc/aem-manual/05_targettingapplications.html +33 -13
  10. data/doc/aem-manual/06_buildingandsendingevents.html +1 -1
  11. data/doc/aem-manual/07_findapp.html +1 -1
  12. data/doc/aem-manual/08_examples.html +1 -1
  13. data/doc/aem-manual/index.html +1 -1
  14. data/doc/appscript-manual/01_introduction.html +1 -1
  15. data/doc/appscript-manual/02_aboutappscripting.html +4 -4
  16. data/doc/appscript-manual/03_quicktutorial.html +1 -1
  17. data/doc/appscript-manual/04_gettinghelp.html +13 -42
  18. data/doc/appscript-manual/05_keywordconversion.html +1 -1
  19. data/doc/appscript-manual/06_classesandenums.html +1 -1
  20. data/doc/appscript-manual/07_applicationobjects.html +19 -1
  21. data/doc/appscript-manual/08_realvsgenericreferences.html +1 -1
  22. data/doc/appscript-manual/09_referenceforms.html +1 -1
  23. data/doc/appscript-manual/10_referenceexamples.html +1 -1
  24. data/doc/appscript-manual/11_applicationcommands.html +1 -1
  25. data/doc/appscript-manual/12_commandexamples.html +1 -1
  26. data/doc/appscript-manual/13_performanceissues.html +1 -1
  27. data/doc/appscript-manual/14_notes.html +1 -1
  28. data/doc/appscript-manual/index.html +1 -1
  29. data/doc/index.html +1 -1
  30. data/doc/mactypes-manual/index.html +13 -1
  31. data/doc/osax-manual/index.html +37 -6
  32. data/extconf.rb +2 -2
  33. data/rb-appscript.gemspec +1 -1
  34. data/sample/AB_export_vcard.rb +31 -0
  35. data/sample/Add_iCal_event.rb +4 -4
  36. data/src/lib/_aem/connect.rb +88 -8
  37. data/src/lib/_aem/mactypes.rb +27 -21
  38. data/src/lib/_appscript/reservedkeywords.rb +1 -0
  39. data/src/lib/aem.rb +40 -5
  40. data/src/lib/appscript.rb +104 -30
  41. data/src/lib/kae.rb +1 -0
  42. data/src/lib/osax.rb +35 -19
  43. data/src/rbae.c +115 -1
  44. data/test/README +3 -1
  45. data/test/test_appscriptcommands.rb +5 -4
  46. data/test/test_codecs.rb +7 -1
  47. data/test/testall.sh +1 -1
  48. metadata +24 -23
@@ -86,6 +86,6 @@ id wait_reply</code></pre>
86
86
  </div>
87
87
 
88
88
  <!--footer-->
89
- <p class="footer">&copy; 2006 HAS</p>
89
+ <p class="footer">&copy; 2007 HAS</p>
90
90
  </body>
91
91
  </html>
@@ -150,6 +150,6 @@ $KCODE = "UTF8"</code></pre>
150
150
  </div>
151
151
 
152
152
  <!--footer-->
153
- <p class="footer">&copy; 2006 HAS</p>
153
+ <p class="footer">&copy; 2007 HAS</p>
154
154
  </body>
155
155
  </html>
@@ -152,6 +152,24 @@ set(reference, :to =&gt; value) -- Set an object's data.
152
152
  <p>Appscript identifies running applications by their process ids so it's possible to control multiple versions of an application running at the same time if their Application objects are created using process ids or eppc URLs.</p>
153
153
 
154
154
 
155
+ <h3>Checking if an application is running</h3>
156
+
157
+ <p>You can check if the application specified by an Application object is currently running by calling its <code>#is_running?</code> method. This is useful if you don't want to perform commands on an application that isn't already running. For example:</p>
158
+
159
+ <pre><code>te = app('TextEdit')
160
+ # Only perform TextEdit-related commands if it's already running:
161
+ if te.is_running?
162
+ # all TextEdit-related code goes here...
163
+ end</code></pre>
164
+
165
+ <p class="hilitebox">Remember that appscript automatically launches a non-running application the first time your script makes reference to any of its properties, elements or commands. To avoid accidental launches, <em>all</em> code relating to that application must be included in a conditional block that only executes if <code>#is_running?</code> returns <code>true</code>.</p>
166
+
167
+
168
+ <h3>Launch errors</h3>
169
+
170
+ <p>If the application can't be launched for some reason (e.g. if it's in the Trash), an <code>Appscript::CantLaunchApplicationError</code> error will be raised. This provides a description of the problem (if it's a standard LaunchServices error) along with the original OS error number, which you can also obtain via the <code>CantLaunchApplicationError#to_i</code> method.</p>
171
+
172
+
155
173
  <h3>Using <code>launch</code> vs <code>run</code></h3>
156
174
 
157
175
  <p>When appscript launches a non-running application, it normally sends it a <code>run</code> command as part of the launching process. If you wish to avoid this, you should start the application by sending it a <code>launch</code> command before doing anything else. For example:</p>
@@ -186,6 +204,6 @@ te.launch
186
204
  </div>
187
205
 
188
206
  <!--footer-->
189
- <p class="footer">&copy; 2006 HAS</p>
207
+ <p class="footer">&copy; 2007 HAS</p>
190
208
  </body>
191
209
  </html>
@@ -81,6 +81,6 @@ puts app('Finder').home == app('Finder').home.get
81
81
  </div>
82
82
 
83
83
  <!--footer-->
84
- <p class="footer">&copy; 2006 HAS</p>
84
+ <p class="footer">&copy; 2007 HAS</p>
85
85
  </body>
86
86
  </html>
@@ -225,6 +225,6 @@ paragraphs[1].before</code></pre>
225
225
  </div>
226
226
 
227
227
  <!--footer-->
228
- <p class="footer">&copy; 2006 HAS</p>
228
+ <p class="footer">&copy; 2007 HAS</p>
229
229
  </body>
230
230
  </html>
@@ -138,6 +138,6 @@ app('TextEdit').documents[1].text.paragraphs[1].before</code></pre>
138
138
  </div>
139
139
 
140
140
  <!--footer-->
141
- <p class="footer">&copy; 2006 HAS</p>
141
+ <p class="footer">&copy; 2007 HAS</p>
142
142
  </body>
143
143
  </html>
@@ -201,6 +201,6 @@ print d
201
201
  </div>
202
202
 
203
203
  <!--footer-->
204
- <p class="footer">&copy; 2006 HAS</p>
204
+ <p class="footer">&copy; 2007 HAS</p>
205
205
  </body>
206
206
  </html>
@@ -124,6 +124,6 @@ app('Address Book').people[
124
124
  </div>
125
125
 
126
126
  <!--footer-->
127
- <p class="footer">&copy; 2006 HAS</p>
127
+ <p class="footer">&copy; 2007 HAS</p>
128
128
  </body>
129
129
  </html>
@@ -113,6 +113,6 @@ p result</code></pre>
113
113
  </div>
114
114
 
115
115
  <!--footer-->
116
- <p class="footer">&copy; 2006 HAS</p>
116
+ <p class="footer">&copy; 2007 HAS</p>
117
117
  </body>
118
118
  </html>
@@ -84,6 +84,6 @@ require 'appscript'
84
84
  </div>
85
85
 
86
86
  <!--footer-->
87
- <p class="footer">&copy; 2006 HAS</p>
87
+ <p class="footer">&copy; 2007 HAS</p>
88
88
  </body>
89
89
  </html>
@@ -43,6 +43,6 @@
43
43
  </div>
44
44
 
45
45
  <!--footer-->
46
- <p class="footer">&copy; 2006 HAS</p>
46
+ <p class="footer">&copy; 2007 HAS</p>
47
47
  </body>
48
48
  </html>
@@ -26,6 +26,6 @@
26
26
  </div>
27
27
 
28
28
  <!--footer-->
29
- <p class="footer">&copy; 2006 HAS</p>
29
+ <p class="footer">&copy; 2007 HAS</p>
30
30
  </body>
31
31
  </html>
@@ -39,6 +39,8 @@
39
39
 
40
40
  Alias.path(path) -- make Alias object from POSIX path string
41
41
 
42
+ Alias.hfs_path(path) -- make Alias object from HFS path string
43
+
42
44
  Alias.url(url) -- make Alias object from a local file:// URL string
43
45
 
44
46
  Alias.desc(desc) -- make Alias object from an AE::AEDesc
@@ -53,6 +55,8 @@
53
55
  inspect
54
56
 
55
57
  path -- returns POSIX path string to the object's current location
58
+
59
+ hfs_path -- returns HFS path string to the object's current location
56
60
 
57
61
  url -- returns file:// URL string to the object's current location
58
62
 
@@ -88,6 +92,8 @@ Appscript.app('TextEdit').open(f)
88
92
 
89
93
  <p>Comparing an <code>Alias</code> object against a <code>FileURL</code> object always returns false, even if both point to the same location.</p>
90
94
 
95
+ <p>Remember that aliases can change when the corresponding filesystem object is moved, so take care when using <code>Alias</code> objects in situations that involve comparing or hashing them (e.g. <code>Hash</code> keys).</p>
96
+
91
97
 
92
98
  <h2><code>MacTypes::FileURL</code></h2>
93
99
 
@@ -100,6 +106,8 @@ Appscript.app('TextEdit').open(f)
100
106
 
101
107
  FileURL.path(path) -- make FileURL object from POSIX path string
102
108
 
109
+ FileURL.hfs_path(path) -- make FileURL object from HFS path string
110
+
103
111
  FileURL.url(url) -- make FileURL object from a local file:// URL string
104
112
 
105
113
  FileURL.desc(desc) -- make FileURL object from an AE::AEDesc
@@ -114,6 +122,8 @@ Appscript.app('TextEdit').open(f)
114
122
  inspect
115
123
 
116
124
  path -- returns POSIX path string
125
+
126
+ hfs_path -- returns HFS path string
117
127
 
118
128
  url -- returns file:// URL string
119
129
 
@@ -165,6 +175,8 @@ Appscript.app('older app').documents[1].save(:in => fs_spec)</code></pre>
165
175
 
166
176
  <p>Note that AEDescs of <code>TypeFSRef</code> can represent existing filesystem locations only. AEDescs of <code>TypeFileURL</code> can represent both existing and non-existing locations. AEDescs of <code>TypeFSS</code> (FSSpecs) are deprecated on Mac OS X due to lack of proper Unicode and long filename support, and are retained for backwards compatibility with older applications only.</p>
167
177
 
178
+ <p>Be aware that <code>FileURL#==</code> does not normalize file URLs; thus minor differences in capitalization, etc. can result in <code>FileURL#==</code> returning <code>false</code> even if both objects happen to identify the same filesystem location.</p>
179
+
168
180
 
169
181
 
170
182
 
@@ -239,6 +251,6 @@ MacTypes::Alias.path('/some/non/existent/location')
239
251
  </div>
240
252
 
241
253
  <!--footer-->
242
- <p class="footer">&copy; 2006 HAS</p>
254
+ <p class="footer">&copy; 2007 HAS</p>
243
255
  </body>
244
256
  </html>
@@ -107,9 +107,18 @@ osax.another_command</code></pre>
107
107
 
108
108
  Constructors:
109
109
 
110
- ScriptingAddition.new(name) -- make a ScriptingAddition object
111
- for the specified scripting addition, targetted at the
112
- current application
110
+ ScriptingAddition.new(name, terms=nil) -- make a ScriptingAddition
111
+ object for the specified scripting addition, targetted
112
+ at the current application
113
+
114
+ name: string -- a scripting addition's name,
115
+ e.g. "StandardAdditions"; basically its filename
116
+ minus the '.osax' suffix
117
+
118
+ terms : module or nil -- an optional terminology glue
119
+ module,as exported by Terminology.dump; if
120
+ given, ScriptingAddition will use this instead
121
+ of retrieving the terminology dynamically
113
122
 
114
123
  Methods:
115
124
 
@@ -117,7 +126,7 @@ osax.another_command</code></pre>
117
126
 
118
127
  commands -- returns names of all available commands
119
128
 
120
- parameters(commandName) -- returns a command's parameter names
129
+ parameters(command_name) -- returns a command's parameter names
121
130
 
122
131
  # Specifying a different target application:
123
132
 
@@ -167,6 +176,8 @@ p sa.display_dialog("Ruby says hello!",
167
176
 
168
177
  <h2>Notes</h2>
169
178
 
179
+ <h3>GUI interaction</h3>
180
+
170
181
  <p>When using scripting addition commands that require GUI access (e.g. <code>display_dialog</code>) targetted at the command-line Ruby interpreter, the osax module will automatically convert the non-GUI interpreter process into a full GUI process to allow these commands to operate correctly. If you want to avoid this, target these commands at a faceless GUI application such as System Events instead:</p>
171
182
 
172
183
  <pre><code>sa = OSAX.osax("StandardAdditions", "System Events")
@@ -177,7 +188,27 @@ p sa.display_dialog("Ruby says hello!",
177
188
  # Result: {:button_returned=&gt;"Duuuude!"}</code></pre>
178
189
 
179
190
 
180
- <p>When using the <code>osax</code> module within RubyCocoa-based applications, avoid creating <code>ScriptingAddition</code> instances before the main event loop is started as this will prevent minimized windows expanding as normal when clicked on in the Dock.</p>
191
+ <h3>64-bit limitations</h3>
192
+
193
+ <p>The <code>osax</code> module currently only supports dynamic retrieval of scripting addition terminology when running in 32-bit processes. To use it in 64-bit processes, use the <code>Terminology</code> module's <code>dump</code> method to export a static terminology 'glue' module for the desired scripting addition (running it in a 32-bit process), then import that module and pass it as the second argument to the <code>ScriptingAddition</code> class's initialiser. For example, to export a glue module for Standard Additions:</p>
194
+
195
+ <pre><code>require 'appscript'
196
+
197
+ Terminology.dump('/System/Library/ScriptingAdditions/StandardAdditions.osax',
198
+ 'StandardAdditions', 'standard_additions.rb')</code></pre>
199
+
200
+ <p>To create a new <code>ScriptingAddition</code> instance using the terminology provided by this glue module:</p>
201
+
202
+ <pre><code>require 'osax'
203
+ require 'standard_additions'
204
+
205
+ sa = OSAX::ScriptingAddition.new('StandardAdditions', StandardAdditions)</code></pre>
206
+
207
+
208
+ <h3>Known problems</h3>
209
+
210
+ <p>When using the <code>osax</code> module within RubyCocoa-based applications, avoid creating <code>ScriptingAddition</code> instances before the main event loop is started as this can result in the application behaving strangely (minimised windows don't expand correctly) due to a bug in OS X's <code>OSAGetAppTerminology</code> function.</p>
211
+
181
212
 
182
213
 
183
214
  </div>
@@ -188,6 +219,6 @@ p sa.display_dialog("Ruby says hello!",
188
219
  </div>
189
220
 
190
221
  <!--footer-->
191
- <p class="footer">&copy; 2006 HAS</p>
222
+ <p class="footer">&copy; 2007 HAS</p>
192
223
  </body>
193
224
  </html>
data/extconf.rb CHANGED
@@ -30,8 +30,8 @@
30
30
 
31
31
  require 'mkmf'
32
32
 
33
- $CFLAGS << ' -Wall'# -arch ppc -arch i386'
34
- $LDFLAGS = '-framework Carbon'
33
+ $CFLAGS << ' -Wall'
34
+ $LDFLAGS << ' -framework Carbon -framework ApplicationServices'
35
35
 
36
36
  # Avoid `ID' and `T_DATA' symbol collisions between Ruby and Carbon.
37
37
  # (adapted code from RubyAEOSA - FUJIMOTO Hisakuni <hisa -at- fobj - com>)
@@ -2,7 +2,7 @@ require "rubygems"
2
2
 
3
3
  spec = Gem::Specification.new do |s|
4
4
  s.name = "rb-appscript"
5
- s.version = "0.4.0"
5
+ s.version = "0.5.0"
6
6
  s.author = "HAS"
7
7
  s.homepage = "http://rb-appscript.rubyforge.org/"
8
8
  s.rubyforge_project="rb-appscript"
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Exports all Address Book entries as vcards to current working folder.
4
+ #
5
+ # Files are named as 'NAME.vcard' (note: existing files will be overwritten).
6
+ # If the name is missing, 'unknown' is used instead. If two or more people
7
+ # share the same name, files will be named 'NAME.vcard', 'NAME 1.vcard',
8
+ # 'NAME 2.vcard', etc.
9
+
10
+
11
+ # Note: if using the appscript gem, rubygems must be required first:
12
+ begin; require 'rubygems'; rescue LoadError; end
13
+
14
+ require 'appscript'
15
+ include Appscript
16
+
17
+ people = app('Address Book').people
18
+
19
+ found_names = []
20
+ people.name.get.zip(people.vcard.get).each do |name, vcard|
21
+ name = 'unknown' if name == ''
22
+ name = name.gsub('/', ':')
23
+ filename = "#{name}.vcard"
24
+ i = 1
25
+ while found_names.include?(filename.downcase)
26
+ filename = "#{name} #{i}.vcard"
27
+ i += 1
28
+ end
29
+ found_names.push(filename.downcase)
30
+ File.open(filename, 'w') { |f| f.puts vcard }
31
+ end
@@ -1,13 +1,13 @@
1
- #!/usr/local/bin/ruby
2
-
3
- require 'appscript'
4
- include Appscript
1
+ #!/usr/bin/env ruby
5
2
 
6
3
  # Add an event to Home calendar that runs from 7am to 9 am tomorrow
7
4
 
8
5
  # Note: if using the appscript gem, rubygems must be required first:
9
6
  begin; require 'rubygems'; rescue LoadError; end
10
7
 
8
+ require 'appscript'
9
+ include Appscript
10
+
11
11
  calendar_name = 'Home'
12
12
  t = Time.now + 60 * 60 * 24
13
13
  start = Time.local(t.year, t.month, t.day, 7)
@@ -1,4 +1,4 @@
1
- #!/usr/local/bin/ruby
1
+ #!/usr/bin/ruby
2
2
  # Copyright (C) 2006 HAS.
3
3
  # Released under MIT License.
4
4
 
@@ -28,7 +28,48 @@ module Connect
28
28
  #######
29
29
  # public
30
30
 
31
- def Connect.launch_app(path)
31
+ class CantLaunchApplicationError < RuntimeError
32
+
33
+ # Taken from <http://developer.apple.com/documentation/Carbon/Reference/LaunchServicesReference>:
34
+ LSErrors = {
35
+ -10660 => "The application cannot be run because it is inside a Trash folder.",
36
+ -10810 => "An unknown error has occurred.",
37
+ -10811 => "The item to be registered is not an application.",
38
+ -10813 => "Data of the desired type is not available (for example, there is no kind string).",
39
+ -10814 => "No application in the Launch Services database matches the input criteria.",
40
+ -10817 => "Data is structured improperly (for example, an item's information property list is malformed).",
41
+ -10818 => "A launch of the application is already in progress.",
42
+ -10822 => "There is a problem communicating with the server process that maintains the Launch Services database.",
43
+ -10823 => "The filename extension to be hidden cannot be hidden.",
44
+ -10825 => "The application to be launched cannot run on the current Mac OS version.",
45
+ -10826 => "The user does not have permission to launch the application (on a managed network).",
46
+ -10827 => "The executable file is missing or has an unusable format.",
47
+ -10828 => "The Classic emulation environment was required but is not available.",
48
+ -10829 => "The application to be launched cannot run simultaneously in two different user sessions.",
49
+ }
50
+
51
+ def initialize(error_number)
52
+ @error_number = error_number
53
+ super("#{ LSErrors.fetch(@error_number, 'OS error') } (#{ @error_number })")
54
+ end
55
+
56
+ def to_i
57
+ return @error_number
58
+ end
59
+ end
60
+
61
+ ##
62
+
63
+ def Connect.launch_application(path, event)
64
+ begin
65
+ return AE.launch_application(path, event,
66
+ LaunchContinue + LaunchNoFileFlags + LaunchDontSwitch)
67
+ rescue AE::MacOSError => err
68
+ raise CantLaunchApplicationError, err.to_i
69
+ end
70
+ end
71
+
72
+ def Connect.launch_app_with_launch_event(path)
32
73
  # Send a 'launch' event to an application. If application is not already running, it will be launched in background first.
33
74
  begin
34
75
  # If app is already running, calling AE.launch_application will send a 'reopen' event, so need to check for this first:
@@ -36,8 +77,7 @@ module Connect
36
77
  rescue AE::MacOSError => err
37
78
  if err.to_i == -600 # Application isn't running, so launch it and send it a 'launch' event
38
79
  sleep(1)
39
- AE.launch_application(path, LaunchEvent,
40
- LaunchContinue + LaunchNoFileFlags + LaunchDontSwitch)
80
+ launch_application(path, LaunchEvent)
41
81
  else
42
82
  raise
43
83
  end
@@ -46,8 +86,11 @@ module Connect
46
86
  end
47
87
  end
48
88
 
49
- def Connect.is_running?(path)
50
- # Is a local application running?
89
+ ##
90
+
91
+ def Connect.process_exists_for_path?(path)
92
+ # Does a local process launched from the specified application file exist?
93
+ # Note: if path is invalid, an AE::MacOSError is raised.
51
94
  begin
52
95
  AE.psn_for_application_path(path)
53
96
  return true
@@ -59,6 +102,44 @@ module Connect
59
102
  end
60
103
  end
61
104
  end
105
+
106
+ def Connect.process_exists_for_pid?(pid)
107
+ # Is there a local application process with the given unix process id?
108
+ begin
109
+ AE.psn_for_process_id(pid)
110
+ return true
111
+ rescue AE::MacOSError => err
112
+ if err.to_i == -600
113
+ return false
114
+ else
115
+ raise
116
+ end
117
+ end
118
+ end
119
+
120
+ def Connect.process_exists_for_url?(url)
121
+ # Does an application process specified by the given eppc:// URL exist?
122
+ # Note: this will send a 'launch' Apple event to the target application.
123
+ return process_exists_for_desc?(AE::AEDesc.new(KAE::TypeApplicationURL, url))
124
+ end
125
+
126
+ def Connect.process_exists_for_desc?(desc)
127
+ # Does an application process specified by the given AEAddressDesc exist?
128
+ # Returns false if process doesn't exist OR remote Apple events aren't allowed.
129
+ # Note: this will send a 'launch' Apple event to the target application.
130
+ begin
131
+ # This will usually raise error -1708 if process is running, and various errors
132
+ # if the process doesn't exist/can't be reached. If app is running but busy,
133
+ # AESendMessage may return a timeout error (this should be -1712, but
134
+ # -609 is often returned instead for some reason).
135
+ Send::Event.new(desc, 'ascrnoop').send
136
+ rescue Send::CommandError => err
137
+ return (not [-600, -905].include?(err.to_i)) # not running/no network access
138
+ end
139
+ return true
140
+ end
141
+
142
+ ##
62
143
 
63
144
  CurrentApp = make_address_desc([0, KCurrentProcess])
64
145
 
@@ -73,8 +154,7 @@ module Connect
73
154
  rescue AE::MacOSError => err
74
155
  if err.to_i == -600 # Application isn't running, so launch it in background and send it a standard 'run' event.
75
156
  sleep(1)
76
- psn = AE.launch_application(path, RunEvent,
77
- LaunchContinue + LaunchNoFileFlags + LaunchDontSwitch)
157
+ psn = launch_application(path, RunEvent)
78
158
  else
79
159
  raise
80
160
  end