ruby-dbus-0.7.2/0000755000004100000410000000000011741120355013463 5ustar www-datawww-dataruby-dbus-0.7.2/README0000644000004100000410000000252711741120355014351 0ustar www-datawww-data= Ruby D-Bus README Ruby D-Bus provides an implementation of the D-Bus protocol such that the D-Bus system can be used in the Ruby programming language. == Requirements * Ruby 1.8.7 or 1.9 == Installation * gem install ruby-dbus == Feature Ruby D-Bus currently supports the following features: * Connecting to local buses. * Accessing remote services, objects and interfaces. * Invoking methods on remote objects synchronously and asynchronously. * Catch signals on remote objects and handle them via callbacks. * Remote object introspection. * Walking object trees. * Creating services and registering them on the bus. * Exporting objects with interfaces on a bus for remote use. * Rubyish D-Bus object and interface syntax support that automatically allows for introspection. * Emitting signals on exported objects. == Usage See some of the examples in the examples/ subdirectory of the tarball. Also, check out the included tutorial (in Markdown format) in doc/tutorial/ or view it online on https://github.com/mvidner/ruby-dbus/blob/master/doc/tutorial/index.markdown . == License Ruby D-Bus is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. ruby-dbus-0.7.2/test/0000755000004100000410000000000011741120355014442 5ustar www-datawww-dataruby-dbus-0.7.2/test/binding_test.rb0000755000004100000410000000334611741120355017451 0ustar www-datawww-data#!/usr/bin/env ruby # Test the binding of dbus concepts to ruby concepts require "test/unit" require "dbus" class BindingTest < Test::Unit::TestCase def setup @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") @base = @svc.object "/org/ruby/MyInstance" @base.introspect @base.default_iface = "org.ruby.SampleInterface" end # https://trac.luon.net/ruby-dbus/ticket/36#comment:3 def test_class_inheritance derived = @svc.object "/org/ruby/MyDerivedInstance" derived.introspect # it should inherit from the parent assert_not_nil derived["org.ruby.SampleInterface"] end # https://trac.luon.net/ruby-dbus/ticket/36 # Interfaces and methods/signals appeared on all classes def test_separation_of_classes test2 = @svc.object "/org/ruby/MyInstance2" test2.introspect # it should have its own interface assert_not_nil test2["org.ruby.Test2"] # but not an interface of the Test class assert_nil test2["org.ruby.SampleInterface"] # and the parent should not get polluted by the child assert_nil @base["org.ruby.Test2"] end def test_translating_errors_into_exceptions # this is a generic call that will reply with the specified error @base.Error "org.example.Fail", "as you wish" assert false, "should have raised" rescue DBus::Error => e assert_equal "org.example.Fail", e.name assert_equal "as you wish", e.message end def test_generic_dbus_error # this is a generic call that will reply with the specified error @base.will_raise_error_failed assert false, "should have raised" rescue DBus::Error => e assert_equal "org.freedesktop.DBus.Error.Failed", e.name assert_equal "failed as designed", e.message end end ruby-dbus-0.7.2/test/t3-ticket27.rb0000755000004100000410000000135111741120355016752 0ustar www-datawww-data#!/usr/bin/env ruby # Test passing a particular struct array through a variant # https://trac.luon.net/ruby-dbus/ticket/27 require "dbus" session_bus = DBus::ASessionBus.new svc = session_bus.service("org.ruby.service") obj = svc.object("/org/ruby/MyInstance") obj.introspect # necessary obj.default_iface = "org.ruby.SampleInterface" # The bug is probably alignment related so whether it triggers # depends also on the combined length of service, interface, # and method names. Luckily here it works out. triple = ['a(uuu)', []] obj.test_variant(triple) quadruple = ['a(uuuu)', []] # a(uuu) works fine # The bus disconnects us because of malformed message, # code 12: DBUS_INVALID_TOO_MUCH_DATA obj.test_variant(quadruple) ruby-dbus-0.7.2/test/service_newapi.rb0000755000004100000410000001424311741120355020001 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: utf-8 -*- # find the library without external help $:.unshift File.expand_path("../../lib", __FILE__) require 'dbus' def d(msg) puts "#{$$} #{msg}" if $DEBUG end PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties" class Test < DBus::Object INTERFACE = "org.ruby.SampleInterface" def initialize(path) super path @read_me = "READ ME" @read_or_write_me = "READ OR WRITE ME" end # Create an interface aggregating all upcoming dbus_method defines. dbus_interface INTERFACE do dbus_method :hello, "in name:s, in name2:s" do |name, name2| puts "hello(#{name}, #{name2})" end dbus_method :test_variant, "in stuff:v" do |variant| p variant end dbus_method :bounce_variant, "in stuff:v, out chaff:v" do |variant| [variant] end dbus_method :variant_size, "in stuff:v, out size:u" do |variant| [variant.size] end dbus_method :the_answer, "out answer:i" do 42 end dbus_method :will_raise, "" do raise "Handle this" end dbus_method :will_raise_error_failed, "" do raise DBus.error, "failed as designed" end dbus_method :will_raise_name_error, "" do "foo".frobnicate end dbus_method :Error, "in name:s, in description:s" do |name, description| raise DBus.error(name), description end end # closing and reopening the same interface dbus_interface INTERFACE do dbus_method :multibyte_string, "out string:s" do "あいうえお" end dbus_signal :SomethingJustHappened, "toto:s, tutu:u" end dbus_interface "org.ruby.AnotherInterface" do dbus_method :ThatsALongMethodNameIThink do puts "ThatsALongMethodNameIThink" end dbus_method :Reverse, "in instr:s, out outstr:s" do |instr| outstr = instr.split(//).reverse.join [outstr] end end dbus_interface "org.ruby.Ticket30" do dbus_method :Sybilla, 'in choices:av, out advice:s' do |choices| ["Do #{choices[0]}"] end end dbus_interface "org.ruby.Loop" do # starts doing something long, but returns immediately # and sends a signal when done dbus_method :LongTaskBegin, 'in delay:i' do |delay| # FIXME did not complain about mismatch between signature and block args d "Long task began" task = Thread.new do d "Long task thread started (#{delay}s)" sleep delay d "Long task will signal end" self.LongTaskEnd end task.abort_on_exception = true # protect from test case bugs end dbus_signal :LongTaskEnd end # Properties: # ReadMe:string, returns "READ ME" at first, then what WriteMe received # WriteMe:string # ReadOrWriteMe:string, returns "READ OR WRITE ME" at first dbus_interface PROPERTY_INTERFACE do dbus_method :Get, "in interface:s, in propname:s, out value:v" do |interface, propname| if interface == INTERFACE if propname == "ReadMe" [@read_me] elsif propname == "ReadOrWriteMe" [@read_or_write_me] elsif propname == "WriteMe" raise DBus.error("org.freedesktop.DBus.Error.InvalidArgs"), "Property '#{interface}.#{propname}' (on object '#{@path}') is not readable" else raise DBus.error("org.freedesktop.DBus.Error.InvalidArgs"), "Property '#{interface}.#{propname}' not found on object '#{@path}'" end else raise DBus.error("org.freedesktop.DBus.Error.UnknownInterface"), "Interface '#{interface}' not found on object '#{@path}'" end # what should happen for unknown properties # plasma: InvalidArgs (propname), UnknownInterface (interface) end dbus_method :Set, "in interface:s, in propname:s, in value:v" do |interface, propname, value| if interface == INTERFACE if propname == "ReadMe" raise DBus.error("org.freedesktop.DBus.Error.InvalidArgs"), "Property '#{interface}.#{propname}' (on object '#{@path}') is not writable" elsif propname == "ReadOrWriteMe" @read_or_write_me = value self.PropertiesChanged(interface, {propname => value}, []) elsif propname == "WriteMe" @read_me = value self.PropertiesChanged(interface, {"ReadMe" => value}, []) else raise DBus.error("org.freedesktop.DBus.Error.InvalidArgs"), "Property '#{interface}.#{propname}' not found on object '#{@path}'" end else raise DBus.error("org.freedesktop.DBus.Error.UnknownInterface"), "Interface '#{interface}' not found on object '#{@path}'" end end dbus_method :GetAll, "in interface:s, out value:a{sv}" do |interface| if interface == INTERFACE [ { "ReadMe" => @read_me, "ReadOrWriteMe" =>@read_or_write_me, } ] else raise DBus.error("org.freedesktop.DBus.Error.UnknownInterface"), "Interface '#{interface}' not found on object '#{@path}'" end end dbus_signal :PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as" end end class Derived < Test end class Test2 < DBus::Object dbus_interface "org.ruby.Test2" do dbus_method :hi, "in name:s, out greeting:s" do |name| "Hi, #{name}!" end end end bus = DBus::SessionBus.instance service = bus.request_service("org.ruby.service") myobj = Test.new("/org/ruby/MyInstance") service.export(myobj) derived = Derived.new "/org/ruby/MyDerivedInstance" service.export derived test2 = Test2.new "/org/ruby/MyInstance2" service.export test2 # introspect every other connection, Ticket #34 # (except the one that activates us - it has already emitted # NOC by the time we run this. Therefore the test for #34 will not work # by running t2.rb alone, one has to run t1 before it; 'rake' does it) mr = DBus::MatchRule.new.from_s "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged'" bus.add_match(mr) do |msg| new_unique_name = msg.params[2] unless new_unique_name.empty? d "RRRING #{new_unique_name}" bus.introspect_data(new_unique_name, "/") do # ignore the result end end end puts "listening, with ruby-#{RUBY_VERSION}" main = DBus::Main.new main << bus begin main.run rescue SystemCallError # the test driver will kill the bus, that's OK end ruby-dbus-0.7.2/test/server_test.rb0000755000004100000410000000241111741120355017335 0ustar www-datawww-data#!/usr/bin/env ruby # Test that a server survives various error cases require "test/unit" require "dbus" class Foo < DBus::Object dbus_interface "org.ruby.ServerTest" do dbus_signal :signal_without_arguments dbus_signal :signal_with_argument, "epsilon:d" end dbus_signal :signal_without_interface rescue DBus::Object::UndefinedInterface => e # raised by the preceding signal declaration end class Bar < DBus::Object dbus_interface "org.ruby.ServerTest" do # a valid Ruby symbol but an invalid DBus name; Ticket#38 dbus_signal :signal_with_a_bang! end rescue DBus::InvalidMethodName # raised by the preceding signal declaration end class ServerTest < Test::Unit::TestCase def setup @bus = DBus::ASessionBus.new @svc = @bus.request_service "org.ruby.server-test" end def teardown @bus.proxy.ReleaseName "org.ruby.server-test" end def test_unexporting_an_object obj = Foo.new "/org/ruby/Foo" @svc.export obj assert @svc.unexport(obj) end def test_unexporting_an_object_not_exported obj = Foo.new "/org/ruby/Foo" assert !@svc.unexport(obj) end def test_emiting_signals obj = Foo.new "/org/ruby/Foo" @svc.export obj obj.signal_without_arguments obj.signal_with_argument(-0.1) end end ruby-dbus-0.7.2/test/t10000755000004100000410000000027111741120355014714 0ustar www-datawww-data#! /bin/sh SEND0="dbus-send --session --dest=org.ruby.service" CALL="$SEND0 --type=method_call --print-reply" $CALL /org/ruby/MyInstance org.ruby.AnotherInterface.Reverse string:Hello ruby-dbus-0.7.2/test/dbus-limited-session.conf0000644000004100000410000000165111741120355021357 0ustar www-datawww-data session unix:tmpdir=/tmp tcp:host=localhost,port=0,family=ipv4 50 ruby-dbus-0.7.2/test/test_env0000755000004100000410000000041711741120355016221 0ustar www-datawww-data#! /bin/bash # test_env: set up the environment needed to run tests: # - set up a private bus # - run a test server on it #export DBUS_VERBOSE=1 #export RUBYOPT="-d" export RUBYOPT="$RUBYOPT -w" ./test_server \ ./service_newapi.rb \ -- \ ./dbus-launch-simple \ "$@" ruby-dbus-0.7.2/test/async_test.rb0000755000004100000410000000201211741120355017141 0ustar www-datawww-data#!/usr/bin/env ruby # Test the binding of dbus concepts to ruby concepts require "test/unit" require "dbus" class AsyncTest < Test::Unit::TestCase def setup @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") @obj = @svc.object "/org/ruby/MyInstance" @obj.introspect @obj.default_iface = "org.ruby.SampleInterface" end # https://github.com/mvidner/ruby-dbus/issues/13 def test_async_call_to_default_interface loop = DBus::Main.new loop << @bus immediate_answer = @obj.the_answer do |msg, retval| assert_equal 42, retval loop.quit end assert_nil immediate_answer # wait for the async reply loop.run end def test_async_call_to_explicit_interface loop = DBus::Main.new loop << @bus ifc = @obj["org.ruby.AnotherInterface"] immediate_answer = ifc.Reverse("abcd") do |msg, retval| assert_equal "dcba", retval loop.quit end assert_nil immediate_answer # wait for the async reply loop.run end end ruby-dbus-0.7.2/test/property_test.rb0000755000004100000410000000310511741120355017714 0ustar www-datawww-data#!/usr/bin/env ruby require "test/unit" require "dbus" class PropertyTest < Test::Unit::TestCase def setup session_bus = DBus::ASessionBus.new svc = session_bus.service("org.ruby.service") @obj = svc.object("/org/ruby/MyInstance") @obj.introspect @iface = @obj["org.ruby.SampleInterface"] end def test_property_reading assert_equal "READ ME", @iface["ReadMe"] end def test_property_nonreading e = assert_raises DBus::Error do @iface["WriteMe"] end assert_match(/not readable/, e.to_s) end def test_property_writing @iface["ReadOrWriteMe"] = "VALUE" assert_equal "VALUE", @iface["ReadOrWriteMe"] end # https://github.com/mvidner/ruby-dbus/pull/19 def test_service_select_timeout @iface["ReadOrWriteMe"] = "VALUE" assert_equal "VALUE", @iface["ReadOrWriteMe"] # wait for the service to become idle sleep 6 assert_equal "VALUE", @iface["ReadOrWriteMe"], "Property value changed; perhaps the service died and got restarted" end def test_property_nonwriting e = assert_raises DBus::Error do @iface["ReadMe"] = "WROTE" end assert_match(/not writable/, e.to_s) end def test_get_all all = @iface.all_properties assert_equal ["ReadMe", "ReadOrWriteMe"], all.keys.sort end def test_unknown_property_reading e = assert_raises DBus::Error do @iface["Spoon"] end assert_match(/not found/, e.to_s) end def test_unknown_property_writing e = assert_raises DBus::Error do @iface["Spoon"] = "FORK" end assert_match(/not found/, e.to_s) end end ruby-dbus-0.7.2/test/bus_driver_test.rb0000755000004100000410000000076711741120355020207 0ustar www-datawww-data#!/usr/bin/env ruby # Test the methods of the bus driver require "test/unit" require "dbus" def d(msg) puts msg if $DEBUG end class BusDriverTest < Test::Unit::TestCase def setup @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") @svc.object("/").introspect end def test_exists assert @svc.exists?, "could not find the service" nonsvc = @bus.service "org.ruby.nosuchservice" assert ! nonsvc.exists?, "found a service that should not exist" end end ruby-dbus-0.7.2/test/signal_test.rb0000755000004100000410000000253511741120355017313 0ustar www-datawww-data#!/usr/bin/env ruby # Test the signal handlers require "test/unit" require "dbus" def d(msg) puts "#{$$} #{msg}" if $DEBUG end class SignalHandlerTest < Test::Unit::TestCase def setup @session_bus = DBus::ASessionBus.new svc = @session_bus.service("org.ruby.service") @obj = svc.object("/org/ruby/MyInstance") @obj.introspect # necessary @obj.default_iface = "org.ruby.Loop" @loop = DBus::Main.new @loop << @session_bus end # testing for commit 017c83 (kkaempf) def test_overriding_a_handler counter = 0 @obj.on_signal "LongTaskEnd" do d "+10" counter += 10 end @obj.on_signal "LongTaskEnd" do d "+1" counter += 1 end d "will begin" @obj.LongTaskBegin 3 quitter = Thread.new do d "sleep before quit" # FIXME if we sleep for too long # the socket will be drained and we deadlock in a select. # It could be worked around by sending ourselves a Unix signal # (with a dummy handler) to interrupt the select sleep 1 d "will quit" @loop.quit end @loop.run assert_equal 1, counter end def test_too_many_rules 100.times do @obj.on_signal "Whichever" do puts "not called" end end end def test_removing_a_nonexistent_rule @obj.on_signal "DoesNotExist" end end ruby-dbus-0.7.2/test/t5-report-dbus-interface.rb0000755000004100000410000000306311741120355021526 0ustar www-datawww-data#!/usr/bin/env ruby # should report it missing on org.ruby.SampleInterface # (on object...) instead of on DBus::Proxy::ObjectInterface require "test/unit" require "dbus" class ErrMsgTest < Test::Unit::TestCase def setup session_bus = DBus::ASessionBus.new svc = session_bus.service("org.ruby.service") @obj = svc.object("/org/ruby/MyInstance") @obj.introspect # necessary @obj.default_iface = "org.ruby.SampleInterface" end def test_report_dbus_interface begin @obj.NoSuchMethod # a specific exception... rescue NameError => e # mentioning DBus and the interface assert_match(/DBus interface.*#{@obj.default_iface}/, e.to_s) end end def test_report_short_struct begin @obj.test_variant ["(ss)", ["too few"] ] rescue DBus::TypeException => e assert_match(/1 elements but type info for 2/, e.to_s) end end def test_report_long_struct begin @obj.test_variant ["(ss)", ["a", "b", "too many"] ] rescue DBus::TypeException => e assert_match(/3 elements but type info for 2/, e.to_s) end end def test_report_nil nils = [ ["(s)", [nil] ], # would get disconnected ["i", nil ], ["a{ss}", {"foo" => nil} ], ] nils.each do |has_nil| begin @obj.test_variant has_nil rescue DBus::TypeException => e # TODO want backtrace from the perspective of the caller: # rescue/reraise in send_sync? assert_match(/Cannot send nil/, e.to_s) end end end end ruby-dbus-0.7.2/test/t2.rb0000755000004100000410000000454611741120355015330 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: utf-8 -*- require "test/unit" require "dbus" class ValueTest < Test::Unit::TestCase def setup session_bus = DBus::ASessionBus.new svc = session_bus.service("org.ruby.service") @obj = svc.object("/org/ruby/MyInstance") @obj.introspect # necessary @obj.default_iface = "org.ruby.SampleInterface" end def test_passing_an_array_through_a_variant # old explicit typing @obj.test_variant(["as", ["coucou", "kuku"]]) # automatic typing @obj.test_variant(["coucou", "kuku"]) @obj.test_variant(["saint", "was that a word or a signature?"]) end def test_bouncing_a_variant assert_equal "cuckoo", @obj.bounce_variant("cuckoo")[0] assert_equal ["coucou", "kuku"], @obj.bounce_variant(["coucou", "kuku"])[0] assert_equal [], @obj.bounce_variant([])[0] empty_hash = {} assert_equal empty_hash, @obj.bounce_variant(empty_hash)[0] end # these are ambiguous def test_pairs_with_a_string # deprecated assert_equal "foo", @obj.bounce_variant(["s", "foo"])[0] assert_equal "foo", @obj.bounce_variant(DBus.variant("s", "foo"))[0] assert_equal "foo", @obj.bounce_variant([DBus.type("s"), "foo"])[0] # does not work, because the server side forgets the explicit typing # assert_equal ["s", "foo"], @obj.bounce_variant(["av", ["s", "foo"]])[0] # assert_equal ["s", "foo"], @obj.bounce_variant(["as", ["s", "foo"]])[0] # instead, use this to demonstrate that the variant is passed as expected assert_equal 4, @obj.variant_size(["s", "four"])[0] # "av" is the simplest thing that will work, # shifting the heuristic from a pair to the individual items assert_equal 2, @obj.variant_size(["av", ["s", "four"]])[0] end def test_marshalling_an_array_of_variants # https://trac.luon.net/ruby-dbus/ticket/30 @obj.default_iface = "org.ruby.Ticket30" choices = [] choices << ['s', 'Plan A'] choices << ['s', 'Plan B'] # old explicit typing assert_equal "Do Plan A", @obj.Sybilla(choices)[0] # automatic typing assert_equal "Do Plan A", @obj.Sybilla(["Plan A", "Plan B"])[0] end def test_service_returning_nonarray # "warning: default `to_a' will be obsolete" @obj.the_answer end def test_multibyte_string str = @obj.multibyte_string[0] assert_equal "あいうえお", str end end ruby-dbus-0.7.2/test/test_server0000755000004100000410000000136411741120355016741 0ustar www-datawww-data#! /bin/sh # A wrapper for DBus tests # Run a server (directly or by dbus activation) and then the test # $0 server [args...] -- test [args...] set -o errexit while [ "$1" != "--" ]; do SERVER="$SERVER $1" shift done shift # -- setup_activation () { SDIR=$XDG_DATA_DIRS/dbus-1/services mkdir -p $SDIR # FIXME Name is hardcoded cat < $SDIR/test.service [D-BUS Service] Name=org.ruby.service Exec=$SERVER EOF } run_server () { echo -n "Hey, server, get on da bus... " # start the server $SERVER & sleep 3 echo "off we go!" } export XDG_DATA_DIRS=`mktemp -d dbus.activation.XXXXXX` RM_FILES="$RM_FILES $XDG_DATA_DIRS" setup_activation #run_server # Clean up at exit. trap "rm -rf \$RM_FILES" EXIT TERM INT "$@" ruby-dbus-0.7.2/test/session_bus_test_manual.rb0000755000004100000410000000064011741120355021722 0ustar www-datawww-data#!/usr/bin/env ruby require "test/unit" require "dbus" def d(msg) puts msg if $DEBUG end class SessionBusAddressTest < Test::Unit::TestCase def setup # test getting the session bus address even if unset in ENV (Issue#4) ENV.delete "DBUS_SESSION_BUS_ADDRESS" @bus = DBus::ASessionBus.new @svc = @bus.service("org.freedesktop.DBus") end def test_connection assert @svc.exists? end end ruby-dbus-0.7.2/test/server_robustness_test.rb0000755000004100000410000000426611741120355021636 0ustar www-datawww-data#!/usr/bin/env ruby # Test that a server survives various error cases require "test/unit" require "dbus" class ServerRobustnessTest < Test::Unit::TestCase def setup @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") end # https://trac.luon.net/ruby-dbus/ticket/31 # the server should not crash def test_no_such_path_with_introspection obj = @svc.object "/org/ruby/NotMyInstance" obj.introspect assert false, "should have raised" rescue DBus::Error => e assert_no_match(/timeout/, e.to_s) end def test_no_such_path_without_introspection obj = @svc.object "/org/ruby/NotMyInstance" ifc = DBus::ProxyObjectInterface.new(obj, "org.ruby.SampleInterface") ifc.define_method("the_answer", "out n:i") ifc.the_answer assert false, "should have raised" rescue DBus::Error => e assert_no_match(/timeout/, e.to_s) end def test_a_method_that_raises obj = @svc.object "/org/ruby/MyInstance" obj.introspect obj.default_iface = "org.ruby.SampleInterface" obj.will_raise assert false, "should have raised" rescue DBus::Error => e assert_no_match(/timeout/, e.to_s) end def test_a_method_that_raises_name_error obj = @svc.object "/org/ruby/MyInstance" obj.introspect obj.default_iface = "org.ruby.SampleInterface" obj.will_raise_name_error assert false, "should have raised" rescue DBus::Error => e assert_no_match(/timeout/, e.to_s) end # https://trac.luon.net/ruby-dbus/ticket/31#comment:3 def test_no_such_method_without_introspection obj = @svc.object "/org/ruby/MyInstance" ifc = DBus::ProxyObjectInterface.new(obj, "org.ruby.SampleInterface") ifc.define_method("not_the_answer", "out n:i") ifc.not_the_answer assert false, "should have raised" rescue DBus::Error => e assert_no_match(/timeout/, e.to_s) end def test_no_such_interface_without_introspection obj = @svc.object "/org/ruby/MyInstance" ifc = DBus::ProxyObjectInterface.new(obj, "org.ruby.NoSuchInterface") ifc.define_method("the_answer", "out n:i") ifc.the_answer assert false, "should have raised" rescue DBus::Error => e assert_no_match(/timeout/, e.to_s) end end ruby-dbus-0.7.2/test/t6-loop.rb0000755000004100000410000000433011741120355016272 0ustar www-datawww-data#!/usr/bin/env ruby # Test the main loop require "test/unit" require "dbus" def d(msg) puts "#{$$} #{msg}" if $DEBUG end class MainLoopTest < Test::Unit::TestCase def setup @session_bus = DBus::ASessionBus.new svc = @session_bus.service("org.ruby.service") @obj = svc.object("/org/ruby/MyInstance") @obj.introspect # necessary @obj.default_iface = "org.ruby.Loop" @loop = DBus::Main.new @loop << @session_bus end # Hack the library internals so that there is a delay between # sending a DBus call and listening for its reply, so that # the bus has a chance to join the server messages and a race is reproducible def call_lazily class << @session_bus alias :wait_for_message_orig :wait_for_message def wait_for_message_lazy d "I am so lazy" sleep 1 # Give the server+bus a chance to join the messages wait_for_message_orig end alias :wait_for_message :wait_for_message_lazy end yield # undo class << @session_bus remove_method :wait_for_message remove_method :wait_for_message_lazy alias :wait_for_message :wait_for_message_orig end end def test_loop_quit(delay = 1) @obj.on_signal "LongTaskEnd" do d "Telling loop to quit" @loop.quit end call_lazily do # The method will sleep the requested amount of seconds # (in another thread) before signalling LongTaskEnd @obj.LongTaskBegin delay end # this thread will make the test fail if @loop.run does not return dynamite = Thread.new do d "Dynamite burning" sleep 2 d "Dynamite explodes" # We need to raise in the main thread. # Simply raising here means the exception is ignored # (until dynamite.join which we don't call) or # (if abort_on_exception is set) it terminates the whole script. Thread.main.raise RuntimeError, "The main loop did not quit in time" end @loop.run d "Defusing dynamite" # if we get here, defuse the bomb dynamite.exit # remove signal handler @obj.on_signal "LongTaskEnd" end # https://bugzilla.novell.com/show_bug.cgi?id=537401 def test_loop_drained_socket test_loop_quit 0 end end ruby-dbus-0.7.2/test/variant_test.rb0000755000004100000410000000346111741120355017501 0ustar www-datawww-data#!/usr/bin/env ruby # Test marshalling variants according to ruby types require "test/unit" require "dbus" class VariantTest < Test::Unit::TestCase def setup @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") end def make_variant(a) DBus::PacketMarshaller.make_variant(a) end def test_make_variant_scalar # special case: do not fail immediately, marshaller will do that assert_equal ["b", nil], make_variant(nil) assert_equal ["b", true], make_variant(true) # Integers # no byte assert_equal ["i", 42], make_variant(42) # 3_000_000_000 can be u or x. # less specific test: just run it thru a loopback assert_equal ["x", 3_000_000_000], make_variant(3_000_000_000) assert_equal ["x", 5_000_000_000], make_variant(5_000_000_000) assert_equal ["d", 3.14], make_variant(3.14) assert_equal ["s", "foo"], make_variant("foo") assert_equal ["s", "bar"], make_variant(:bar) # left: strruct, array, dict # object path: detect exported objects?, signature # # by Ruby types # class Foo # end # make_variant(Foo.new) # if we don;t understand a class, the error should be informative -> new exception end def test_make_variant_array ai = [1, 2, 3] # as = ["one", "two", "three"] # which? # assert_equal ["ai", [1, 2, 3]], make_variant(ai) assert_equal ["av", [["i", 1], ["i", 2], ["i", 3]]], make_variant(ai) a0 = [] assert_equal ["av", []], make_variant(a0) end def test_make_variant_hash h = {"k1" => "v1", "k2" => "v2"} assert_equal ["a{sv}", { "k1" => ["s", "v1"], "k2" => ["s", "v2"], }], make_variant(h) h0 = {} assert_equal ["a{sv}", {}], make_variant(h0) end end ruby-dbus-0.7.2/test/bus_test.rb0000755000004100000410000000076611741120355016633 0ustar www-datawww-data#!/usr/bin/env ruby # Test the bus class require "test/unit" require "dbus" class BusTest < Test::Unit::TestCase def setup @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") @svc.object("/").introspect end def test_introspection_not_leaking # peek inside the object to see if a cleanup step worked or not some_hash = @bus.instance_eval { @method_call_replies || Hash.new } assert_equal 0, some_hash.size, "there are leftover method handlers" end end ruby-dbus-0.7.2/test/thread_safety_test.rb0000755000004100000410000000147311741120355020660 0ustar www-datawww-data#!/usr/bin/env ruby # Test thread safety require "test/unit" require "dbus" def d(msg) puts "#{$$} #{msg}" if $DEBUG end class ThreadSafetyTest < Test::Unit::TestCase def test_thread_competition print "Thread competition: " jobs = [] 5.times do jobs << Thread.new do Thread.current.abort_on_exception = true # use separate connections to avoid races bus = DBus::ASessionBus.new svc = bus.service("org.ruby.service") obj = svc.object("/org/ruby/MyInstance") obj.introspect obj.default_iface = "org.ruby.SampleInterface" 10.times do |i| print "#{i} " $stdout.flush assert_equal 42, obj.the_answer[0] sleep 0.1 * rand end end end jobs.each do |thread| thread.join end end end ruby-dbus-0.7.2/test/dbus-launch-simple0000755000004100000410000000206411741120355020066 0ustar www-datawww-data#! /bin/sh # A wrapper for DBus tests # Reimplementing dbus-launch because it is in dbus-1-x11.rpm # Sets up a private session bus and call the specified program set -o errexit # This launches the bus daemon, # exports DBUS_SESSION_BUS_ADDRESS and sets DBUS_SESSION_BUS_PID my_dbus_launch () { # reimplementing dbus-launch because it is in dbus-1-x11.rpm PF=`mktemp dbus.pid.XXXXXX` || exit AF=`mktemp dbus.addr.XXXXXX` || exit RM_FILES="$RM_FILES $PF $AF" dbus-daemon --config-file=dbus-limited-session.conf --print-address=3 3>$AF --print-pid=4 4>$PF & # wait for the daemon to print the info TRIES=0 while [ ! -s $AF -o ! -s $PF ]; do sleep 0.1 TRIES=`expr $TRIES + 1` if [ $TRIES -gt 100 ]; then echo "dbus-daemon failed?"; exit 1; fi done DBUS_SESSION_BUS_PID=$(cat $PF) export DBUS_SESSION_BUS_ADDRESS=$(cat $AF) KILLS="$KILLS $DBUS_SESSION_BUS_PID" # dbus-monitor & } my_dbus_launch # Clean up at exit. trap "kill \$KILLS; rm -rf \$RM_FILES" EXIT TERM INT # run the payload; the return value is passed on "$@" ruby-dbus-0.7.2/doc/0000755000004100000410000000000011741120355014230 5ustar www-datawww-dataruby-dbus-0.7.2/doc/tutorial/0000755000004100000410000000000011741120355016073 5ustar www-datawww-dataruby-dbus-0.7.2/doc/tutorial/index.markdown0000644000004100000410000004160011741120355020747 0ustar www-datawww-data Welcome ======= This is the Ruby D-Bus tutorial. It aims to show you the features of Ruby D-Bus and as you read through the tutorial also how to use them. © Arnaud Cornet and Paul van Tilburg; this tutorial is part of free software; you can redistribute it and/or modify it under the terms of the [GNU Lesser General Public License, version 2.1](http://www.gnu.org/licenses/lgpl.html) as published by the [Free Software Foundation](http://www.fsf.org/). Introduction ============ This is a tutorial for Ruby D-Bus, a library to access D-Bus facilities of your system. What is D-Bus? -------------- D-Bus is an RPC(Remote Procedure Call) protocol. A common setup can have multiple D-Bus daemons running that route procedure calls and signals in the form of messages. Each of these daemons supports a bus. A bus that is often used by modern desktop environments, and is available per session, is called the _session bus_. Another bus that can be available, but in a system-wide manner, is called the _system bus_. It is used for example by the [Hardware Abstraction Layer](http://hal.freedesktop.org/) daemon. Note that theoretically the D-Bus RPC protocol can be used without a system or session bus. I never came across any actual use of this though. At the desktop level, D-Bus allows some components to interact. Typically if you are writing an application or a personal script that wants to interact with your web browser, your music player, or that simply wants to pop-up a desktop notification, D-Bus comes into play. At the system level, the Hardware Abstraction Layer is a privileged daemon that notifies other software of hardware activities. Typically, if you want to be notified if a CD-ROM has been loaded in, of if you want to explore hardware, the system daemon comes into play. The D-Bus RPC system is as we will see _object oriented_. Buses provide access to _services_ provided in turn by running or ready to run processes. Let me introduce some D-Bus terminology before we discuss the API of Ruby D-Bus. Client ------ A D-Bus client is a process that connects to a D-Bus. They issue method calls and register to the bus for signals and events. Service ------- A connected client can export some of its objects and let other clients call some of its methods. Such clients typically register a special name like `org.freedesktop.Notifications`, the service name. There is slightly different type of service. They are provided by processes that can be launched by a D-Bus daemon on demand. Once they are started by D-Bus they register a service name and behave like another client. Note that the buses themselves provide the `org.freedesktop.DBus` service, and provide some features through it. Object path ----------- An object path is the D-Bus way to specify an object _instance_ address. A service can provide different object instances to the outside world, so that external processes can call methods on each of them. An object path is an address of an instance in a very similar way that the path is an address of a file on a file system. For example: `/org/freedesktop/Notification` is an object path of an object provided by the `org.freedesktop.Notification` service **Beware**: service names and object paths can, but do _not_ have to be related! You'll probably encounter a lot of cases though, where the object path is a slashed version of the dotted service name. Interface --------- Classically in an object model, classes can implement interfaces. That is, some method definitions grouped in an interface. This is exactly what a D-Bus interface is as well. In D-Bus interfaces have names. These names must be specified on method calls. The `org.freedesktop.Notification` service provides an object instance called `/org/freedesktop/Notification`. This instance object implements an interface called `org.freedesktop.Notifications`. It also provides two special D-Bus specific interfaces: `org.freedesktop.DBus.Introspect` and `org.freedesktop.DBus.Properties`. Again, object paths, service names, and interface names can be related but do not have to be. Basically the `org.freedesktop.DBus.Introspect` has an `Introspect` method, that returns XML data describing the `/org/freedesktop/Notification` object interfaces. This is used heavily internally by Ruby D-Bus. Method ------ A method is, well, a method in the classical meaning. It's a function that is called in the context of an object instance. Methods have typed parameters and return typed return values. Signal ------ Signals are simplified method calls that do not have a return value. They do have typed parameters though. Message ------- Method calls, method returns, signals, errors: all are encoded as D-Bus messages sent over a bus. They are made of a packet header with source and destination address, a type (method call, method reply, signal) and the body containing the parameters (for signals and method calls) or the return values (for a method return message). Signature --------- Because D-Bus is typed and dynamic, each message comes with a signature that describes the types of the data that is contained within the message. The signature is a string with an extremely basic language that only describes a data type. You will need to have some knowledge of what a signature looks like if you are setting up a service. If you are just programming a D-Bus client, you can live without knowing about them. Client Usage ============ This chapter discusses basic client usage and has the following topics: Using the library ----------------- If you want to use the library, you have to make Ruby load it by issuing: require 'dbus' That's all! Now we can move on to really using it... Connecting to a bus ------------------- On a typical system, two buses are running, the system bus and the session bus. The system bus can be accessed by: bus = DBus::SystemBus.instance Probably you already have guessed how to access the session bus. This can be done by: bus = DBus::SessionBus.instance Performing method calls ----------------------- Let me continue this example using the session bus. Let's say that I want to access an object of some client on the session bus. This particular D-Bus client provides a service called `org.gnome.Rhythmbox`. Let me access this service: rb_service = bus.service("org.gnome.Rhythmbox") In this example I access the `org.gnome.Rhythmbox` service, which is provided by the application [Rhythmbox](http://www.gnome.org/projects/rhythmbox/). OK, I have a service handle now, and I know that it exports the object "/org/gnome/Rhythmbox/Player". I will trivially access this remote object using: rb_player = rb_service.object("/org/gnome/Rhythmbox/Player") Introspection ------------- Well, that was easy. Let's say that I know that this particular object is introspectable. In real life most of them are. The `rb_object` object we have here is just a handle of a remote object, in general they are called _proxy objects_, because they are the local handle of a remote object. It would be nice to be able to make it have methods, and that its methods send a D-Bus call to remotely execute the actual method in another process. Well, instating these methods for a _introspectable_ object is trivial: rb_player.introspect And there you go. Note that not all services or objects can be introspected, therefore you have to do this manually! Let me remind you that objects in D-Bus have interfaces and interfaces have methods. Let's now access these methods: rb_player_iface = rb_player["org.gnome.Rhythmbox.Player"] puts rb_player_iface.getPlayingUri As you can see, when you want to call a method on an instance object, you have to get the correct interface. It is a bit tedious, so we have the following shortcut that does the same thing as before: rb_player.default_iface = "org.gnome.Rhythmbox.Player" puts rb_player.getPlayingUri The `default_iface=` call specifies the default interface that should be used when non existing methods are called directly on a proxy object, and not on one of its interfaces. Note that the bus itself has a corresponding introspectable object. You can access it with `bus.proxy` method. For example, you can retrieve an array of exported service names of a bus like this: bus.proxy.ListNames[0] Properties ---------- Some D-Bus objects provide access to properties. They are accessed by treating a proxy interface as a hash: nm_iface = network_manager_object["org.freedesktop.NetworkManager"] enabled = nm_iface["WirelessEnabled"] puts "Wireless is " + (enabled ? "enabled":"disabled") puts "Toggling wireless" nm_iface["WirelessEnabled"] = ! enabled Calling a method asynchronously ------------------------------- D-Bus is _asynchronous_. This means that you do not have to wait for a reply when you send a message. When you call a remote method that takes a lot of time to process remotely, you don't want your application to hang, right? Well the asychronousness exists for this reason. What if you dont' want to wait for the return value of a method, but still you want to take some action when you receive it? There is a classical method to program this event-driven mechanism. You do some computation, perform some method call, and at the same time you setup a callback that will be triggered once you receive a reply. Then you run a main loop that is responsible to call the callbacks properly. Here is how you do it: rb_player.getPlayingUri do |resp| puts "The playing URI is #{resp}" end puts "See, I'm not waiting!" loop = DBus::Main.new loop << bus loop.run This code will print the following: See, I'm not waiting! The playing URI is file:///music/papapingoin.mp3 Waiting for a signal -------------------- Signals are calls from the remote object to your program. As a client, you set yourself up to receive a signal and handle it with a callback. Then running the main loop triggers the callback. You can register a callback handler as allows: rb_player.on_signal("elapsedChanged") do |u| puts u end More about introspection ------------------------ There are various ways to inspect a remote service. You can simply call `Introspect()` and read the XML output. However, in this tutorial I assume that you want to do it using the Ruby D-Bus API. Notice that you can introspect a service, and not only objects: rb_service = bus.service("org.gnome.Rhythmbox") rb_service.introspect p rb_service.root This dumps a tree-like structure that represents multiple object paths. In this particular case the output is: {gnome => {Rhythmbox => {Player => ..fdbe625de {},Shell => ..fdbe6852e {},PlaylistManager => ..fdbe4e340 {}}> Read this left to right: the root node is "/", it has one child node "org", "org" has one child node "gnome", and "gnome" has one child node "Rhythmbox". Rhythmbox has Tree child nodes "Player", "Shell" and "PlaylistManager". These three last child nodes have a weird digit that means it has an object instance. Such object instances are already introspected. If the prose wasn't clear, maybe the following ASCII art will help you: / org gnome Rhythmbox Shell (with object) Player (with object) PlaylistManager (with object) ### Walking the object tree You can have an object on any node, i.e. it is not limited to leaves. You can access a specific node like this: rb_player = rb_service.root["org"]["gnome"]["Rhythmbox"]["Player"] rb_player = rb_service.object("/org/gnome/Rhythmbox/Player") The difference between the two is that for the first one, `rb_service` needs to have been introspected. Also the obtained `rb_player` is already introspected whereas the second `rb_player` isn't yet. Errors ------ D-Bus calls can reply with an error instead of a return value. An error is translated to a Ruby exception. begin network_manager.sleep rescue DBus::Error => e puts e unless e.name == "org.freedesktop.NetworkManager.AlreadyAsleepOrAwake" end Creating a Service ================== This chapter deals with the opposite side of the basic client usage, namely the creation of a D-Bus service. Registering a service --------------------- Now that you know how to perform D-Bus calls, and how to wait for and handle signals, you might want to learn how to publish some object and interface to provide them to the D-Bus world. Here is how you do that. As you should already know, D-Bus clients that provide some object to be called remotely are services. Here is how to allocate a name on a bus: bus = DBus.session_bus service = bus.request_service("org.ruby.service") Now this client is know to the outside world as `org.ruby.service`. Note that this is a request and it _can_ be denied! When it is denied, an exception (`DBus::NameRequestError`) is thrown. Exporting an object ------------------- Now, let's define a class that we want to export: class Test < DBus::Object # Create an interface. dbus_interface "org.ruby.SampleInterface" do # Create a hello method in that interface. dbus_method :hello, "in name:s, in name2:s" do |name, name2| puts "hello(#{name}, #{name2})" end end end As you can see, we define a `Test` class in which we define a `org.ruby.SampleInterface` interface. In this interface, we define a method. The given code block is the method's implementation. This will be executed when remote programs performs a D-Bus call. Now the annoying part: the actual method definition. As you can guess the call dbus_method :hello, "in name:s, in name2:s" do ... creates a `hello` method that takes two parameters both of type string. The _:s_ means "of type string". Let's have a look at some other common parameter types: - *u* means unsigned integer - *i* means integer - *y* means byte - *(ui)* means a structure having a unsigned integer and a signed one. - *a* means array, so that "ai" means array of integers - *as* means array of string - *a(is)* means array of structures, each having an integer and a string. For a full description of the available D-Bus types, please refer to the [D-Bus specification](http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-signatures). Now that the class has been defined, we can instantiate an object and export it as follows: exported_obj = Test.new("/org/ruby/MyInstance") service.export(exported_obj) This piece of code above instantiates a `Test` object with a D-Bus object path. This object is reachable from the outside world after `service.export(exported_obj)` is called. We also need a loop which will read and process the calls coming over the bus: loop = DBus::Main.new loop << bus loop.run ### Using the exported object Now, let's consider another program that will access our newly created service: ruby_service = bus.service("org.ruby.service") obj = ruby_service.object("/org/ruby/MyInstance") obj.introspect obj.default_iface = "org.ruby.SampleInterface" obj.hello("giligiligiligili", "haaaaaaa") As you can see, the object we defined earlier is automatically introspectable. See also "Basic Client Usage". Emitting a signal ----------------- Let's add some example method so you can see how to return a value to the caller and let's also define another example interface that has a signal. class Test2 < DBus::Object # Create an interface dbus_interface "org.ruby.SampleInterface" do # Create a hello method in the interface: dbus_method :hello, "in name:s, in name2:s" do |name, name2| puts "hello(#{name}, #{name2})" end # Define a signal in the interface: dbus_signal :SomethingJustHappened, "toto:s, tutu:u" end dbus_interface "org.ruby.AnotherInterface" do dbus_method :ThatsALongMethodNameIThink, "in name:s, out ret:s" do |name| ["So your name is #{name}"] end end end Triggering the signal is a easy as calling a method, but then this time on a local (exported) object and not on a remote/proxy object: exported_obj.SomethingJustHappened("blah", 1) Note that the `ThatsALongMethodNameIThink` method is returning a single value to the caller. Notice that you always have to return an array. If you want to return multiple values, just have an array with multiple values. Replying with an error ---------------------- To reply to a dbus_method with a D-Bus error, raise a `DBus::Error`, as constructed by the `error` convenience function: raise DBus.error("org.example.Error.SeatOccupied"), "Seat #{seat} is occupied" If the error name is not specified, the generic `org.freedesktop.DBus.Error.Failed` is used. raise DBus.error, "Seat #{seat} is occupied" raise DBus.error ruby-dbus-0.7.2/examples/0000755000004100000410000000000011741120355015301 5ustar www-datawww-dataruby-dbus-0.7.2/examples/no-introspect/0000755000004100000410000000000011741120355020105 5ustar www-datawww-dataruby-dbus-0.7.2/examples/no-introspect/nm-test.rb0000755000004100000410000000107711741120355022031 0ustar www-datawww-data#!/usr/bin/env ruby # # Trivial network interface lister using NetworkManager. # NetworkManager does not support introspection, so the api is not that sexy. require 'dbus' bus = DBus::SystemBus.instance nm_service = bus.service("org.freedesktop.NetworkManager") nm_manager = nm_service.object("/org/freedesktop/NetworkManager") poi = DBus::ProxyObjectInterface.new(nm_manager, "org.freedesktop.NetworkManager") begin poi.define_method("getDevices", "") # NM 0.6 p poi.getDevices rescue Exception poi.define_method("GetDevices", "") # NM 0.7 p poi.GetDevices end ruby-dbus-0.7.2/examples/no-introspect/tracker-test.rb0000755000004100000410000000116111741120355023044 0ustar www-datawww-data#!/usr/bin/env ruby # # Trivial network interface lister using NetworkManager. # NetworkManager does not support introspection, so the api is not that sexy. require 'dbus' bus = DBus::SessionBus.instance tracker_service = bus.service("org.freedesktop.Tracker") tracker_manager = tracker_service.object("/org/freedesktop/tracker") poi = DBus::ProxyObjectInterface.new(tracker_manager, "org.freedesktop.Tracker.Files") poi.define_method("GetMetadataForFilesInFolder", "in live_query_id:i, in uri:s, in fields:as, out values:aas") p poi.GetMetadataForFilesInFolder(-1, ENV['HOME'] + "/Desktop", ["File:Name", "File:Size"]) ruby-dbus-0.7.2/examples/service/0000755000004100000410000000000011741120355016741 5ustar www-datawww-dataruby-dbus-0.7.2/examples/service/service_newapi.rb0000755000004100000410000000220411741120355022272 0ustar www-datawww-data#!/usr/bin/env ruby require 'dbus' require 'thread' Thread.abort_on_exception = true class Test < DBus::Object # Create an interface aggregating all upcoming dbus_method defines. dbus_interface "org.ruby.SampleInterface" do dbus_method :hello, "in name:s, in name2:s" do |name, name2| puts "hello(#{name}, #{name2})" end dbus_method :test_variant, "in stuff:v" do |variant| p variant end dbus_signal :SomethingJustHappened, "toto:s, tutu:u" end dbus_interface "org.ruby.AnotherInterface" do dbus_method :ThatsALongMethodNameIThink do puts "ThatsALongMethodNameIThink" end dbus_method :Reverse, "in instr:s, out outstr:s" do |instr| outstr = instr.split(//).reverse.join puts "got: #{instr}, replying: #{outstr}" [outstr] end end end bus = DBus::SessionBus.instance service = bus.request_service("org.ruby.service") myobj = Test.new("/org/ruby/MyInstance") service.export(myobj) Thread.new do i = 0 loop do # Signal emission myobj.SomethingJustHappened("hey", i += 1) sleep(0.5) end end puts "listening" main = DBus::Main.new main << bus main.run ruby-dbus-0.7.2/examples/service/call_service.rb0000755000004100000410000000111511741120355021722 0ustar www-datawww-data#!/usr/bin/env ruby require "dbus" session_bus = DBus::SessionBus.instance ruby_srv = session_bus.service("org.ruby.service") # Get the object from this service player = ruby_srv.object("/org/ruby/MyInstance") # Introspect it puts player.introspect player.default_iface = "org.ruby.SampleInterface" player.test_variant(["s", "coucou"]) player.on_signal("SomethingJustHappened") do |u, v| puts "SomethingJustHappened: #{u} #{v}" end player.hello("8=======D", "(_._)") p player["org.ruby.AnotherInterface"].Reverse("Hello world!") main = DBus::Main.new main << session_bus main.run ruby-dbus-0.7.2/examples/utils/0000755000004100000410000000000011741120355016441 5ustar www-datawww-dataruby-dbus-0.7.2/examples/utils/notify.rb0000755000004100000410000000057211741120355020305 0ustar www-datawww-data#!/usr/bin/env ruby require 'dbus' if ARGV.size < 2 puts "Usage:" puts "notify.rb \"title\" \"body\"" exit end d = DBus::SessionBus.instance o = d.service("org.freedesktop.Notifications").object("/org/freedesktop/Notifications") o.introspect i = o["org.freedesktop.Notifications"] i.Notify('notify.rb', 0, 'info', ARGV[0], ARGV[1], [], {}, 2000) do |ret, param| end ruby-dbus-0.7.2/examples/utils/listnames.rb0000755000004100000410000000026511741120355020773 0ustar www-datawww-data#!/usr/bin/env ruby require 'dbus' d = if ARGV.member?("--system") DBus::SystemBus.instance else DBus::SessionBus.instance end d.proxy.ListNames[0].each{ |n| puts "\t#{n}" } ruby-dbus-0.7.2/examples/simple/0000755000004100000410000000000011741120355016572 5ustar www-datawww-dataruby-dbus-0.7.2/examples/simple/call_introspect.rb0000755000004100000410000000144011741120355022306 0ustar www-datawww-data#!/usr/bin/env ruby require "dbus" session_bus = DBus::SessionBus.instance # Get the Rhythmbox service rhythmbox = session_bus.service("org.gnome.Rhythmbox") # Get the object from this service player = rhythmbox.object("/org/gnome/Rhythmbox/Player") # Introspect it player.introspect if player.has_iface? "org.gnome.Rhythmbox.Player" puts "We have Rhythmbox Player interface" end player_with_iface = player["org.gnome.Rhythmbox.Player"] p player_with_iface.getPlayingUri # Maybe support default_iface=(iface_str) on an ProxyObject, so # that this is possible? player.default_iface = "org.gnome.Rhythmbox.Player" puts "default_iface test:" p player.getPlayingUri player.on_signal("elapsedChanged") do |u| puts "elapsedChanged: #{u}" end main = DBus::Main.new main << session_bus main.run ruby-dbus-0.7.2/examples/simple/properties.rb0000755000004100000410000000100711741120355021314 0ustar www-datawww-data#! /usr/bin/env ruby require 'dbus' bus = DBus::SystemBus.instance nm_service = bus["org.freedesktop.NetworkManager"] network_manager_object = nm_service.object("/org/freedesktop/NetworkManager") network_manager_object.introspect nm_iface = network_manager_object["org.freedesktop.NetworkManager"] # read a property enabled = nm_iface["WirelessEnabled"] if enabled puts "Wireless is enabled" else puts "Wireless is disabled" end puts "Toggling wireless" # write a property nm_iface["WirelessEnabled"] = ! enabled ruby-dbus-0.7.2/examples/rhythmbox/0000755000004100000410000000000011741120355017325 5ustar www-datawww-dataruby-dbus-0.7.2/examples/rhythmbox/playpause.rb0000755000004100000410000000100611741120355021655 0ustar www-datawww-data#!/usr/bin/env ruby require 'dbus' bus = DBus::SessionBus.instance # get a rb object proxy = bus.introspect("org.gnome.Rhythmbox", "/org/gnome/Rhythmbox/Player") proxyi = proxy["org.gnome.Rhythmbox.Player"] # register for signals mr = DBus::MatchRule.new mr.type = "signal" mr.interface = "org.gnome.Rhythmbox.Player" mr.path = "/org/gnome/Rhythmbox/Player" bus.add_match(mr) do |msg, first_param| print msg.member + " " puts first_param end proxyi.playPause(true) main = DBus::Main.new main << bus main.run ruby-dbus-0.7.2/examples/gdbus/0000755000004100000410000000000011741120355016405 5ustar www-datawww-dataruby-dbus-0.7.2/examples/gdbus/gdbus.glade0000644000004100000410000001644411741120355020520 0ustar www-datawww-data True GD-Bus GTK_WINDOW_TOPLEVEL GTK_WIN_POS_NONE False 500 400 True False True False False GDK_WINDOW_TYPE_HINT_NORMAL GDK_GRAVITY_NORTH_WEST True False True True True True True True GTK_POS_TOP False False True True GTK_POLICY_ALWAYS GTK_POLICY_ALWAYS GTK_SHADOW_IN GTK_CORNER_TOP_LEFT True True True False False True False False False False True True Session False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 tab True True GTK_POLICY_ALWAYS GTK_POLICY_ALWAYS GTK_SHADOW_IN GTK_CORNER_TOP_LEFT True True True False False True False False False False True True System False False GTK_JUSTIFY_LEFT False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 tab True False True True GTK_POLICY_ALWAYS GTK_POLICY_ALWAYS GTK_SHADOW_IN GTK_CORNER_TOP_LEFT True True True False False True False False False True False ruby-dbus-0.7.2/examples/gdbus/launch.sh0000755000004100000410000000010111741120355020206 0ustar www-datawww-data#!/bin/sh set -e # for the lazy typer ruby -w -I ../../lib gdbus ruby-dbus-0.7.2/examples/gdbus/gdbus0000755000004100000410000001641411741120355017445 0ustar www-datawww-data#!/usr/bin/env ruby # # This is a quite complex example using internal lower level API. # Not a good starting point, but might be usefull if you want to do tricky # stuff. # -- Arnaud require 'dbus' require 'libglade2' $enable_system = false class MethodCallWindow def initialize(pwindow, intf, meth) @intf, @meth = intf, meth @entries = Array.new @dialog = Gtk::Dialog.new(meth.name, pwindow, Gtk::Dialog::MODAL | Gtk::Dialog::NO_SEPARATOR, [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK], [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL]) @meth.params.each do |param| shbox = Gtk::HBox.new(true, 0) label = Gtk::Label.new("#{param[0]} (#{param[1]})") input = Gtk::Entry.new @entries << input shbox.pack_start(label, true, true, 0) shbox.pack_start(input, true, true, 0) @dialog.vbox.pack_start(shbox, true, true, 0) @dialog.vbox.show_all end end def run on_ok if @dialog.run == Gtk::Dialog::RESPONSE_OK @dialog.destroy end def on_ok bus = @intf.object.bus m = DBus::Message.new(DBus::Message::METHOD_CALL) m.path = @intf.object.path m.interface = @intf.name m.destination = @intf.object.destination m.member = @meth.name m.sender = bus.unique_name @meth.params.each_with_index do |param, idx| entry = @entries[idx] data = nil case param[1] when "u", "i" data = entry.text.to_i when "s" data = entry.text when /^a/ begin data = eval(entry.text) rescue puts "Incorrect data: #{data}" end end m.add_param(param[1], data) end bus.on_return(m) do |retm| if retm.is_a?(DBus::Error) puts "Error: #{retm.inspect}" else puts "Method #{m.member} returns: #{retm.params.inspect}" end end bus.send(m.marshall) end end class DBusUI def initialize @glade = GladeXML.new("gdbus.glade") { |h| method(h) } # This block is like # black magic :) @sessiontreeview = @glade.get_widget("sessiontreeview") setup_treeview_renderer(@sessiontreeview, 'D-Bus Objects') @sessiontreeview.selection.signal_connect("changed") do |selection| on_treeview_selection_changed(selection) end @systemtreeview = @glade.get_widget("systemtreeview") setup_treeview_renderer(@systemtreeview, 'D-Bus Objects') @systemtreeview.selection.signal_connect("changed") do |selection| on_treeview_selection_changed(selection) end @methsigtreeview = @glade.get_widget("methsigtreeview") # ierk setup_methodview_renderer(@methsigtreeview) @window = @glade.get_widget("window1") @window.show_all start_buses end def beautify_method(meth) # Damn, this need to be rewritten :p s = meth.name + "(" if meth.kind_of?(DBus::Method) s += (meth.params.collect { |a| "in #{a[0]}:#{a[1]}" } + meth.rets.collect { |a| "out #{a[0]}:#{a[1]}" }).join(", ") elsif meth.kind_of?(DBus::Signal) s += (meth.params.collect { |a| "in #{a[0]}:#{a[1]}" }).join(", ") end s += ")" s end def on_treeview_selection_changed(selection) selected = selection.selected model = Gtk::ListStore.new(String, String, DBus::Method, DBus::ProxyObjectInterface) @methsigtreeview.model = model if selected if intf = selected[1] intf.methods.keys.sort.each do |mi| m = intf.methods[mi] subiter = model.append subiter[0] = beautify_method(m) subiter[1] = "M" subiter[2] = m subiter[3] = intf end intf.signals.keys.sort.each do |mi| m = intf.signals[mi] subiter = model.append subiter[0] = beautify_method(m) subiter[1] = "S" subiter[2] = m subiter[3] = intf end end end end def on_method_activated(view, path, column) name = view.model.get_iter(path)[0] puts "Clicked on: #{name.inspect}" type = view.model.get_iter(path)[1] intf = view.model.get_iter(path)[2] if type == "M" method = view.model.get_iter(path)[2] intf = view.model.get_iter(path)[3] MethodCallWindow.new(@window, intf, method).run elsif type == "S" signal = view.model.get_iter(path)[2] intf = view.model.get_iter(path)[3] mr = DBus::MatchRule.new.from_signal(intf, signal) puts "*** Registering matchrule: #{mr.to_s} ***" intf.object.bus.add_match(mr) do |sig| puts "Got #{sig.member}(#{sig.params.join(',')})" end end end def on_sessiontreeview_row_activated(view, path, column) name = view.model.get_iter(path)[0] puts "Clicked on: #{name.inspect}" intf = view.model.get_iter(path)[1] end def on_window_delete_event(window, event) Gtk.main_quit end def setup_methodview_renderer(treeview) renderer = Gtk::CellRendererText.new col_offset = treeview.insert_column(-1, "T", renderer, 'text' => 1) col_offset = treeview.insert_column(-1, "Name", renderer, 'text' => 0) column = treeview.get_column(col_offset - 1) column.clickable = true end def setup_treeview_renderer(treeview, str) renderer = Gtk::CellRendererText.new col_offset = treeview.insert_column(-1, str, renderer, 'text' => 0) column = treeview.get_column(col_offset - 1) column.clickable = true end def process_input(bus) # THIS is the bad ass loop # we should return to the glib main loop from time to time. Anyone with a # proper way to handle it ? bus.update_buffer bus.messages.each do |msg| bus.process(msg) end end def start_buses # call glibize to get dbus messages from the glib mainloop DBus::SessionBus.instance.glibize DBus::SystemBus.instance.glibize if $enable_system DBus::SessionBus.instance.proxy.ListNames do |msg, names| fill_treeview(DBus::SessionBus.instance, @sessiontreeview, names) end if $enable_system DBus::SystemBus.instance.proxy.ListNames do |msg, names| fill_treeview(DBus::SystemBus.instance, @systemtreeview, names) end end end def walk_node(model, iter, node) node.each_pair do |key, val| subiter = model.append(iter) subiter[0] = key walk_node(model, subiter, val) end unless node.object.nil? node.object.interfaces.sort.each do |ifname| subiter = model.append(iter) subiter[0] = ifname subiter[1] = node.object[ifname] end end end def introspect_services(model, bus) el = @introspect_array.shift if not el =~ /^:/ iter = model.append(nil) iter[0] = el puts "introspecting: #{el}" begin service = bus.service(el).introspect walk_node(model, iter, service.root) rescue Exception => e puts "DBus Error:" puts e.backtrace.join("\n") end end not @introspect_array.empty? end def fill_treeview(bus, treeview, array) model = Gtk::TreeStore.new(String, DBus::ProxyObjectInterface) treeview.model = model @introspect_array = array.sort Gtk::idle_add { introspect_services(model, bus) } end def main Gtk.main end end DBusUI.new.main ruby-dbus-0.7.2/Rakefile0000644000004100000410000000265411741120355015137 0ustar www-datawww-data#! /usr/bin/env ruby require 'rake' require 'rake/gempackagetask' require 'fileutils' include FileUtils require 'tmpdir' require 'rake/rdoctask' require 'rake/testtask' desc 'Default: run tests in the proper environment' task :default => :test def common_test_task(t) t.libs << "lib" t.test_files = FileList['test/*_test.rb', 'test/t[0-9]*.rb'] t.verbose = true end Rake::TestTask.new("bare:test") {|t| common_test_task t } begin require 'rcov/rcovtask' Rcov::RcovTask.new("bare:rcov") {|t| common_test_task t } rescue LoadError # no rcov, never mind end %w(test rcov).each do |tname| desc "Run bare:#{tname} in the proper environment" task tname do |t| cd "test" do system "./test_env rake bare:#{tname}" end end end load "ruby-dbus.gemspec" Rake::GemPackageTask.new(GEMSPEC) do |pkg| # no other formats needed end desc "Build a package from a clone of the local Git repo" task :package_git do |t| Dir.mktmpdir do |temp| sh "git clone . #{temp}" cd temp do sh "rake package" end cp_r "#{temp}/pkg", "." end end Rake::RDocTask.new do |rd| rd.rdoc_dir = 'doc/rdoc' rd.rdoc_files.include("README", "lib/**/*.rb") # rd.options << "--diagram" # rd.options << "--all" end desc "Render the tutorial in HTML" task :tutorial => "doc/tutorial/index.html" file "doc/tutorial/index.html" => "doc/tutorial/index.markdown" do |t| sh "markdown #{t.prerequisites[0]} > #{t.name}" end ruby-dbus-0.7.2/VERSION0000644000004100000410000000000611741120355014527 0ustar www-datawww-data0.7.2 ruby-dbus-0.7.2/metadata.yml0000644000004100000410000000441011741120355015765 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: ruby-dbus version: !ruby/object:Gem::Version hash: 7 prerelease: segments: - 0 - 7 - 2 version: 0.7.2 platform: ruby authors: - Ruby DBus Team autorequire: bindir: bin cert_chain: [] date: 2012-04-05 00:00:00 Z dependencies: [] description: email: ruby-dbus-devel@lists.luon.net executables: [] extensions: [] extra_rdoc_files: - COPYING - README - NEWS files: - Rakefile - VERSION - doc/tutorial/index.markdown - examples/gdbus/gdbus - examples/gdbus/gdbus.glade - examples/gdbus/launch.sh - examples/no-introspect/nm-test.rb - examples/no-introspect/tracker-test.rb - examples/rhythmbox/playpause.rb - examples/service/call_service.rb - examples/service/service_newapi.rb - examples/simple/call_introspect.rb - examples/simple/properties.rb - examples/utils/listnames.rb - examples/utils/notify.rb - lib/dbus.rb - lib/dbus/auth.rb - lib/dbus/bus.rb - lib/dbus/error.rb - lib/dbus/export.rb - lib/dbus/introspect.rb - lib/dbus/marshall.rb - lib/dbus/matchrule.rb - lib/dbus/message.rb - lib/dbus/type.rb - ruby-dbus.gemspec - test/async_test.rb - test/binding_test.rb - test/bus_driver_test.rb - test/bus_test.rb - test/dbus-launch-simple - test/dbus-limited-session.conf - test/property_test.rb - test/server_robustness_test.rb - test/server_test.rb - test/service_newapi.rb - test/session_bus_test_manual.rb - test/signal_test.rb - test/t1 - test/t2.rb - test/t3-ticket27.rb - test/t5-report-dbus-interface.rb - test/t6-loop.rb - test/test_env - test/test_server - test/thread_safety_test.rb - test/variant_test.rb - COPYING - README - NEWS homepage: https://trac.luon.net/ruby-dbus licenses: - LGPL v2.1 post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ">=" - !ruby/object:Gem::Version hash: 57 segments: - 1 - 8 - 7 version: 1.8.7 required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ">=" - !ruby/object:Gem::Version hash: 3 segments: - 0 version: "0" requirements: [] rubyforge_project: rubygems_version: 1.8.15 signing_key: specification_version: 3 summary: Ruby module for interaction with D-Bus test_files: [] ruby-dbus-0.7.2/COPYING0000644000004100000410000006350411741120355014526 0ustar www-datawww-data GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ruby-dbus-0.7.2/ruby-dbus.gemspec0000644000004100000410000000124011741120355016741 0ustar www-datawww-data# -*- ruby -*- require "rubygems" require "rake" GEMSPEC = Gem::Specification.new do |s| s.name = "ruby-dbus" # s.rubyforge_project = nil s.summary = "Ruby module for interaction with D-Bus" # s.description = FIXME s.version = File.read("VERSION").strip s.license = "LGPL v2.1" s.author = "Ruby DBus Team" s.email = "ruby-dbus-devel@lists.luon.net" s.homepage = "https://trac.luon.net/ruby-dbus" s.files = FileList["{doc/tutorial,examples,lib,test}/**/*", "Rakefile", "ruby-dbus.gemspec", "VERSION"].to_a.sort s.require_path = "lib" s.has_rdoc = true s.extra_rdoc_files = ["COPYING", "README", "NEWS"] s.required_ruby_version = ">= 1.8.7" end ruby-dbus-0.7.2/lib/0000755000004100000410000000000011741120355014231 5ustar www-datawww-dataruby-dbus-0.7.2/lib/dbus/0000755000004100000410000000000011741120355015166 5ustar www-datawww-dataruby-dbus-0.7.2/lib/dbus/export.rb0000644000004100000410000001141711741120355017040 0ustar www-datawww-data# dbus/introspection.rb - module containing a low-level D-Bus introspection implementation # # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. require 'thread' module DBus # Exported object type # = Exportable D-Bus object class # # Objects that are going to be exported by a D-Bus service # should inherit from this class. At the client side, use ProxyObject. class Object # The path of the object. attr_reader :path # The interfaces that the object supports. # intfs: Hash: String => Interface # @@intfs_hash simulates a class attribute by being a hash Class => intfs @@intfs_hash = {DBus::Object => nil} # The service that the object is exported by. attr_writer :service @@cur_intf = nil # Interface @@intfs_mutex = Mutex.new # Create a new object with a given _path_. # Use Service#export to export it. def initialize(path) @path = path @service = nil end def self.intfs if self.equal? DBus::Object @@intfs_hash[DBus::Object] else @@intfs_hash[self] || self.superclass.intfs end end def self.intfs= param @@intfs_hash[self] = param end def intfs self.class.intfs end def intfs= param self.class.intfs = param end # State that the object implements the given _intf_. def implements(intf) # use a setter self.intfs = (self.intfs || {}).merge({intf.name => intf}) end # Dispatch a message _msg_ to call exported methods def dispatch(msg) case msg.message_type when Message::METHOD_CALL reply = nil begin if not self.intfs[msg.interface] raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"), "Interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist" end meth = self.intfs[msg.interface].methods[msg.member.to_sym] if not meth raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"), "Method \"#{msg.member}\" on interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist" end methname = Object.make_method_name(msg.interface, msg.member) retdata = method(methname).call(*msg.params) retdata = [*retdata] reply = Message.method_return(msg) meth.rets.zip(retdata).each do |rsig, rdata| reply.add_param(rsig.type, rdata) end rescue => ex reply = ErrorMessage.from_exception(ex).reply_to(msg) end @service.bus.send(reply.marshall) end end # Select (and create) the interface that the following defined methods # belong to. def self.dbus_interface(s) @@intfs_mutex.synchronize do unless @@cur_intf = (self.intfs && self.intfs[s]) @@cur_intf = Interface.new(s) self.intfs = (self.intfs || {}).merge({s => @@cur_intf}) end yield @@cur_intf = nil end end # Dummy undefined interface class. class UndefinedInterface < ScriptError def initialize(sym) super "No interface specified for #{sym}" end end # Defines an exportable method on the object with the given name _sym_, # _prototype_ and the code in a block. def self.dbus_method(sym, protoype = "", &block) raise UndefinedInterface, sym if @@cur_intf.nil? @@cur_intf.define(Method.new(sym.to_s).from_prototype(protoype)) define_method(Object.make_method_name(@@cur_intf.name, sym.to_s), &block) end # Emits a signal from the object with the given _interface_, signal # _sig_ and arguments _args_. def emit(intf, sig, *args) @service.bus.emit(@service, self, intf, sig, *args) end # Defines a signal for the object with a given name _sym_ and _prototype_. def self.dbus_signal(sym, protoype = "") raise UndefinedInterface, sym if @@cur_intf.nil? cur_intf = @@cur_intf signal = Signal.new(sym.to_s).from_prototype(protoype) cur_intf.define(Signal.new(sym.to_s).from_prototype(protoype)) define_method(sym.to_s) do |*args| emit(cur_intf, signal, *args) end end #################################################################### private # Helper method that returns a method name generated from the interface # name _intfname_ and method name _methname_. def self.make_method_name(intfname, methname) "#{intfname}%%#{methname}" end end # class Object end # module DBus ruby-dbus-0.7.2/lib/dbus/marshall.rb0000644000004100000410000003207111741120355017321 0ustar www-datawww-data# dbus.rb - Module containing the low-level D-Bus implementation # # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. require 'socket' # = D-Bus main module # # Module containing all the D-Bus modules and classes. module DBus # Exception raised when an invalid packet is encountered. class InvalidPacketException < Exception end # = D-Bus packet unmarshaller class # # Class that handles the conversion (unmarshalling) of payload data # to Array. class PacketUnmarshaller # Index pointer that points to the byte in the data that is # currently being processed. # # Used to kown what part of the buffer has been consumed by unmarshalling. # FIXME: Maybe should be accessed with a "consumed_size" method. attr_reader :idx # Create a new unmarshaller for the given data _buffer_ and _endianness_. def initialize(buffer, endianness) @buffy, @endianness = buffer.dup, endianness if @endianness == BIG_END @uint32 = "N" @uint16 = "n" @double = "G" elsif @endianness == LIL_END @uint32 = "V" @uint16 = "v" @double = "E" else raise InvalidPacketException, "Incorrect endianness #{@endianness}" end @idx = 0 end # Unmarshall the buffer for a given _signature_ and length _len_. # Return an array of unmarshalled objects def unmarshall(signature, len = nil) if len != nil if @buffy.bytesize < @idx + len raise IncompleteBufferException end end sigtree = Type::Parser.new(signature).parse ret = Array.new sigtree.each do |elem| ret << do_parse(elem) end ret end # Align the pointer index on a byte index of _a_, where a # must be 1, 2, 4 or 8. def align(a) case a when 1 when 2, 4, 8 bits = a - 1 @idx = @idx + bits & ~bits raise IncompleteBufferException if @idx > @buffy.bytesize else raise "Unsupported alignment #{a}" end end ############################################################### # FIXME: does anyone except the object itself call the above methods? # Yes : Message marshalling code needs to align "body" to 8 byte boundary private # Retrieve the next _nbytes_ number of bytes from the buffer. def get(nbytes) raise IncompleteBufferException if @idx + nbytes > @buffy.bytesize ret = @buffy.slice(@idx, nbytes) @idx += nbytes ret end # Retrieve the series of bytes until the next NULL (\0) byte. def get_nul_terminated raise IncompleteBufferException if not @buffy[@idx..-1] =~ /^([^\0]*)\0/ str = $1 raise IncompleteBufferException if @idx + str.bytesize + 1 > @buffy.bytesize @idx += str.bytesize + 1 str end # Get the string length and string itself from the buffer. # Return the string. def get_string align(4) str_sz = get(4).unpack(@uint32)[0] ret = @buffy.slice(@idx, str_sz) raise IncompleteBufferException if @idx + str_sz + 1 > @buffy.bytesize @idx += str_sz if @buffy[@idx].ord != 0 raise InvalidPacketException, "String is not nul-terminated" end @idx += 1 # no exception, see check above ret end # Get the signature length and signature itself from the buffer. # Return the signature. def get_signature str_sz = get(1).unpack('C')[0] ret = @buffy.slice(@idx, str_sz) raise IncompleteBufferException if @idx + str_sz + 1 >= @buffy.bytesize @idx += str_sz if @buffy[@idx].ord != 0 raise InvalidPacketException, "Type is not nul-terminated" end @idx += 1 # no exception, see check above ret end # Based on the _signature_ type, retrieve a packet from the buffer # and return it. def do_parse(signature) packet = nil case signature.sigtype when Type::BYTE packet = get(1).unpack("C")[0] when Type::UINT16 align(2) packet = get(2).unpack(@uint16)[0] when Type::INT16 align(4) packet = get(4).unpack(@uint16)[0] if (packet & 0x8000) != 0 packet -= 0x10000 end when Type::UINT32 align(4) packet = get(4).unpack(@uint32)[0] when Type::INT32 align(4) packet = get(4).unpack(@uint32)[0] if (packet & 0x80000000) != 0 packet -= 0x100000000 end when Type::UINT64 align(8) packet_l = get(4).unpack(@uint32)[0] packet_h = get(4).unpack(@uint32)[0] if @endianness == LIL_END packet = packet_l + packet_h * 2**32 else packet = packet_l * 2**32 + packet_h end when Type::INT64 align(8) packet_l = get(4).unpack(@uint32)[0] packet_h = get(4).unpack(@uint32)[0] if @endianness == LIL_END packet = packet_l + packet_h * 2**32 else packet = packet_l * 2**32 + packet_h end if (packet & 0x8000000000000000) != 0 packet -= 0x10000000000000000 end when Type::DOUBLE align(8) packet = get(8).unpack(@double)[0] when Type::BOOLEAN align(4) v = get(4).unpack(@uint32)[0] raise InvalidPacketException if not [0, 1].member?(v) packet = (v == 1) when Type::ARRAY align(4) # checks please array_sz = get(4).unpack(@uint32)[0] raise InvalidPacketException if array_sz > 67108864 align(signature.child.alignment) raise IncompleteBufferException if @idx + array_sz > @buffy.bytesize packet = Array.new start_idx = @idx while @idx - start_idx < array_sz packet << do_parse(signature.child) end if signature.child.sigtype == Type::DICT_ENTRY then packet = packet.inject(Hash.new) do |hash, pair| hash[pair[0]] = pair[1] hash end end when Type::STRUCT align(8) packet = Array.new signature.members.each do |elem| packet << do_parse(elem) end when Type::VARIANT string = get_signature # error checking please sig = Type::Parser.new(string).parse[0] align(sig.alignment) packet = do_parse(sig) when Type::OBJECT_PATH packet = get_string when Type::STRING packet = get_string packet.force_encoding('UTF-8') if RUBY_VERSION >= '1.9' when Type::SIGNATURE packet = get_signature when Type::DICT_ENTRY align(8) key = do_parse(signature.members[0]) value = do_parse(signature.members[1]) packet = [key, value] else raise NotImplementedError, "sigtype: #{signature.sigtype} (#{signature.sigtype.chr})" end packet end # def do_parse end # class PacketUnmarshaller # D-Bus packet marshaller class # # Class that handles the conversion (marshalling) of Ruby objects to # (binary) payload data. class PacketMarshaller # The current or result packet. # FIXME: allow access only when marshalling is finished attr_reader :packet # Create a new marshaller, setting the current packet to the # empty packet. def initialize(offset = 0) @packet = "" @offset = offset # for correct alignment of nested marshallers end # Round _n_ up to the specified power of two, _a_ def num_align(n, a) case a when 1, 2, 4, 8 bits = a - 1 n + bits & ~bits else raise "Unsupported alignment" end end # Align the buffer with NULL (\0) bytes on a byte length of _a_. def align(a) @packet = @packet.ljust(num_align(@offset + @packet.bytesize, a) - @offset, 0.chr) end # Append the the string _str_ itself to the packet. def append_string(str) align(4) @packet += [str.bytesize].pack("L") + [str].pack("Z*") end # Append the the signature _signature_ itself to the packet. def append_signature(str) @packet += str.bytesize.chr + str + "\0" end # Append the array type _type_ to the packet and allow for appending # the child elements. def array(type) # Thanks to Peter Rullmann for this line align(4) sizeidx = @packet.bytesize @packet += "ABCD" align(type.alignment) contentidx = @packet.bytesize yield sz = @packet.bytesize - contentidx raise InvalidPacketException if sz > 67108864 @packet[sizeidx...sizeidx + 4] = [sz].pack("L") end # Align and allow for appending struct fields. def struct align(8) yield end # Append a string of bytes without type. def append_simple_string(s) @packet += s + "\0" end # Append a value _val_ to the packet based on its _type_. # # Host native endianness is used, declared in Message#marshall def append(type, val) raise TypeException, "Cannot send nil" if val.nil? type = type.chr if type.kind_of?(Fixnum) type = Type::Parser.new(type).parse[0] if type.kind_of?(String) case type.sigtype when Type::BYTE @packet += val.chr when Type::UINT32 align(4) @packet += [val].pack("L") when Type::UINT64 align(8) @packet += [val].pack("Q") when Type::INT64 align(8) @packet += [val].pack("q") when Type::INT32 align(4) @packet += [val].pack("l") when Type::UINT16 align(2) @packet += [val].pack("S") when Type::INT16 align(2) @packet += [val].pack("s") when Type::DOUBLE align(8) @packet += [val].pack("d") when Type::BOOLEAN align(4) if val @packet += [1].pack("L") else @packet += [0].pack("L") end when Type::OBJECT_PATH append_string(val) when Type::STRING append_string(val) when Type::SIGNATURE append_signature(val) when Type::VARIANT vartype = nil if val.is_a?(Array) and val.size == 2 if val[0].is_a?(DBus::Type::Type) vartype, vardata = val elsif val[0].is_a?(String) begin parsed = Type::Parser.new(val[0]).parse vartype = parsed[0] if parsed.size == 1 vardata = val[1] rescue Type::SignatureException # no assignment end end end if vartype.nil? vartype, vardata = PacketMarshaller.make_variant(val) vartype = Type::Parser.new(vartype).parse[0] end append_signature(vartype.to_s) align(vartype.alignment) sub = PacketMarshaller.new(@offset + @packet.bytesize) sub.append(vartype, vardata) @packet += sub.packet when Type::ARRAY if val.kind_of?(Hash) raise TypeException, "Expected an Array but got a Hash" if type.child.sigtype != Type::DICT_ENTRY # Damn ruby rocks here val = val.to_a end if not val.kind_of?(Array) raise TypeException, "Expected an Array but got a #{val.class}" end array(type.child) do val.each do |elem| append(type.child, elem) end end when Type::STRUCT, Type::DICT_ENTRY # TODO use duck typing, val.respond_to? raise TypeException, "Struct/DE expects an Array" if not val.kind_of?(Array) if type.sigtype == Type::DICT_ENTRY and val.size != 2 raise TypeException, "Dict entry expects a pair" end if type.members.size != val.size raise TypeException, "Struct/DE has #{val.size} elements but type info for #{type.members.size}" end struct do type.members.zip(val).each do |t, v| append(t, v) end end else raise NotImplementedError, "sigtype: #{type.sigtype} (#{type.sigtype.chr})" end end # def append # Make a [signature, value] pair for a variant def self.make_variant(value) # TODO: mix in _make_variant to String, Integer... if value == true ["b", true] elsif value == false ["b", false] elsif value.nil? ["b", nil] elsif value.is_a? Float ["d", value] elsif value.is_a? Symbol ["s", value.to_s] elsif value.is_a? Array ["av", value.map {|i| make_variant(i) }] elsif value.is_a? Hash h = {} value.each_key {|k| h[k] = make_variant(value[k]) } ["a{sv}", h] elsif value.respond_to? :to_str ["s", value.to_str] elsif value.respond_to? :to_int i = value.to_int if -2_147_483_648 <= i && i < 2_147_483_648 ["i", i] else ["x", i] end end end end # class PacketMarshaller end # module DBus ruby-dbus-0.7.2/lib/dbus/message.rb0000644000004100000410000002244511741120355017146 0ustar www-datawww-data# dbus.rb - Module containing the low-level D-Bus implementation # # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. # = D-Bus main module # # Module containing all the D-Bus modules and classes. module DBus # = InvalidDestinationName class # Thrown when you try to send a message to /org/freedesktop/DBus/Local, that # is reserved. class InvalidDestinationName < Exception end # = D-Bus message class # # Class that holds any type of message that travels over the bus. class Message # The serial number of the message. @@serial = 1 # Mutex that protects updates on the serial number. @@serial_mutex = Mutex.new # Type of a message (by specification). MESSAGE_SIGNATURE = "yyyyuua(yv)" # FIXME: following message type constants should be under Message::Type IMO # well, yeah sure # # Invalid message type. INVALID = 0 # Method call message type. METHOD_CALL = 1 # Method call return value message type. METHOD_RETURN = 2 # Error message type. ERROR = 3 # Signal message type. SIGNAL = 4 # Message flag signyfing that no reply is expected. NO_REPLY_EXPECTED = 0x1 # Message flag signifying that no automatic start is required/must be # performed. NO_AUTO_START = 0x2 # The type of the message. attr_reader :message_type # The path of the object instance the message must be sent to/is sent from. attr_accessor :path # The interface of the object that must be used/was used. attr_accessor :interface # The interface member (method/signal name) of the object that must be # used/was used. attr_accessor :member # The name of the error (in case of an error message type). attr_accessor :error_name # The destination connection of the object that must be used/was used. attr_accessor :destination # The sender of the message. attr_accessor :sender # The signature of the message contents. attr_accessor :signature # The serial number of the message this message is a reply for. attr_accessor :reply_serial # The protocol. attr_reader :protocol # The serial of the message. attr_reader :serial # The parameters of the message. attr_reader :params # Create a message with message type _mtype_ with default values and a # unique serial number. def initialize(mtype = INVALID) @message_type = mtype @flags = 0 @protocol = 1 @body_length = 0 @signature = String.new @@serial_mutex.synchronize do @serial = @@serial @@serial += 1 end @params = Array.new @destination = nil @interface = nil @error_name = nil @member = nil @path = nil @reply_serial = nil if mtype == METHOD_RETURN @flags = NO_REPLY_EXPECTED end end # Create a regular reply to a message _m_. def self.method_return(m) MethodReturnMessage.new.reply_to(m) end # Create an error reply to a message _m_. def self.error(m, error_name, description=nil) ErrorMessage.new(error_name, description).reply_to(m) end # Mark this message as a reply to a another message _m_, taking # the serial number of _m_ as reply serial and the sender of _m_ as # destination. def reply_to(m) @reply_serial = m.serial @destination = m.sender self end # Add a parameter _val_ of type _type_ to the message. def add_param(type, val) type = type.chr if type.kind_of?(Fixnum) @signature += type.to_s @params << [type, val] end # FIXME: what are these? a message element constant enumeration? # See method below, in a message, you have and array of optional parameters # that come with an index, to determine their meaning. The values are in # spec, more a definition than an enumeration. PATH = 1 INTERFACE = 2 MEMBER = 3 ERROR_NAME = 4 REPLY_SERIAL = 5 DESTINATION = 6 SENDER = 7 SIGNATURE = 8 # Marshall the message with its current set parameters and return # it in a packet form. def marshall if @path == "/org/freedesktop/DBus/Local" raise InvalidDestinationName end params = PacketMarshaller.new @params.each do |param| params.append(param[0], param[1]) end @body_length = params.packet.bytesize marshaller = PacketMarshaller.new marshaller.append(Type::BYTE, HOST_END) marshaller.append(Type::BYTE, @message_type) marshaller.append(Type::BYTE, @flags) marshaller.append(Type::BYTE, @protocol) marshaller.append(Type::UINT32, @body_length) marshaller.append(Type::UINT32, @serial) marshaller.array(Type::Parser.new("y").parse[0]) do if @path marshaller.struct do marshaller.append(Type::BYTE, PATH) marshaller.append(Type::BYTE, 1) marshaller.append_simple_string("o") marshaller.append(Type::OBJECT_PATH, @path) end end if @interface marshaller.struct do marshaller.append(Type::BYTE, INTERFACE) marshaller.append(Type::BYTE, 1) marshaller.append_simple_string("s") marshaller.append(Type::STRING, @interface) end end if @member marshaller.struct do marshaller.append(Type::BYTE, MEMBER) marshaller.append(Type::BYTE, 1) marshaller.append_simple_string("s") marshaller.append(Type::STRING, @member) end end if @error_name marshaller.struct do marshaller.append(Type::BYTE, ERROR_NAME) marshaller.append(Type::BYTE, 1) marshaller.append_simple_string("s") marshaller.append(Type::STRING, @error_name) end end if @reply_serial marshaller.struct do marshaller.append(Type::BYTE, REPLY_SERIAL) marshaller.append(Type::BYTE, 1) marshaller.append_simple_string("u") marshaller.append(Type::UINT32, @reply_serial) end end if @destination marshaller.struct do marshaller.append(Type::BYTE, DESTINATION) marshaller.append(Type::BYTE, 1) marshaller.append_simple_string("s") marshaller.append(Type::STRING, @destination) end end if @signature != "" marshaller.struct do marshaller.append(Type::BYTE, SIGNATURE) marshaller.append(Type::BYTE, 1) marshaller.append_simple_string("g") marshaller.append(Type::SIGNATURE, @signature) end end end marshaller.align(8) @params.each do |param| marshaller.append(param[0], param[1]) end marshaller.packet end # Unmarshall a packet contained in the buffer _buf_ and set the # parameters of the message object according the data found in the # buffer. # Return the detected message and the index pointer of the buffer where # the message data ended. def unmarshall_buffer(buf) buf = buf.dup if buf[0] == ?l endianness = LIL_END else endianness = BIG_END end pu = PacketUnmarshaller.new(buf, endianness) mdata = pu.unmarshall(MESSAGE_SIGNATURE) dummy, @message_type, @flags, @protocol, @body_length, @serial, headers = mdata headers.each do |struct| case struct[0] when PATH @path = struct[1] when INTERFACE @interface = struct[1] when MEMBER @member = struct[1] when ERROR_NAME @error_name = struct[1] when REPLY_SERIAL @reply_serial = struct[1] when DESTINATION @destination = struct[1] when SENDER @sender = struct[1] when SIGNATURE @signature = struct[1] end end pu.align(8) if @body_length > 0 and @signature @params = pu.unmarshall(@signature, @body_length) end [self, pu.idx] end # def unmarshall_buf # Unmarshall the data of a message found in the buffer _buf_ using # Message#unmarshall_buf. # Return the message. def unmarshall(buf) ret, size = unmarshall_buffer(buf) ret end end # class Message class MethodReturnMessage < Message def initialize super(METHOD_RETURN) end end class ErrorMessage < Message def initialize(error_name, description=nil) super(ERROR) @error_name = error_name unless description.nil? add_param(Type::STRING, description) end end def self.from_exception(ex) name = if ex.is_a? DBus::Error ex.name else "org.freedesktop.DBus.Error.Failed" # ex.class.to_s # RuntimeError is not a valid name, has no dot end description = ex.message msg = self.new(name, description) msg.add_param(DBus.type("as"), ex.backtrace) msg end end end # module DBus ruby-dbus-0.7.2/lib/dbus/type.rb0000644000004100000410000001226611741120355016503 0ustar www-datawww-data# dbus/type.rb - module containing low-level D-Bus data type information # # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. module DBus # = D-Bus type module # # This module containts the constants of the types specified in the D-Bus # protocol. module Type # The types. INVALID = 0 BYTE = ?y BOOLEAN = ?b INT16 = ?n UINT16 = ?q INT32 = ?i UINT32 = ?u INT64 = ?x UINT64 = ?t DOUBLE = ?d STRUCT = ?r ARRAY = ?a VARIANT = ?v OBJECT_PATH = ?o STRING = ?s SIGNATURE = ?g DICT_ENTRY = ?e # Mapping from type number to name. TypeName = { INVALID => "INVALID", BYTE => "BYTE", BOOLEAN => "BOOLEAN", INT16 => "INT16", UINT16 => "UINT16", INT32 => "INT32", UINT32 => "UINT32", INT64 => "INT64", UINT64 => "UINT64", DOUBLE => "DOUBLE", STRUCT => "STRUCT", ARRAY => "ARRAY", VARIANT => "VARIANT", OBJECT_PATH => "OBJECT_PATH", STRING => "STRING", SIGNATURE => "SIGNATURE", DICT_ENTRY => "DICT_ENTRY" } # Exception raised when an unknown/incorrect type is encountered. class SignatureException < Exception end # = D-Bus type conversion class # # Helper class for representing a D-Bus type. class Type # Returns the signature type number. attr_reader :sigtype # Return contained member types. attr_reader :members # Create a new type instance for type number _sigtype_. def initialize(sigtype) if not TypeName.keys.member?(sigtype) raise SignatureException, "Unknown key in signature: #{sigtype.chr}" end @sigtype = sigtype @members = Array.new end # Return the required alignment for the type. def alignment { BYTE => 1, BOOLEAN => 4, INT16 => 2, UINT16 => 2, INT32 => 4, UINT32 => 4, INT64 => 8, UINT64 => 8, STRUCT => 8, DICT_ENTRY => 8, DOUBLE => 8, ARRAY => 4, VARIANT => 1, OBJECT_PATH => 4, STRING => 4, SIGNATURE => 1, }[@sigtype] end # Return a string representation of the type according to the # D-Bus specification. def to_s case @sigtype when STRUCT "(" + @members.collect { |t| t.to_s }.join + ")" when ARRAY "a" + child.to_s when DICT_ENTRY "{" + @members.collect { |t| t.to_s }.join + "}" else if not TypeName.keys.member?(@sigtype) raise NotImplementedError end @sigtype.chr end end # Add a new member type _a_. def <<(a) if not [STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype) raise SignatureException end raise SignatureException if @sigtype == ARRAY and @members.size > 0 if @sigtype == DICT_ENTRY if @members.size == 2 raise SignatureException, "Dict entries have exactly two members" end if @members.size == 0 if [STRUCT, ARRAY, DICT_ENTRY].member?(a.sigtype) raise SignatureException, "Dict entry keys must be basic types" end end end @members << a end # Return the first contained member type. def child @members[0] end def inspect s = TypeName[@sigtype] if [STRUCT, ARRAY].member?(@sigtype) s += ": " + @members.inspect end s end end # class Type # = D-Bus type parser class # # Helper class to parse a type signature in the protocol. class Parser # Create a new parser for the given _signature_. def initialize(signature) @signature = signature @idx = 0 end # Returns the next character from the signature. def nextchar c = @signature[@idx] @idx += 1 c end # Parse one character _c_ of the signature. def parse_one(c) res = nil case c when ?a res = Type.new(ARRAY) child = parse_one(nextchar) res << child when ?( res = Type.new(STRUCT) while (c = nextchar) != nil and c != ?) res << parse_one(c) end raise SignatureException, "Parse error in #{@signature}" if c == nil when ?{ res = Type.new(DICT_ENTRY) while (c = nextchar) != nil and c != ?} res << parse_one(c) end raise SignatureException, "Parse error in #{@signature}" if c == nil else res = Type.new(c) end res end # Parse the entire signature, return a DBus::Type object. def parse @idx = 0 ret = Array.new while (c = nextchar) ret << parse_one(c) end ret end end # class Parser end # module Type # shortcuts # Parse a String to a DBus::Type::Type def type(string_type) Type::Parser.new(string_type).parse[0] end module_function :type # Make an explicit [Type, value] pair def variant(string_type, value) [type(string_type), value] end module_function :variant end # module DBus ruby-dbus-0.7.2/lib/dbus/matchrule.rb0000644000004100000410000000615311741120355017504 0ustar www-datawww-data# This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. module DBus # Exception raised when an erroneous match rule type is encountered. class MatchRuleException < Exception end # = D-Bus match rule class # # FIXME class MatchRule # The list of possible match filters. TODO argN, argNpath FILTERS = [:sender, :interface, :member, :path, :destination, :type] # The sender filter. attr_accessor :sender # The interface filter. attr_accessor :interface # The member filter. attr_accessor :member # The path filter. attr_accessor :path # The destination filter. attr_accessor :destination # The type type that is matched. attr_reader :type # Create a new match rule def initialize @sender = @interface = @member = @path = @destination = @type = nil end # Set the message types to filter to type _t_. # Possible message types are: signal, method_call, method_return, and error. def type=(t) if not ['signal', 'method_call', 'method_return', 'error'].member?(t) raise MatchRuleException, t end @type = t end # Returns a match rule string version of the object. # E.g.: "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='Foo',path='/bar/foo',destination=':452345.34',arg2='bar'" def to_s FILTERS.select do |sym| not method(sym).call.nil? end.collect do |sym| "#{sym.to_s}='#{method(sym).call}'" end.join(",") end # Parses a match rule string _s_ and sets the filters on the object. def from_s(str) str.split(",").each do |eq| if eq =~ /^(.*)='([^']*)'$/ # " name = $1 val = $2 if FILTERS.member?(name.to_sym) method(name + "=").call(val) else raise MatchRuleException, name end end end self end # Sets the match rule to filter for the given _signal_ and the # given interface _intf_. def from_signal(intf, signal) signal = signal.name unless signal.is_a?(String) self.type = "signal" self.interface = intf.name self.member = signal self.path = intf.object.path self end # Determines whether a message _msg_ matches the match rule. def match(msg) if @type if {Message::SIGNAL => "signal", Message::METHOD_CALL => "method_call", Message::METHOD_RETURN => "method_return", Message::ERROR => "error"}[msg.message_type] != @type return false end end return false if @interface and @interface != msg.interface return false if @member and @member != msg.member return false if @path and @path != msg.path # FIXME sender and destination are ignored true end end # class MatchRule end # module D-Bus ruby-dbus-0.7.2/lib/dbus/introspect.rb0000644000004100000410000004052611741120355017714 0ustar www-datawww-data# dbus/introspection.rb - module containing a low-level D-Bus introspection implementation # # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. # TODO check if it is slow, make replaceable require 'rexml/document' module DBus # Regular expressions that should match all method names. MethodSignalRE = /^[A-Za-z][A-Za-z0-9_]*$/ # Regular expressions that should match all interface names. InterfaceElementRE = /^[A-Za-z][A-Za-z0-9_]*$/ # Exception raised when an unknown signal is used. class UnknownSignal < Exception end # Exception raised when an invalid class definition is encountered. class InvalidClassDefinition < Exception end # = D-Bus interface class # # This class is the interface descriptor. In most cases, the Introspect() # method call instantiates and configures this class for us. # # It also is the local definition of interface exported by the program. # At the client side, see ProxyObjectInterface class Interface # The name of the interface. String attr_reader :name # The methods that are part of the interface. Hash: Symbol => DBus::Method attr_reader :methods # The signals that are part of the interface. Hash: Symbol => Signal attr_reader :signals # Creates a new interface with a given _name_. def initialize(name) validate_name(name) @name = name @methods, @signals = Hash.new, Hash.new end # Validates a service _name_. def validate_name(name) raise InvalidIntrospectionData if name.bytesize > 255 raise InvalidIntrospectionData if name =~ /^\./ or name =~ /\.$/ raise InvalidIntrospectionData if name =~ /\.\./ raise InvalidIntrospectionData if not name =~ /\./ name.split(".").each do |element| raise InvalidIntrospectionData if not element =~ InterfaceElementRE end end # Helper method for defining a method _m_. def define(m) if m.kind_of?(Method) @methods[m.name.to_sym] = m elsif m.kind_of?(Signal) @signals[m.name.to_sym] = m end end alias :<< :define # Defines a method with name _id_ and a given _prototype_ in the # interface. def define_method(id, prototype) m = Method.new(id) m.from_prototype(prototype) define(m) end end # class Interface # = A formal parameter has a name and a type class FormalParameter attr_reader :name attr_reader :type def initialize(name, type) @name = name @type = type end # backward compatibility, deprecated def [](index) case index when 0 then name when 1 then type else nil end end end # = D-Bus interface element class # # This is a generic class for entities that are part of the interface # such as methods and signals. class InterfaceElement # The name of the interface element. Symbol attr_reader :name # The parameters of the interface element. Array: FormalParameter attr_reader :params # Validates element _name_. def validate_name(name) if (not name =~ MethodSignalRE) or (name.bytesize > 255) raise InvalidMethodName, name end end # Creates a new element with the given _name_. def initialize(name) validate_name(name.to_s) @name = name @params = Array.new end # Adds a formal parameter with _name_ and _signature_ # (See also Message#add_param which takes signature+value) def add_fparam(name, signature) @params << FormalParameter.new(name, signature) end # Deprecated, for backward compatibility def add_param(name_signature_pair) add_fparam(*name_signature_pair) end end # class InterfaceElement # = D-Bus interface method class # # This is a class representing methods that are part of an interface. class Method < InterfaceElement # The list of return values for the method. Array: FormalParameter attr_reader :rets # Creates a new method interface element with the given _name_. def initialize(name) super(name) @rets = Array.new end # Add a return value _name_ and _signature_. def add_return(name, signature) @rets << FormalParameter.new(name, signature) end # Add parameter types by parsing the given _prototype_. def from_prototype(prototype) prototype.split(/, */).each do |arg| arg = arg.split(" ") raise InvalidClassDefinition if arg.size != 2 dir, arg = arg if arg =~ /:/ arg = arg.split(":") name, sig = arg else sig = arg end case dir when "in" add_fparam(name, sig) when "out" add_return(name, sig) end end self end # Return an XML string representation of the method interface elment. def to_xml xml = %{\n} @params.each do |param| name = param.name ? %{name="#{param.name}" } : "" xml += %{\n} end @rets.each do |param| name = param.name ? %{name="#{param.name}" } : "" xml += %{\n} end xml += %{\n} xml end end # class Method # = D-Bus interface signal class # # This is a class representing signals that are part of an interface. class Signal < InterfaceElement # Add parameter types based on the given _prototype_. def from_prototype(prototype) prototype.split(/, */).each do |arg| if arg =~ /:/ arg = arg.split(":") name, sig = arg else sig = arg end add_fparam(name, sig) end self end # Return an XML string representation of the signal interface elment. def to_xml xml = %{\n} @params.each do |param| name = param.name ? %{name="#{param.name}" } : "" xml += %{\n} end xml += %{\n} xml end end # class Signal # = D-Bus introspect XML parser class # # This class parses introspection XML of an object and constructs a tree # of Node, Interface, Method, Signal instances. class IntrospectXMLParser # Creates a new parser for XML data in string _xml_. def initialize(xml) @xml = xml end # return the names of direct subnodes def parse_subnodes subnodes = Array.new d = REXML::Document.new(@xml) d.elements.each("node/node") do |e| subnodes << e.attributes["name"] end subnodes end # return a pair: [list of Interfaces, list of direct subnode names] def parse interfaces = Array.new subnodes = Array.new t = Time.now d = REXML::Document.new(@xml) d.elements.each("node/node") do |e| subnodes << e.attributes["name"] end d.elements.each("node/interface") do |e| i = Interface.new(e.attributes["name"]) e.elements.each("method") do |me| m = Method.new(me.attributes["name"]) parse_methsig(me, m) i << m end e.elements.each("signal") do |se| s = Signal.new(se.attributes["name"]) parse_methsig(se, s) i << s end interfaces << i end d = Time.now - t if d > 2 puts "Some XML took more that two secs to parse. Optimize me!" if $DEBUG end [interfaces, subnodes] end ###################################################################### private # Parses a method signature XML element _e_ and initialises # method/signal _m_. def parse_methsig(e, m) e.elements.each("arg") do |ae| name = ae.attributes["name"] dir = ae.attributes["direction"] sig = ae.attributes["type"] if m.is_a?(DBus::Signal) m.add_fparam(name, sig) elsif m.is_a?(DBus::Method) case dir when "in" m.add_fparam(name, sig) when "out" m.add_return(name, sig) end else raise NotImplementedError, dir end end end end # class IntrospectXMLParser # = D-Bus proxy object interface class # # A class similar to the normal Interface used as a proxy for remote # object interfaces. class ProxyObjectInterface # The proxied methods contained in the interface. attr_accessor :methods # The proxied signals contained in the interface. attr_accessor :signals # The proxy object to which this interface belongs. attr_reader :object # The name of the interface. attr_reader :name # Creates a new proxy interface for the given proxy _object_ # and the given _name_. def initialize(object, name) @object, @name = object, name @methods, @signals = Hash.new, Hash.new end # Returns the string representation of the interface (the name). def to_str @name end # Returns the singleton class of the interface. def singleton_class (class << self ; self ; end) end # FIXME def check_for_eval(s) raise RuntimeError, "invalid internal data '#{s}'" if not s.to_s =~ /^[A-Za-z0-9_]*$/ end # FIXME def check_for_quoted_eval(s) raise RuntimeError, "invalid internal data '#{s}'" if not s.to_s =~ /^[^"]+$/ end # Defines a method on the interface from the Method descriptor _m_. def define_method_from_descriptor(m) check_for_eval(m.name) check_for_quoted_eval(@name) methdef = "def #{m.name}(" methdef += (0..(m.params.size - 1)).to_a.collect { |n| "arg#{n}" }.push("&reply_handler").join(", ") methdef += %{) msg = Message.new(Message::METHOD_CALL) msg.path = @object.path msg.interface = "#{@name}" msg.destination = @object.destination msg.member = "#{m.name}" msg.sender = @object.bus.unique_name } idx = 0 m.params.each do |fpar| par = fpar.type check_for_quoted_eval(par) # This is the signature validity check Type::Parser.new(par).parse methdef += %{ msg.add_param("#{par}", arg#{idx}) } idx += 1 end methdef += " @object.bus.send_sync_or_async(msg, &reply_handler) end " singleton_class.class_eval(methdef) @methods[m.name] = m end # Defines a signal from the descriptor _s_. def define_signal_from_descriptor(s) @signals[s.name] = s end # Defines a signal or method based on the descriptor _m_. def define(m) if m.kind_of?(Method) define_method_from_descriptor(m) elsif m.kind_of?(Signal) define_signal_from_descriptor(m) end end # Defines a proxied method on the interface. def define_method(methodname, prototype) m = Method.new(methodname) m.from_prototype(prototype) define(m) end # Registers a handler (code block) for a signal with _name_ arriving # over the given _bus_. If no block is given, the signal is unregistered. def on_signal(bus, name, &block) mr = DBus::MatchRule.new.from_signal(self, name) if block.nil? bus.remove_match(mr) else bus.add_match(mr) { |msg| block.call(*msg.params) } end end PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties" # Read a property. def [](propname) self.object[PROPERTY_INTERFACE].Get(self.name, propname)[0] end # Write a property. def []=(propname, value) self.object[PROPERTY_INTERFACE].Set(self.name, propname, value) end # Read all properties at once, as a hash. def all_properties self.object[PROPERTY_INTERFACE].GetAll(self.name)[0] end end # class ProxyObjectInterface # D-Bus proxy object class # # Class representing a remote object in an external application. # Typically, calling a method on an instance of a ProxyObject sends a message # over the bus so that the method is executed remotely on the correctponding # object. class ProxyObject # The names of direct subnodes of the object in the tree. attr_accessor :subnodes # Flag determining whether the object has been introspected. attr_accessor :introspected # The (remote) destination of the object. attr_reader :destination # The path to the object. attr_reader :path # The bus the object is reachable via. attr_reader :bus # The default interface of the object, as String. attr_accessor :default_iface # Creates a new proxy object living on the given _bus_ at destination _dest_ # on the given _path_. def initialize(bus, dest, path) @bus, @destination, @path = bus, dest, path @interfaces = Hash.new @subnodes = Array.new end # Returns the interfaces of the object. def interfaces @interfaces.keys end # Retrieves an interface of the proxy object (ProxyObjectInterface instance). def [](intfname) @interfaces[intfname] end # Maps the given interface name _intfname_ to the given interface _intf. def []=(intfname, intf) @interfaces[intfname] = intf end # Introspects the remote object. Allows you to find and select # interfaces on the object. def introspect # Synchronous call here. xml = @bus.introspect_data(@destination, @path) ProxyObjectFactory.introspect_into(self, xml) xml end # Returns whether the object has an interface with the given _name_. def has_iface?(name) raise "Cannot call has_iface? if not introspected" if not @introspected @interfaces.member?(name) end # Registers a handler, the code block, for a signal with the given _name_. # It uses _default_iface_ which must have been set. def on_signal(name, &block) if @default_iface and has_iface?(@default_iface) @interfaces[@default_iface].on_signal(@bus, name, &block) else # TODO improve raise NoMethodError end end #################################################### private # Handles all unkown methods, mostly to route method calls to the # default interface. def method_missing(name, *args, &reply_handler) if @default_iface and has_iface?(@default_iface) begin @interfaces[@default_iface].method(name).call(*args, &reply_handler) rescue NameError => e # interesting, foo.method("unknown") # raises NameError, not NoMethodError raise unless e.to_s =~ /undefined method `#{name}'/ # BTW e.exception("...") would preserve the class. raise NoMethodError,"undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'" end else # TODO distinguish: # - di not specified #TODO # - di is specified but not found in introspection data raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'" end end end # class ProxyObject # = D-Bus proxy object factory class # # Class that generates and sets up a proxy object based on introspection data. class ProxyObjectFactory # Creates a new proxy object factory for the given introspection XML _xml_, # _bus_, destination _dest_, and _path_. def initialize(xml, bus, dest, path) @xml, @bus, @path, @dest = xml, bus, path, dest end # Investigates the sub-nodes of the proxy object _po_ based on the # introspection XML data _xml_ and sets them up recursively. def ProxyObjectFactory.introspect_into(po, xml) intfs, po.subnodes = IntrospectXMLParser.new(xml).parse intfs.each do |i| poi = ProxyObjectInterface.new(po, i.name) i.methods.each_value { |m| poi.define(m) } i.signals.each_value { |s| poi.define(s) } po[i.name] = poi end po.introspected = true end # Generates, sets up and returns the proxy object. def build po = ProxyObject.new(@bus, @dest, @path) ProxyObjectFactory.introspect_into(po, @xml) po end end # class ProxyObjectFactory end # module DBus ruby-dbus-0.7.2/lib/dbus/error.rb0000644000004100000410000000270311741120355016646 0ustar www-datawww-data# error.rb # # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. module DBus # Represents a D-Bus Error, both on the client and server side. class Error < StandardError # error_name. +message+ is inherited from +Exception+ attr_reader :name # for received errors, the raw D-Bus message attr_reader :dbus_message # If +msg+ is a +DBus::Message+, its contents is used for initialization. # Otherwise, +msg+ is taken as a string and +name+ is used. def initialize(msg, name = "org.freedesktop.DBus.Error.Failed") if msg.is_a? DBus::Message @dbus_message = msg @name = msg.error_name super(msg.params[0]) # or nil if msg.params[1].is_a? Array set_backtrace msg.params[1] end else @name = name super(msg) end # TODO validate error name end end # class Error # raise DBus.error, "message" # raise DBus.error("org.example.Error.SeatOccupied"), "Seat #{n} is occupied" def error(name = "org.freedesktop.DBus.Error.Failed") # message will be set by Kernel.raise DBus::Error.new(nil, name) end module_function :error end # module DBus ruby-dbus-0.7.2/lib/dbus/bus.rb0000644000004100000410000006175311741120355016320 0ustar www-datawww-data# dbus.rb - Module containing the low-level D-Bus implementation # # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. require 'socket' require 'thread' require 'singleton' require 'fcntl' # = D-Bus main module # # Module containing all the D-Bus modules and classes. module DBus # This represents a remote service. It should not be instantiated directly # Use Bus::service() class Service # The service name. attr_reader :name # The bus the service is running on. attr_reader :bus # The service root (FIXME). attr_reader :root # Create a new service with a given _name_ on a given _bus_. def initialize(name, bus) @name, @bus = name, bus @root = Node.new("/") end # Determine whether the service name already exists. def exists? bus.proxy.ListNames[0].member?(@name) end # Perform an introspection on all the objects on the service # (starting recursively from the root). def introspect if block_given? raise NotImplementedError else rec_introspect(@root, "/") end self end # Retrieves an object (ProxyObject) at the given _path_. def object(path) node = get_node(path, true) if node.object.nil? node.object = ProxyObject.new(@bus, @name, path) end node.object end # Export an object _obj_ (an DBus::Object subclass instance). def export(obj) obj.service = self get_node(obj.path, true).object = obj end # Undo exporting an object _obj_. # Raises ArgumentError if it is not a DBus::Object. # Returns the object, or false if _obj_ was not exported. def unexport(obj) raise ArgumentError.new("DBus::Service#unexport() expects a DBus::Object argument") unless obj.kind_of?(DBus::Object) return false unless obj.path pathSep = obj.path.rindex("/") #last path seperator parent_path = obj.path[1..pathSep-1] node_name = obj.path[pathSep+1..-1] parent_node = get_node(parent_path, false) return false unless parent_node obj.service = nil parent_node.delete(node_name) end # Get the object node corresponding to the given _path_. if _create_ is # true, the the nodes in the path are created if they do not already exist. def get_node(path, create = false) n = @root path.sub(/^\//, "").split("/").each do |elem| if not n[elem] if not create return nil else n[elem] = Node.new(elem) end end n = n[elem] end if n.nil? puts "Warning, unknown object #{path}" if $DEBUG end n end ######### private ######### # Perform a recursive retrospection on the given current _node_ # on the given _path_. def rec_introspect(node, path) xml = bus.introspect_data(@name, path) intfs, subnodes = IntrospectXMLParser.new(xml).parse subnodes.each do |nodename| subnode = node[nodename] = Node.new(nodename) if path == "/" subpath = "/" + nodename else subpath = path + "/" + nodename end rec_introspect(subnode, subpath) end if intfs.size > 0 node.object = ProxyObjectFactory.new(xml, @bus, @name, path).build end end end # = Object path node class # # Class representing a node on an object path. class Node < Hash # The D-Bus object contained by the node. attr_accessor :object # The name of the node. attr_reader :name # Create a new node with a given _name_. def initialize(name) @name = name @object = nil end # Return an XML string representation of the node. # It is shallow, not recursing into subnodes def to_xml xml = ' ' self.each_pair do |k, v| xml += "" end if @object @object.intfs.each_pair do |k, v| xml += %{\n} v.methods.each_value { |m| xml += m.to_xml } v.signals.each_value { |m| xml += m.to_xml } xml +="\n" end end xml += '' xml end # Return inspect information of the node. def inspect # Need something here "" end # Return instance inspect information, used by Node#inspect. def sub_inspect s = "" if not @object.nil? s += "%x " % @object.object_id end s + "{" + keys.collect { |k| "#{k} => #{self[k].sub_inspect}" }.join(",") + "}" end end # class Inspect # FIXME: rename Connection to Bus? # D-Bus main connection class # # Main class that maintains a connection to a bus and can handle incoming # and outgoing messages. class Connection # The unique name (by specification) of the message. attr_reader :unique_name # The socket that is used to connect with the bus. attr_reader :socket # Create a new connection to the bus for a given connect _path_. _path_ # format is described in the D-Bus specification: # http://dbus.freedesktop.org/doc/dbus-specification.html#addresses # and is something like: # "transport1:key1=value1,key2=value2;transport2:key1=value1,key2=value2" # e.g. "unix:path=/tmp/dbus-test" or "tcp:host=localhost,port=2687" def initialize(path) @path = path @unique_name = nil @buffer = "" @method_call_replies = Hash.new @method_call_msgs = Hash.new @signal_matchrules = Hash.new @proxy = nil @object_root = Node.new("/") @is_tcp = false end # Connect to the bus and initialize the connection. def connect addresses = @path.split ";" # connect to first one that succeeds worked = addresses.find do |a| transport, keyvaluestring = a.split ":" kv_list = keyvaluestring.split "," kv_hash = Hash.new kv_list.each do |kv| key, escaped_value = kv.split "=" value = escaped_value.gsub(/%(..)/) {|m| [$1].pack "H2" } kv_hash[key] = value end case transport when "unix" connect_to_unix kv_hash when "tcp" connect_to_tcp kv_hash else # ignore, report? end end worked # returns the address that worked or nil. # how to report failure? end # Connect to a bus over tcp and initialize the connection. def connect_to_tcp(params) #check if the path is sufficient if params.key?("host") and params.key?("port") begin #initialize the tcp socket @socket = TCPSocket.new(params["host"],params["port"].to_i) @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) init_connection @is_tcp = true rescue puts "Error: Could not establish connection to: #{@path}, will now exit." exit(0) #a little harsh end else #Danger, Will Robinson: the specified "path" is not usable puts "Error: supplied path: #{@path}, unusable! sorry." end end # Connect to an abstract unix bus and initialize the connection. def connect_to_unix(params) @socket = Socket.new(Socket::Constants::PF_UNIX,Socket::Constants::SOCK_STREAM, 0) @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if ! params['abstract'].nil? if HOST_END == LIL_END sockaddr = "\1\0\0#{params['abstract']}" else sockaddr = "\0\1\0#{params['abstract']}" end elsif ! params['path'].nil? sockaddr = Socket.pack_sockaddr_un(params['path']) end @socket.connect(sockaddr) init_connection end # Send the buffer _buf_ to the bus using Connection#writel. def send(buf) @socket.write(buf) unless @socket.nil? end # Tell a bus to register itself on the glib main loop def glibize require 'glib2' # Circumvent a ruby-glib bug @channels ||= Array.new gio = GLib::IOChannel.new(@socket.fileno) @channels << gio gio.add_watch(GLib::IOChannel::IN) do |c, ch| update_buffer messages.each do |msg| process(msg) end true end end # FIXME: describe the following names, flags and constants. # See DBus spec for definition NAME_FLAG_ALLOW_REPLACEMENT = 0x1 NAME_FLAG_REPLACE_EXISTING = 0x2 NAME_FLAG_DO_NOT_QUEUE = 0x4 REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x1 REQUEST_NAME_REPLY_IN_QUEUE = 0x2 REQUEST_NAME_REPLY_EXISTS = 0x3 REQUEST_NAME_REPLY_ALREADY_OWNER = 0x4 DBUSXMLINTRO = ' ' # This apostroph is for syntax highlighting editors confused by above xml: " # Send a _message_. # If _reply_handler_ is not given, wait for the reply # and return the reply, or raise the error. # If _reply_handler_ is given, it will be called when the reply # eventually arrives, with the reply message as the 1st param # and its params following def send_sync_or_async(message, &reply_handler) ret = nil if reply_handler.nil? send_sync(message) do |rmsg| if rmsg.is_a?(Error) raise rmsg else ret = rmsg.params end end else on_return(message) do |rmsg| if rmsg.is_a?(Error) reply_handler.call(rmsg) else reply_handler.call(rmsg, * rmsg.params) end end send(message.marshall) end ret end def introspect_data(dest, path, &reply_handler) m = DBus::Message.new(DBus::Message::METHOD_CALL) m.path = path m.interface = "org.freedesktop.DBus.Introspectable" m.destination = dest m.member = "Introspect" m.sender = unique_name if reply_handler.nil? send_sync_or_async(m).first else send_sync_or_async(m) do |*args| # TODO test async introspection, is it used at all? args.shift # forget the message, pass only the text reply_handler.call(*args) nil end end end # Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method # _dest_ is the service and _path_ the object path you want to introspect # If a code block is given, the introspect call in asynchronous. If not # data is returned # # FIXME: link to ProxyObject data definition # The returned object is a ProxyObject that has methods you can call to # issue somme METHOD_CALL messages, and to setup to receive METHOD_RETURN def introspect(dest, path) if not block_given? # introspect in synchronous ! data = introspect_data(dest, path) pof = DBus::ProxyObjectFactory.new(data, self, dest, path) return pof.build else introspect_data(dest, path) do |async_data| yield(DBus::ProxyObjectFactory.new(async_data, self, dest, path).build) end end end # Exception raised when a service name is requested that is not available. class NameRequestError < Exception end # Attempt to request a service _name_. # # FIXME, NameRequestError cannot really be rescued as it will be raised # when dispatching a later call. Rework the API to better match the spec. def request_service(name) # Use RequestName, but asynchronously! # A synchronous call would not work with service activation, where # method calls to be serviced arrive before the reply for RequestName # (Ticket#29). proxy.RequestName(name, NAME_FLAG_REPLACE_EXISTING) do |rmsg, r| if rmsg.is_a?(Error) # check and report errors first raise rmsg elsif r != REQUEST_NAME_REPLY_PRIMARY_OWNER raise NameRequestError end end @service = Service.new(name, self) @service end # Set up a ProxyObject for the bus itself, since the bus is introspectable. # Returns the object. def proxy if @proxy == nil path = "/org/freedesktop/DBus" dest = "org.freedesktop.DBus" pof = DBus::ProxyObjectFactory.new(DBUSXMLINTRO, self, dest, path) @proxy = pof.build["org.freedesktop.DBus"] end @proxy end # Fill (append) the buffer from data that might be available on the # socket. def update_buffer @buffer += @socket.read_nonblock(MSG_BUF_SIZE) rescue EOFError raise # the caller expects it rescue Exception => e puts "Oops:", e raise if @is_tcp # why? puts "WARNING: read_nonblock failed, falling back to .recv" @buffer += @socket.recv(MSG_BUF_SIZE) end # Get one message from the bus and remove it from the buffer. # Return the message. def pop_message return nil if @buffer.empty? ret = nil begin ret, size = Message.new.unmarshall_buffer(@buffer) @buffer.slice!(0, size) rescue IncompleteBufferException => e # fall through, let ret be null end ret end # Retrieve all the messages that are currently in the buffer. def messages ret = Array.new while msg = pop_message ret << msg end ret end # The buffer size for messages. MSG_BUF_SIZE = 4096 # Update the buffer and retrieve all messages using Connection#messages. # Return the messages. def poll_messages ret = nil r, d, d = IO.select([@socket], nil, nil, 0) if r and r.size > 0 update_buffer end messages end # Wait for a message to arrive. Return it once it is available. def wait_for_message if @socket.nil? puts "ERROR: Can't wait for messages, @socket is nil." return end ret = pop_message while ret == nil r, d, d = IO.select([@socket]) if r and r[0] == @socket update_buffer ret = pop_message end end ret end # Send a message _m_ on to the bus. This is done synchronously, thus # the call will block until a reply message arrives. def send_sync(m, &retc) # :yields: reply/return message return if m.nil? #check if somethings wrong send(m.marshall) @method_call_msgs[m.serial] = m @method_call_replies[m.serial] = retc retm = wait_for_message return if retm.nil? #check if somethings wrong process(retm) while @method_call_replies.has_key? m.serial retm = wait_for_message process(retm) end end # Specify a code block that has to be executed when a reply for # message _m_ is received. def on_return(m, &retc) # Have a better exception here if m.message_type != Message::METHOD_CALL raise "on_return should only get method_calls" end @method_call_msgs[m.serial] = m @method_call_replies[m.serial] = retc end # Asks bus to send us messages matching mr, and execute slot when # received def add_match(mr, &slot) # check this is a signal. mrs = mr.to_s puts "#{@signal_matchrules.size} rules, adding #{mrs.inspect}" if $DEBUG # don't ask for the same match if we override it unless @signal_matchrules.key?(mrs) puts "Asked for a new match" if $DEBUG proxy.AddMatch(mrs) end @signal_matchrules[mrs] = slot end def remove_match(mr) mrs = mr.to_s unless @signal_matchrules.delete(mrs).nil? # don't remove nonexisting matches. # FIXME if we do try, the Error.MatchRuleNotFound is *not* raised # and instead is reported as "no return code for nil" proxy.RemoveMatch(mrs) end end # Process a message _m_ based on its type. def process(m) return if m.nil? #check if somethings wrong case m.message_type when Message::ERROR, Message::METHOD_RETURN raise InvalidPacketException if m.reply_serial == nil mcs = @method_call_replies[m.reply_serial] if not mcs puts "DEBUG: no return code for mcs: #{mcs.inspect} m: #{m.inspect}" if $DEBUG else if m.message_type == Message::ERROR mcs.call(Error.new(m)) else mcs.call(m) end @method_call_replies.delete(m.reply_serial) @method_call_msgs.delete(m.reply_serial) end when DBus::Message::METHOD_CALL if m.path == "/org/freedesktop/DBus" puts "DEBUG: Got method call on /org/freedesktop/DBus" if $DEBUG end node = @service.get_node(m.path) if not node reply = Message.error(m, "org.freedesktop.DBus.Error.UnknownObject", "Object #{m.path} doesn't exist") send(reply.marshall) # handle introspectable as an exception: elsif m.interface == "org.freedesktop.DBus.Introspectable" and m.member == "Introspect" reply = Message.new(Message::METHOD_RETURN).reply_to(m) reply.sender = @unique_name reply.add_param(Type::STRING, node.to_xml) send(reply.marshall) else obj = node.object return if obj.nil? # FIXME, sends no reply obj.dispatch(m) if obj end when DBus::Message::SIGNAL # the signal can match multiple different rules @signal_matchrules.each do |mrs, slot| if DBus::MatchRule.new.from_s(mrs).match(m) slot.call(m) end end else puts "DEBUG: Unknown message type: #{m.message_type}" if $DEBUG end end # Retrieves the Service with the given _name_. def service(name) # The service might not exist at this time so we cannot really check # anything Service.new(name, self) end alias :[] :service # Emit a signal event for the given _service_, object _obj_, interface # _intf_ and signal _sig_ with arguments _args_. def emit(service, obj, intf, sig, *args) m = Message.new(DBus::Message::SIGNAL) m.path = obj.path m.interface = intf.name m.member = sig.name m.sender = service.name i = 0 sig.params.each do |par| m.add_param(par.type, args[i]) i += 1 end send(m.marshall) end ########################################################################### private # Send a hello messages to the bus to let it know we are here. def send_hello m = Message.new(DBus::Message::METHOD_CALL) m.path = "/org/freedesktop/DBus" m.destination = "org.freedesktop.DBus" m.interface = "org.freedesktop.DBus" m.member = "Hello" send_sync(m) do |rmsg| @unique_name = rmsg.destination puts "Got hello reply. Our unique_name is #{@unique_name}" if $DEBUG end @service = Service.new(@unique_name, self) end # Initialize the connection to the bus. def init_connection @client = Client.new(@socket) @client.authenticate end end # class Connection # = D-Bus session bus class # # The session bus is a session specific bus (mostly for desktop use). # # Use SessionBus, the non-singleton ASessionBus is # for the test suite. class ASessionBus < Connection # Get the the default session bus. def initialize super(ENV["DBUS_SESSION_BUS_ADDRESS"] || address_from_file) connect send_hello end def address_from_file f = File.new("/var/lib/dbus/machine-id") machine_id = f.readline.chomp f.close display = ENV["DISPLAY"].gsub(/.*:([0-9]*).*/, '\1') File.open(ENV["HOME"] + "/.dbus/session-bus/#{machine_id}-#{display}").each do |line| if line =~ /^DBUS_SESSION_BUS_ADDRESS=(.*)/ return $1 end end end end # See ASessionBus class SessionBus < ASessionBus include Singleton end # = D-Bus system bus class # # The system bus is a system-wide bus mostly used for global or # system usages. # # Use SystemBus, the non-singleton ASystemBus is # for the test suite. class ASystemBus < Connection # Get the default system bus. def initialize super(SystemSocketName) connect send_hello end end # = D-Bus remote (TCP) bus class # # This class may be used when connecting to remote (listening on a TCP socket) # busses. You can also use it to connect to other non-standard path busses. # # The specified socket_name should look like this: # (for TCP) tcp:host=127.0.0.1,port=2687 # (for Unix-socket) unix:path=/tmp/my_funky_bus_socket # # you'll need to take care about authentification then, more info here: # http://github.com/pangdudu/ruby-dbus/blob/master/README.rdoc class RemoteBus < Connection # Get the remote bus. def initialize socket_name super(socket_name) connect send_hello end end # See ASystemBus class SystemBus < ASystemBus include Singleton end # Shortcut for the SystemBus instance def DBus.system_bus SystemBus.instance end # Shortcut for the SessionBus instance def DBus.session_bus SessionBus.instance end # = Main event loop class. # # Class that takes care of handling message and signal events # asynchronously. *Note:* This is a native implement and therefore does # not integrate with a graphical widget set main loop. class Main # Create a new main event loop. def initialize @buses = Hash.new @quitting = false end # Add a _bus_ to the list of buses to watch for events. def <<(bus) @buses[bus.socket] = bus end # Quit a running main loop, to be used eg. from a signal handler def quit @quitting = true end # Run the main loop. This is a blocking call! def run # before blocking, empty the buffers # https://bugzilla.novell.com/show_bug.cgi?id=537401 @buses.each_value do |b| while m = b.pop_message b.process(m) end end while not @quitting and not @buses.empty? ready = IO.select(@buses.keys, [], [], 5) # timeout 5 seconds next unless ready # timeout exceeds so continue unless quitting ready.first.each do |socket| b = @buses[socket] begin b.update_buffer rescue EOFError, SystemCallError @buses.delete socket # this bus died next end while m = b.pop_message b.process(m) end end end end end # class Main end # module DBus ruby-dbus-0.7.2/lib/dbus/auth.rb0000644000004100000410000002001411741120355016451 0ustar www-datawww-data# This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. $debug = $DEBUG #it's all over the state machine require 'rbconfig' module DBus # Exception raised when authentication fails somehow. class AuthenticationFailed < Exception end # = General class for authentication. class Authenticator # Returns the name of the authenticator. def name self.class.to_s.upcase.sub(/.*::/, "") end end # = External authentication class # # Class for 'external' type authentication. class External < Authenticator # Performs the authentication. def authenticate # Take the user id (eg integer 1000) make a string out of it "1000", take # each character and determin hex value "1" => 0x31, "0" => 0x30. You # obtain for "1000" => 31303030 This is what the server is expecting. # Why? I dunno. How did I come to that conclusion? by looking at rbus # code. I have no idea how he found that out. return Process.uid.to_s.split(//).collect { |a| "%x" % a[0].ord }.join end end # = Authentication class using SHA1 crypto algorithm # # Class for 'CookieSHA1' type authentication. # Implements the AUTH DBUS_COOKIE_SHA1 mechanism. class DBusCookieSHA1 < Authenticator #the autenticate method (called in stage one of authentification) def authenticate require 'etc' #number of retries we have for auth @retries = 1 return "#{hex_encode(Etc.getlogin)}" #server expects it to be binary end #returns the modules name def name return 'DBUS_COOKIE_SHA1' end #handles the interesting crypto stuff, check the rbus-project for more info: http://rbus.rubyforge.org/ def data(hexdata) require 'digest/sha1' data = hex_decode(hexdata) # name of cookie file, id of cookie in file, servers random challenge context, id, s_challenge = data.split(' ') # Random client challenge c_challenge = Array.new(s_challenge.bytesize/2).map{|obj|obj=rand(255).to_s}.join # Search cookie file for id path = File.join(ENV['HOME'], '.dbus-keyrings', context) puts "DEBUG: path: #{path.inspect}" if $debug File.foreach(path) do |line| if line.index(id) == 0 # Right line of file, read cookie cookie = line.split(' ')[2].chomp puts "DEBUG: cookie: #{cookie.inspect}" if $debug # Concatenate and encrypt to_encrypt = [s_challenge, c_challenge, cookie].join(':') sha = Digest::SHA1.hexdigest(to_encrypt) #the almighty tcp server wants everything hex encoded hex_response = hex_encode("#{c_challenge} #{sha}") # Return response response = [:AuthOk, hex_response] return response end end #a little rescue magic unless @retries <= 0 puts "ERROR: Could not auth, will now exit." puts "ERROR: Unable to locate cookie, retry in 1 second." @retries -= 1 sleep 1 data(hexdata) end end # encode plain to hex def hex_encode(plain) return nil if plain.nil? plain.to_s.unpack('H*')[0] end # decode hex to plain def hex_decode(encoded) encoded.scan(/[[:xdigit:]]{2}/).map{|h|h.hex.chr}.join end end #DBusCookieSHA1 class ends here # Note: this following stuff is tested with External authenticator only! # = Authentication client class. # # Class tha performs the actional authentication. class Client # Create a new authentication client. def initialize(socket) @socket = socket @state = nil @auth_list = [External,DBusCookieSHA1] end # Start the authentication process. def authenticate if (RbConfig::CONFIG["target_os"] =~ /bsd/) @socket.sendmsg(0.chr, 0, nil, [:SOCKET, :SCM_CREDS, ""]) else @socket.write(0.chr) end next_authenticator @state = :Starting while @state != :Authenticated r = next_state return r if not r end true end ########## private ########## # Send an authentication method _meth_ with arguments _args_ to the # server. def send(meth, *args) o = ([meth] + args).join(" ") @socket.write(o + "\r\n") end # Try authentication using the next authenticator. def next_authenticator begin raise AuthenticationFailed if @auth_list.size == 0 @authenticator = @auth_list.shift.new auth_msg = ["AUTH", @authenticator.name, @authenticator.authenticate] puts "DEBUG: auth_msg: #{auth_msg.inspect}" if $debug send(auth_msg) rescue AuthenticationFailed @socket.close raise end end # Read data (a buffer) from the bus until CR LF is encountered. # Return the buffer without the CR LF characters. def next_msg data,crlf = "","\r\n" left = 1024 #1024 byte, no idea if it's ever getting bigger while left > 0 buf = @socket.read( left > 1 ? 1 : left ) break if buf.nil? left -= buf.bytesize data += buf break if data.include? crlf #crlf means line finished, the TCP socket keeps on listening, so we break end readline = data.chomp.split(" ") puts "DEBUG: readline: #{readline.inspect}" if $debug return readline end =begin # Read data (a buffer) from the bus until CR LF is encountered. # Return the buffer without the CR LF characters. def next_msg @socket.readline.chomp.split(" ") end =end # Try to reach the next state based on the current state. def next_state msg = next_msg if @state == :Starting puts "DEBUG: :Starting msg: #{msg[0].inspect}" if $debug case msg[0] when "OK" @state = :WaitingForOk when "CONTINUE" @state = :WaitingForData when "REJECTED" #needed by tcp, unix-path/abstract doesn't get here @state = :WaitingForData end end puts "DEBUG: state: #{@state}" if $debug case @state when :WaitingForData puts "DEBUG: :WaitingForData msg: #{msg[0].inspect}" if $debug case msg[0] when "DATA" chall = msg[1] resp, chall = @authenticator.data(chall) puts "DEBUG: :WaitingForData/DATA resp: #{resp.inspect}" if $debug case resp when :AuthContinue send("DATA", chall) @state = :WaitingForData when :AuthOk send("DATA", chall) @state = :WaitingForOk when :AuthError send("ERROR") @state = :WaitingForData end when "REJECTED" next_authenticator @state = :WaitingForData when "ERROR" send("CANCEL") @state = :WaitingForReject when "OK" send("BEGIN") @state = :Authenticated else send("ERROR") @state = :WaitingForData end when :WaitingForOk puts "DEBUG: :WaitingForOk msg: #{msg[0].inspect}" if $debug case msg[0] when "OK" send("BEGIN") @state = :Authenticated when "REJECT" next_authenticator @state = :WaitingForData when "DATA", "ERROR" send("CANCEL") @state = :WaitingForReject else send("ERROR") @state = :WaitingForOk end when :WaitingForReject puts "DEBUG: :WaitingForReject msg: #{msg[0].inspect}" if $debug case msg[0] when "REJECT" next_authenticator @state = :WaitingForOk else @socket.close return false end end return true end # def next_state end # class Client end # module D-Bus ruby-dbus-0.7.2/lib/dbus.rb0000644000004100000410000000420611741120355015515 0ustar www-datawww-data# dbus.rb - Module containing the low-level D-Bus implementation # # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. require 'dbus/type' require 'dbus/introspect' require 'dbus/error' require 'dbus/export' require 'dbus/bus.rb' require 'dbus/marshall' require 'dbus/message' require 'dbus/matchrule' require 'dbus/auth' require 'socket' require 'thread' unless 0.respond_to?(:ord) # Backward compatibility with Ruby 1.8.6, see http://www.pubbs.net/ruby/200907/65871/ class Integer def ord; self; end end end # = D-Bus main module # # Module containing all the D-Bus modules and classes. module DBus # Default socket name for the system bus. SystemSocketName = "unix:path=/var/run/dbus/system_bus_socket" # Byte signifying big endianness. BIG_END = ?B # Byte signifying little endianness. LIL_END = ?l # Byte signifying the host's endianness. HOST_END = if [0x01020304].pack("L").unpack("V")[0] == 0x01020304 LIL_END else BIG_END end # General exceptions. # Exception raised when an invalid packet is encountered. class InvalidPacketException < Exception end # Exception raised when there is a problem with a type (may be unknown or # mismatch). class TypeException < Exception end # Exception raised when an unmarshalled buffer is truncated and # incomplete. class IncompleteBufferException < Exception end # Exception raised when a method has not been implemented (yet). class MethodNotImplemented < Exception end # Exception raised when a method is invoked with invalid # parameters (wrong number or type). class InvalidParameters < Exception end # Exception raised when an invalid method name is used. class InvalidMethodName < Exception end # Exception raised when invalid introspection data is parsed/used. class InvalidIntrospectionData < Exception end end # module DBus ruby-dbus-0.7.2/NEWS0000644000004100000410000001315111741120355014163 0ustar www-datawww-data= Ruby D-Bus NEWS Note about bug numbers: Ticket#1 - https://trac.luon.net/ruby-dbus/ticket/1 Issue#1 - http://github.com/mvidner/ruby-dbus/issues#issue/1 bnc#1 - https://bugzilla.novell.com/show_bug.cgi?id=1 == Ruby D-Bus 0.7.2 - 2012-04-05 A brown-paper-bag release. Bug fixes: * Fixed "undefined local variable or method `continue'" in DBus::Main#run when a service becomes idle (by Ravil Bayramgalin) == Ruby D-Bus 0.7.1 - 2012-04-04 Bug fixes: * Fixed calling asynchronous methods on the default interface (Issue#13, by Eugene Korbut). * Fixed Main#quit to really quit the loop (by Josef Reidinger) * Unbundled files from Active Support (by Bohuslav Kabrda) == Ruby D-Bus 0.7.0 - 2011-07-26 Features: * Added ASystemBus and ASessionBus, non-singletons useful in tests and threads. Bug fixes: * Fixed handling of multibyte strings (Issue#8, by Takayuki YAMAGUCHI). * Allow reopening of a dbus_interface declaration (Issue#9, by T. YAMAGUCHI). * Fixed ruby-1.9.2 compatibility again (Issue#12). * Fixed authentication on BSD (Issue#11, by Jonathan Walker) * Fixed exiting a nested event loop for synchronous calls (reported by Timo Warns). * Fixed introspection calls leaking reply handlers. * "rake test" now works, doing what was called "rake env:test" == Ruby D-Bus 0.6.0 - 2010-12-11 Features: * Clients can access properties conveniently (Ticket#28). Bug fixes: * Service won't crash whan handling an unknown method or interface (Ticket#31). * Don't send an invalid error name when it originates from a NameError. == Ruby D-Bus 0.5.0 - 2010-11-07 Features: * Better binding of Ruby Exceptions to D-Bus Errors. * Converted the package to a Gem (Issue#6). * Converted the tutorial from Webgen to Markdown. Bug fixes: * Don't pass file descriptors to subprocesses. * Fixed InterfaceElement::validate_name (Ticket#38, by Herwin Weststrate). * Fixed a typo in InvalidDestinationName description (Ticket#40). == Ruby D-Bus 0.4.0 - 2010-08-20 Features: * TCP transport (by pangdudu) * Enabled test code coverage report (rcov) Bug fixes: * Classes should not share all interfaces (Ticket#36/Issue#5) * Ruby 1.9 compatibility (Ticket#37, by Myra Nelson) == Ruby D-Bus 0.3.1 - 2010-07-22 Bug fixes: * Many on_signal could cause DBus.Error.LimitsExceeded bnc#617350). Don't add a match rule that already exists, enable removing match rules. Now only one handler for a rule is called (but it is possible for one signal to match more rules). This reverts the half-fix done to fix Issue#3 * Re-added InterfaceElement#add_param for compatibility. * Handle more ways which tell us that a bus connection has died. == Ruby D-Bus 0.3.0 - 2010-03-28 Bug fixes: * Fixed "undefined method `get_node' for nil:NilClass" on Ubuntu Karmic (Ticket#34). * Get the session bus address even if unset in ENV (Issue#4). * Improved exceptions a bit: UndefinedInterface, InvalidMethodName, NoMethodError, no RuntimeException These are by Klaus Kaempf: * Make the signal dispatcher call all handlers (Issue#3). * Run on Ruby < 1.8.7 (Issue#2). * Avoid needless DBus::IncompleteBufferException (Ticket#33). * Don't ignore DBus Errors in request_service, raise them (Ticket#32). Features: * Automatic signature inference for variants. * Introduced FormalParameter where a plain pair had been used. == Ruby D-Bus 0.2.12 - 2010-01-24 Bug fixes: * Fixed a long-standing bug where a service activated by the bus would fail with "undefined method `get_node' for nil:NilClass" (Tickets#25 and #29). == Ruby D-Bus 0.2.11 - 2009-11-12 Features: * Added DBus::Service#unexport (da1l6). Bug fixes: * Return org.freedesktop.DBus.Error.UnknownObject instead of crashing (Ticket#31). * Rescue exceptions in dbus_methods and reply with DBus errors instead of crashing (da1l6). * Better exception messages when sending nil, or mismatched structs. * Call mktemp without --tmpdir, to build on older distros. == Ruby D-Bus 0.2.10 - 2009-09-10 Bug fixes: * DBus::Service.exists? fixed (Murat Demirten). * Ruby 1.9 fixes (Jedediah Smith). * Fixed an endless sleep in DBus::Main.run (bnc#537401). * Added details to PacketMarshaller exceptions (bnc#538050). (bnc#FOO refers to https://bugzilla.novell.com/show_bug.cgi?id=FOO ) == Ruby D-Bus "I'm not dead" 0.2.9 - 2009-08-26 Thank you to Paul and Arnaud for starting the project. I, Martin Vidner, am continuing with it on GitHub. * Fixed passing an array through a variant (no ticket). * Fixed marshalling "av" (Ticket #30). * Fixed variant alignment (Ticket #27). * Added DBus::Main.quit. * Mention the DBus interface in a NameError for an unknown method. * Fixed ruby-1.9 "warning: default `to_a' will be obsolete". * Added Rakefile and gemspec. == Ruby D-Bus "Thanks for all the fish" 0.2.1 - 2007-12-29 More bugfixes, mostly supplied by users supplying us with patches. Thanks! * Support for new types added: - dict (courtesy of Drake Wilson); - double (courtesy of Patrick Sissons); - variant. * Improved exception raise support (courtesy of Sjoerd Simons, Patrick Sissons). * Some polish (removed debug output, solved unnecessary warnings). * Documentation updates, example fixes and updates. == Ruby D-Bus "Almost live from DebConf 7" 0.2.0 - 2007-06-02 Again a bugfix release, also meant to be the public release for exploratory purposes. New in 0.2.0: * Complete tutorial revamp. * Relicensed to the LGPL. == Ruby D-Bus "Release Often" 0.1.1 - 2007-04-23 Bugfix release. Fixes hardcoded string for requesting bus names, found by Rudi Cilibrasi. == Ruby D-Bus "Happy Birthday Paul" 0.1.0 - 2007-04-17 First release. Supports most of D-Bus' features.