rubyosa
Advanced tools
| # Ask BBEdit to run the uptime(1) command and get the result. | ||
| begin require 'rubygems'; rescue LoadError; end | ||
| require 'rbosa' | ||
| puts 'Asking for uptime...' | ||
| bbedit = OSA.app('BBEdit') | ||
| bbedit.make(OSA::BBEdit::TextDocument).text = <<EOS | ||
| #!/bin/sh | ||
| uptime | ||
| EOS | ||
| bbedit.run_unix_script | ||
| output_doc = bbedit.text_documents.find { |x| x.name == 'Unix Script Output' } | ||
| puts output_doc.text.get |
| # Periodically set your iChat image to one of the default images. | ||
| begin require 'rubygems'; rescue LoadError; end | ||
| require 'rbosa' | ||
| ichat = OSA.app('iChat') | ||
| old_image = ichat.image | ||
| trap('INT') { ichat.image = old_image; exit 0 } | ||
| paths = Dir.glob("/Library/User Pictures/**/*.tif") | ||
| while true do | ||
| paths.each do |path| | ||
| ichat.image = File.read(path) | ||
| sleep 2 | ||
| end | ||
| end |
| # Open the artwork of the current iTunes track in Preview. | ||
| begin require 'rubygems'; rescue LoadError; end | ||
| require 'rbosa' | ||
| artworks = OSA.app('iTunes').current_track.artworks | ||
| if artworks.size == 0 | ||
| puts "No artwork for current track." | ||
| exit 1 | ||
| end | ||
| fname = '/tmp/foo.' + artworks[0].format.downcase.strip | ||
| File.open(fname, 'w') { |io| io.write(artworks[0].data) } | ||
| system("open -a Preview #{fname}") |
| # Create new TextEdit documents with a 'Hello World' text. | ||
| begin require 'rubygems'; rescue LoadError; end | ||
| require 'rbosa' | ||
| textedit = OSA.app('TextEdit') | ||
| # Complex way. | ||
| textedit.make(OSA::TextEdit::Document, nil, nil, {:ctxt => 'Hello World #1'}) | ||
| # Easier way. | ||
| textedit.make(OSA::TextEdit::Document).text = 'Hello World #2' | ||
| =begin | ||
| # Easiest way, not implemented for now. | ||
| document = OSA::TextEdit::Document.new | ||
| document.text = 'Hello World #3' | ||
| textedit << document | ||
| =end |
+2
-2
@@ -1,3 +0,3 @@ | ||
| Maintainer: | ||
| Laurent Sansonetti <lsansonett@apple.com> | ||
| Maintainer & Author: | ||
| Laurent Sansonetti <lsansonetti@apple.com> | ||
@@ -4,0 +4,0 @@ Contributors: |
+4
-1
@@ -36,5 +36,8 @@ == Introduction == | ||
| Feel free to send feedback to lsansonetti@apple.com. | ||
| Feel free to send feedback to rubyosa-discuss@rubyforge.org. | ||
| You can also file bugs, patches and feature requests to the tracker: | ||
| http://rubyforge.org/tracker/?group_id=1845. | ||
| When reporting a bug, please set the AEDebugSends and AEDebugReceives | ||
| environment variables to 1 and attach the logs. |
@@ -0,1 +1,4 @@ | ||
| # Lists the content of the Finder desktop. | ||
| begin require 'rubygems'; rescue LoadError; end | ||
| require 'rbosa' | ||
@@ -2,0 +5,0 @@ |
| # Periodically set your iChat status to the output of uptime(1). | ||
| begin require 'rubygems'; rescue LoadError; end | ||
| require 'rbosa' | ||
@@ -8,5 +9,8 @@ | ||
| trap('INT') { app.status_message = previous_status_message; exit 0 } | ||
| while true | ||
| app.status_message = `uptime`.strip | ||
| while true | ||
| u = `uptime` | ||
| hours = u.scan(/^\s*(\d+:\d+)\s/).to_s + ' hours' | ||
| days = u.scan(/\d+\sdays/).to_s | ||
| app.status_message = "OSX up #{days} #{hours}" | ||
| sleep 5 | ||
| end |
| # Simple iTunes controller. | ||
| begin require 'rubygems'; rescue LoadError; end | ||
| require 'rbosa' | ||
@@ -8,2 +9,3 @@ require 'curses' | ||
| app = OSA.app('iTunes') | ||
| OSA.utf8_strings = true | ||
@@ -10,0 +12,0 @@ if app.current_track.nil? |
| # Start playing, then fade the volume from 0 to the original setting. | ||
| begin require 'rubygems'; rescue LoadError; end | ||
| require 'rbosa' | ||
@@ -4,0 +5,0 @@ |
| # Quick inspection of iTunes' sources, playlists and tracks. | ||
| begin require 'rubygems'; rescue LoadError; end | ||
| require 'rbosa' | ||
| app = OSA.app('iTunes') | ||
| OSA.utf8_strings = true | ||
| app.sources.each do |source| | ||
@@ -7,0 +9,0 @@ puts source.name |
| # Opens given movies and in QuickTime and starts playing them indefinitely in fullscreen mode. | ||
| begin require 'rubygems'; rescue LoadError; end | ||
| require 'rbosa' | ||
@@ -4,0 +5,0 @@ |
+1
-0
| # Print the given application's sdef(5). | ||
| begin require 'rubygems'; rescue LoadError; end | ||
| require 'rbosa' | ||
@@ -4,0 +5,0 @@ require 'rexml/document' |
+112
-42
@@ -27,7 +27,11 @@ # Copyright (c) 2006, Apple Computer, Inc. All rights reserved. | ||
| $KCODE = 'u' # we will use UTF-8 strings | ||
| require 'osa' | ||
| require 'date' | ||
| require 'uri' | ||
| require 'iconv' | ||
| # Try to load RubyGems first, libxml-ruby may have been installed by it. | ||
| begin require 'rubygems' rescue LoadError end | ||
| begin require 'rubygems'; rescue LoadError; end | ||
@@ -73,3 +77,3 @@ # If libxml-ruby is not present, switch to REXML. | ||
| def to_4cc | ||
| OSA.__four_char_code__(self) | ||
| OSA.__four_char_code__(Iconv.iconv('MACROMAN', 'UTF-8', self).to_s) | ||
| end | ||
@@ -117,3 +121,4 @@ end | ||
| def self.from_rbobj(requested_type, value, enum_group_codes) | ||
| self.__new__(*OSA.convert_to_osa(requested_type, value, enum_group_codes)) | ||
| obj = OSA.convert_to_osa(requested_type, value, enum_group_codes) | ||
| obj.is_a?(OSA::Element) ? obj : self.__new__(*obj) | ||
| end | ||
@@ -158,2 +163,6 @@ end | ||
| def empty? | ||
| length == 0 | ||
| end | ||
| def [](idx) | ||
@@ -243,3 +252,3 @@ idx += 1 # AE starts counting at 1. | ||
| def self.convert_to_ruby(osa_object) | ||
| osa_type = osa_object.__type__.to_s | ||
| osa_type = osa_object.__type__ | ||
| osa_data = osa_object.__data__(osa_type) if osa_type and osa_type != 'null' | ||
@@ -253,2 +262,20 @@ if conversion = @conversions_to_ruby[osa_type] | ||
| def self.convert_to_osa(requested_type, value, enum_group_codes=nil) | ||
| if requested_type.nil? | ||
| case value | ||
| when OSA::Element | ||
| return value | ||
| when String | ||
| requested_type = 'text' | ||
| when Array | ||
| requested_type = 'list' | ||
| when Hash | ||
| requested_type = 'record' | ||
| when Integer | ||
| requested_type = 'integer' | ||
| else | ||
| STDERR.puts "can't determine OSA type for #{value}" if $VERBOSE | ||
| ['null', nil] | ||
| end | ||
| end | ||
| if conversion = @conversions_to_osa[requested_type] | ||
@@ -259,3 +286,6 @@ args = [value, requested_type] | ||
| ['enum', value.code.to_4cc] | ||
| else | ||
| elsif md = /^list_of_(.+)$/.match(requested_type) | ||
| ary = value.to_a.map { |x| convert_to_osa(md[1], x, enum_group_codes) } | ||
| ElementList.__new__(ary) | ||
| else | ||
| STDERR.puts "unrecognized type #{requested_type}" if $VERBOSE | ||
@@ -373,8 +403,14 @@ ['null', nil] | ||
| # Creates properties. | ||
| element.find('property').each do |pelement| | ||
| name = pelement['name'] | ||
| code = pelement['code'] | ||
| type = pelement['type'] | ||
| access = pelement['access'] | ||
| description = pelement['description'] | ||
| # Add basic properties that might be missing to the Item class (if any). | ||
| props = {} | ||
| element.find('property').each do |x| | ||
| props[x['name']] = [x['code'], x['type'], x['access'], x['description']] | ||
| end | ||
| if klass.name[-6..-1] == '::Item' | ||
| unless props.has_key?('id') | ||
| props['id'] = ['ID ', 'integer', 'r', 'the unique ID of the item'] | ||
| end | ||
| end | ||
| props.each do |name, pary| | ||
| code, type, access, description = pary | ||
| setter = (access == nil or access.include?('w')) | ||
@@ -420,3 +456,3 @@ | ||
| end | ||
| klass.class_eval(method_code) | ||
@@ -477,3 +513,3 @@ ptypedoc = if pklass.nil? | ||
| [['----', Element.__new_object_specifier__( | ||
| '#{eklass::CODE}', @app == self ? Element.__new__('null', nil) : self, | ||
| '#{eklass::CODE}'.to_4cc, @app == self ? Element.__new__('null', nil) : self, | ||
| 'indx', Element.__new__('abso', 'all '.to_4cc))]], | ||
@@ -582,2 +618,4 @@ true).to_rbobj | ||
| code = Iconv.iconv('MACROMAN', 'UTF-8', code).to_s | ||
| method_code = <<EOC | ||
@@ -677,6 +715,3 @@ def %METHOD_NAME%(#{p_dec.join(', ')}) | ||
| def self.new_element_code(type, varname, enum_group_codes) | ||
| if md = /^list_of_(.+)$/.match(type) | ||
| return "#{varname}.is_a?(OSA::Element) ? #{varname} : ElementList.__new__(#{varname}.to_a.map { |x| #{new_element_code(md[1], 'x', enum_group_codes)} })" | ||
| end | ||
| "#{varname}.is_a?(OSA::Element) ? #{varname} : Element.from_rbobj('#{type}', #{varname}, #{enum_group_codes.inspect})" | ||
| "#{varname}.is_a?(OSA::Element) ? #{varname} : Element.from_rbobj('#{type}', #{varname}, #{enum_group_codes.keys.inspect})" | ||
| end | ||
@@ -689,7 +724,8 @@ | ||
| def self.rubyfy_constant_string(string, upcase=false) | ||
| if /^\d/.match(string) | ||
| string = 'C' << string | ||
| else | ||
| string = string.dup | ||
| string[0] = string[0].chr.upcase | ||
| string = string.gsub(/[^\w\s]/, '') | ||
| first = string[0] | ||
| if (?a..?z).include?(first) | ||
| string[0] = first.chr.upcase | ||
| elsif !(?A..?Z).include?(first) | ||
| string.insert(0, 'C') | ||
| end | ||
@@ -699,6 +735,6 @@ escape_string(upcase ? string.upcase : string.gsub(/\s(.)/) { |s| s[1].chr.upcase }) | ||
| RUBY_RESERVED_KEYWORDS = ['for', 'in'] | ||
| RUBY_RESERVED_KEYWORDS = ['for', 'in', 'class'] | ||
| def self.rubyfy_string(string, handle_ruby_reserved_keywords=false) | ||
| # Prefix with '_' parameter names to avoid possible collisions with reserved Ruby keywords (for, etc...). | ||
| if RUBY_RESERVED_KEYWORDS.include?(string) | ||
| if handle_ruby_reserved_keywords and RUBY_RESERVED_KEYWORDS.include?(string) | ||
| '_' + string | ||
@@ -711,14 +747,17 @@ else | ||
| def self.rubyfy_method(string, klass, return_type=nil, setter=false) | ||
| s = rubyfy_string(string) | ||
| if setter | ||
| # Suffix setters with '='. | ||
| s << '=' | ||
| elsif return_type == 'boolean' | ||
| # Suffix predicates with '?'. | ||
| s << '?' | ||
| base = rubyfy_string(string) | ||
| s, i = base.dup, 1 | ||
| loop do | ||
| if setter | ||
| # Suffix setters with '='. | ||
| s << '=' | ||
| elsif return_type == 'boolean' | ||
| # Suffix predicates with '?'. | ||
| s << '?' | ||
| end | ||
| break unless klass.method_defined?(s) | ||
| # Suffix with an integer if the class already has a method with such a name. | ||
| i += 1 | ||
| s = base + i.to_s | ||
| end | ||
| # Prefix with 'osa_' in case the class already has a method with such a name. | ||
| if klass.method_defined?(s) | ||
| s = 'osa_' + s | ||
| end | ||
| return s | ||
@@ -736,11 +775,17 @@ end | ||
| # String, force TEXT type to not get Unicode. | ||
| OSA.add_conversion_to_ruby('TEXT', 'utxt') { |value, type, object| object.__data__('TEXT') } | ||
| OSA.add_conversion_to_osa('string', 'text', 'Unicode text') { |value| ['TEXT', value.to_s] } | ||
| # String, for unicode stuff force utf8 type if specified. | ||
| OSA.add_conversion_to_ruby('TEXT') { |value, type, object| object.__data__('TEXT') } | ||
| OSA.add_conversion_to_ruby('utxt', 'utf8') { |value, type, object| object.__data__(OSA.utf8_strings ? 'utf8' : 'TEXT') } | ||
| OSA.add_conversion_to_osa('string', 'text') { |value| ['TEXT', value.to_s] } | ||
| OSA.add_conversion_to_osa('Unicode text') { |value| [OSA.utf8_strings ? 'utf8' : 'TEXT', value.to_s] } | ||
| # Signed/unsigned integer. | ||
| OSA.add_conversion_to_ruby('shor', 'long', 'comp') { |value| value.unpack('l').first } | ||
| OSA.add_conversion_to_ruby('shor', 'long') { |value| value.unpack('l').first } | ||
| OSA.add_conversion_to_ruby('comp') { |value| value.unpack('q').first } | ||
| OSA.add_conversion_to_ruby('magn') { |value| value.unpack('d').first } | ||
| OSA.add_conversion_to_osa('integer', 'double integer') { |value| ['magn', [value].pack('l')] } | ||
| # Float | ||
| OSA.add_conversion_to_ruby('sing') { |value| value.unpack('f').first } | ||
| # Boolean. | ||
@@ -765,11 +810,29 @@ OSA.add_conversion_to_ruby('bool') { |value| value.unpack('c').first != 0 } | ||
| OSA.add_conversion_to_osa('alias', 'file') { |value| ['furl', value.to_s] } | ||
| OSA.add_conversion_to_ruby('alis') { |value, type, object| URI.parse(object.__data__('furl')).path } | ||
| # Hash. | ||
| OSA.add_conversion_to_ruby('reco') { |value, type, object| object.is_a?(OSA::ElementRecord) ? object.to_hash : self } | ||
| OSA.add_conversion_to_ruby('reco') { |value, type, object| object.is_a?(OSA::ElementRecord) ? object.to_hash : value } | ||
| OSA.add_conversion_to_osa('record') do |value| | ||
| if value.is_a?(Hash) | ||
| value.each { |key, val| value[key] = OSA::Element.from_rbobj(nil, val, nil) } | ||
| OSA::ElementRecord.__new__(value) | ||
| else | ||
| value | ||
| end | ||
| end | ||
| # Enumerator. | ||
| OSA.add_conversion_to_ruby('enum') { |value, type, object| OSA::Enumerator.enum_for_code(object.__data__('TEXT')) or self } | ||
| OSA.add_conversion_to_ruby('enum') { |value, type, object| OSA::Enumerator.enum_for_code(object.__data__('TEXT')) or object } | ||
| # Class. | ||
| OSA.add_conversion_to_osa('type class') { |value| value.is_a?(Class) and value.ancestors.include?(OSA::Element) ? ['type', value::CODE.to_4cc] : self } | ||
| OSA.add_conversion_to_osa('type class') { |value| value.is_a?(Class) and value.ancestors.include?(OSA::Element) ? ['type', value::CODE.to_4cc] : value } | ||
| OSA.add_conversion_to_ruby('type') do |value, type, object| | ||
| if value == 'msng' | ||
| # Missing values. | ||
| nil | ||
| else | ||
| hash = object.instance_variable_get(:@app).instance_variable_get(:@classes) | ||
| hash[value] or value | ||
| end | ||
| end | ||
@@ -779,1 +842,8 @@ # QuickDraw Rectangle, aka "bounding rectangle". | ||
| OSA.add_conversion_to_osa('bounding rectangle') { |value| ['qdrt', value.pack('S4')] } | ||
| # Pictures (just return the raw data). | ||
| OSA.add_conversion_to_ruby('PICT') { |value, type, object| value[222..-1] } # Removing trailing garbage. | ||
| OSA.add_conversion_to_osa('picture') { |value| ['PICT', value.to_s] } | ||
| OSA.add_conversion_to_ruby('imaA') { |value, type, object| value } | ||
| OSA.add_conversion_to_osa('Image') { |value| ['imaA', value.to_s] } | ||
| OSA.add_conversion_to_osa('TIFF picture') { |value| ['TIFF', value.to_s] } |
+104
-2
@@ -30,2 +30,3 @@ /* | ||
| #include "rbosa.h" | ||
| #include <st.h> | ||
@@ -198,2 +199,50 @@ static VALUE mOSA; | ||
| static void | ||
| __rbosa_raise_potential_app_error (AEDesc *reply) | ||
| { | ||
| OSErr error; | ||
| AEDesc errorNumDesc; | ||
| AEDesc errorStringDesc; | ||
| int errorNum; | ||
| char exception[128]; | ||
| if (AEGetParamDesc (reply, keyErrorNumber, typeInteger, &errorNumDesc) != noErr) | ||
| return; | ||
| if (AEGetDescData (&errorNumDesc, &errorNum, sizeof errorNum) != noErr | ||
| || (errorNum = CFSwapInt32HostToBig (errorNum)) == 0) { | ||
| AEDisposeDesc (&errorNumDesc); | ||
| return; | ||
| } | ||
| /* The reply is an application error. */ | ||
| exception[0] = '\0'; | ||
| error = AEGetParamDesc (reply, keyErrorString, typeChar, &errorStringDesc); | ||
| if (error == noErr) { | ||
| Size size; | ||
| size = AEGetDescDataSize (&errorStringDesc); | ||
| if (size > 0) { | ||
| char *msg; | ||
| msg = (char *)malloc (size); | ||
| if (msg != NULL) { | ||
| if (AEGetDescData (&errorStringDesc, &msg, size) == noErr) | ||
| snprintf (exception, sizeof exception, "application returned error %d with message '%s'", errorNum, msg); | ||
| free (msg); | ||
| } | ||
| } | ||
| AEDisposeDesc (&errorStringDesc); | ||
| } | ||
| if (exception[0] == '\0') | ||
| snprintf (exception, sizeof exception, "application returned error %d", errorNum); | ||
| AEDisposeDesc (&errorNumDesc); | ||
| rb_raise (rb_eRuntimeError, exception); | ||
| } | ||
| static VALUE | ||
@@ -254,2 +303,4 @@ rbosa_app_send_event (VALUE self, VALUE event_class, VALUE event_id, VALUE params, VALUE need_retval) | ||
| __rbosa_raise_potential_app_error (&reply); | ||
| if (RTEST (need_retval)) { | ||
@@ -290,4 +341,6 @@ VALUE rb_reply; | ||
| VALUE retval; | ||
| bool to_4cc; | ||
| rb_scan_args (argc, argv, "01", &coerce_type); | ||
| to_4cc = false; | ||
@@ -297,3 +350,6 @@ desc = rbosa_element_aedesc (self); | ||
| if (!NIL_P (coerce_type)) { | ||
| error = AECoerceDesc (desc, RVAL2FOURCHAR (coerce_type), &coerced_desc); | ||
| FourCharCode code; | ||
| code = RVAL2FOURCHAR (coerce_type); | ||
| error = AECoerceDesc (desc, code, &coerced_desc); | ||
| if (error != noErr) | ||
@@ -304,2 +360,3 @@ rb_raise (rb_eRuntimeError, "Cannot coerce desc to type %s : %s (%d)", | ||
| desc = &coerced_desc; | ||
| to_4cc = code == 'type'; | ||
| } | ||
@@ -313,3 +370,10 @@ | ||
| error = AEGetDescData (desc, data, datasize); | ||
| retval = error == noErr ? rb_str_new (data, datasize) : Qnil; | ||
| if (error == noErr) { | ||
| if (to_4cc) | ||
| *(DescType*)data = CFSwapInt32HostToBig (*(DescType*)data); | ||
| retval = rb_str_new (data, datasize); | ||
| } | ||
| else { | ||
| retval = Qnil; | ||
| } | ||
@@ -492,3 +556,39 @@ if (!NIL_P (coerce_type)) | ||
| static int | ||
| __rbosa_elementrecord_set (VALUE key, VALUE value, AEDescList *list) | ||
| { | ||
| OSErr error; | ||
| error = AEPutKeyDesc (list, RVAL2FOURCHAR (key), rbosa_element_aedesc (value)); | ||
| if (error != noErr) | ||
| rb_raise (rb_eRuntimeError, "Cannot set value %p for key %p of record %p: %s (%d)", | ||
| value, key, list, GetMacOSStatusErrorString (error), error); | ||
| return ST_CONTINUE; | ||
| } | ||
| static VALUE | ||
| rbosa_elementrecord_new (int argc, VALUE *argv, VALUE self) | ||
| { | ||
| OSErr error; | ||
| AEDescList list; | ||
| VALUE hash; | ||
| rb_scan_args (argc, argv, "01", &hash); | ||
| if (!NIL_P (hash)) | ||
| Check_Type (hash, T_HASH); | ||
| error = AECreateList (NULL, 0, true, &list); | ||
| if (error != noErr) | ||
| rb_raise (rb_eRuntimeError, "Cannot create Apple Event descriptor list : %s (%d)", | ||
| GetMacOSStatusErrorString (error), error); | ||
| if (!NIL_P (hash)) | ||
| rb_hash_foreach (hash, __rbosa_elementrecord_set, (VALUE)&list); | ||
| return rbosa_element_make (self, &list, Qnil); | ||
| } | ||
| static VALUE | ||
| rbosa_elementrecord_to_a (VALUE self) | ||
@@ -552,2 +652,3 @@ { | ||
| cOSAElementRecord = rb_define_class_under (mOSA, "ElementRecord", cOSAElement); | ||
| rb_define_singleton_method (cOSAElementRecord, "__new__", rbosa_elementrecord_new, -1); | ||
| rb_define_method (cOSAElementRecord, "to_a", rbosa_elementrecord_to_a, 0); | ||
@@ -560,2 +661,3 @@ | ||
| rbosa_define_param ("lazy_events", Qtrue); | ||
| rbosa_define_param ("utf8_strings", Qfalse); | ||
| } |