ruby-dbus-0.13.0/0000755000004100000410000000000013004252346013536 5ustar www-datawww-dataruby-dbus-0.13.0/Rakefile0000644000004100000410000000332713004252346015210 0ustar www-datawww-data#! /usr/bin/env ruby require "rake" require "fileutils" include FileUtils require "tmpdir" require "rspec/core/rake_task" begin require "rubocop/rake_task" rescue LoadError nil end require "packaging" Packaging.configuration do |conf| conf.obs_project = "devel:languages:ruby:extensions" conf.obs_target = "openSUSE_Tumbleweed" conf.package_name = "rubygem-ruby-dbus" conf.obs_sr_project = "openSUSE:Factory" conf.skip_license_check << %r{^[^/]*$} conf.skip_license_check << %r{^(doc|examples|spec)/.*} # "Ruby on Rails is released under the MIT License." # but the files are missing copyright headers conf.skip_license_check << %r{^lib/dbus/core_ext/} end desc "Default: run specs in the proper environment" task default: [:spec, :rubocop] task test: :spec RSpec::Core::RakeTask.new("bare:spec") %w(spec).each do |tname| desc "Run bare:#{tname} in the proper environment" task tname do |_t| cd "spec/tools" do sh "./test_env rake bare:#{tname}" end end end if ENV["TRAVIS"] require "coveralls/rake/task" Coveralls::RakeTask.new task default: "coveralls:push" end # remove tarball implementation and create gem for this gemfile Rake::Task[:tarball].clear desc "Build a package from a clone of the local Git repo" task :tarball do |_t| Dir.mktmpdir do |temp| sh "git clone . #{temp}" cd temp do sh "gem build ruby-dbus.gemspec" end sh "rm -f package/*.gem" cp Dir.glob("#{temp}/*.gem"), "package" end end namespace :doc do desc "Extract code examples from doc/Reference.md to examples/doc" task :examples do cd "examples/doc" do sh "./_extract_examples ../../doc/Reference.md" end end end RuboCop::RakeTask.new if Object.const_defined? :RuboCop ruby-dbus-0.13.0/NEWS.md0000644000004100000410000002460013004252346014636 0ustar www-datawww-data# Ruby D-Bus NEWS ## Unreleased ## Ruby D-Bus 0.13.0 - 2016-09-21 Bug fixes: * It is no longer required to explicitly call ProxyObject#introspect, it will be done automatically once ([#28][]). Requirements: * Introduced RuboCop to keep a consistent coding style. * Replaced Gemfile.ci with a regular Gemfile. [#28]: http://github.com/mvidner/ruby-dbus/issue/28 ## Ruby D-Bus 0.12.0 - 2016-09-12 API: * Added proxy objects whose methods return single values instead of arrays (use Service#[] instead of Service#object; [#30][]). Requirements: * Require ruby 2.0.0, stopped supporting 1.9.3. [#30]: http://github.com/mvidner/ruby-dbus/issue/30 ## Ruby D-Bus 0.11.2 - 2016-09-11 Bug fixes: * Fixed reading a quoted session bus address, as written by dbus-1.10.10 ([#62][], Yasuhiro Asaka) [#62]: https://github.com/mvidner/ruby-dbus/pull/62 ## Ruby D-Bus 0.11.1 - 2016-05-12 Bug fixes: * Fix default path finding on FreeBSD (Greg) * Service#unexport fixed to really return the unexported object Requirements: * made tests compatible with RSpec 3 ## Ruby D-Bus 0.11.0 - 2014-02-17 API: * Connection: split off MessageQueue, marked other methods as private. Requirements: * converted tests to RSpec, rather mechanically for now ## Ruby D-Bus 0.10.0 - 2014-01-10 Bug fixes: * fixed "Interfaces added with singleton_class.instance_eval aren't exported" ([#22][], by miaoufkirsh) Requirements: * Require ruby 1.9.3, stopped supporting 1.8.7. [#22]: https://github.com/mvidner/ruby-dbus/issue/22 ## Ruby D-Bus 0.9.3 - 2014-01-02 Bug fixes: * re-added COPYING, NEWS, README.md to the gem ([#47][], by Cédric Boutillier) Packaging: * use packaging_rake_tasks [#47]: https://github.com/mvidner/ruby-dbus/issue/47 ## Ruby D-Bus 0.9.2 - 2013-05-08 Features: * Ruby strings can be passed where byte arrays ("ay") are expected ([#40][], by Jesper B. Rosenkilde) Bug fixes: * Fixed accessing ModemManager properties ([#41][], reported by Ernest Bursa). MM introspection produces two elements for a single interface; merge them. [#40]: https://github.com/mvidner/ruby-dbus/issue/40 [#41]: https://github.com/mvidner/ruby-dbus/issue/41 ## Ruby D-Bus 0.9.1 - 2013-04-23 Bug fixes: * Prefer /etc/machine-id to /var/lib/dbus/machine-id when DBUS_SESSION_BUS_ADDRESS is unset ([#39][], by WU Jun). [#39]: https://github.com/mvidner/ruby-dbus/issue/39 ## Ruby D-Bus 0.9.0 - 2012-11-06 Features: * When calling methods, the interface can be left unspecified if unambiguous (Damiano Stoffie) * YARD documentation, Reference.md Bug fixes: * Introspection attribute "direction" can be omitted as allowed by the specification (Noah Meyerhans). * ProxyObjectInterface#on_signal no longer needs the "bus" parameter ([#31][], by Damiano Stoffie) [#31]: https://github.com/mvidner/ruby-dbus/issue/31 ## Ruby D-Bus 0.8.0 - 2012-09-20 Features: * Add Anonymous authentication ([#27][], by Walter Brebels). * Use Nokogiri for XML parsing when available ([#24][], by Geoff Youngs). Bug fixes: * Use SCM_CREDS authentication only on FreeBSD, not on OpenBSD ([#21][], reported by Adde Nilsson). * Recognize signature "h" (UNIX_FD) used eg. by Upstart ([#23][], by Bernd Ahlers). * Find the session bus also via launchd, on OS X ([#20][], reported by Paul Sturgess). Other: * Now doing continuous integration with Travis: http://travis-ci.org/#!/mvidner/ruby-dbus [#20]: https://github.com/mvidner/ruby-dbus/issue/20 [#21]: https://github.com/mvidner/ruby-dbus/issue/21 [#23]: https://github.com/mvidner/ruby-dbus/issue/23 [#24]: https://github.com/mvidner/ruby-dbus/issue/24 [#27]: https://github.com/mvidner/ruby-dbus/issue/27 ## 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 ([#13][], by Eugene Korbut). * Fixed Main#quit to really quit the loop (by Josef Reidinger) * Unbundled files from Active Support (by Bohuslav Kabrda) [#13]: https://github.com/mvidner/ruby-dbus/issue/13 ## 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 ([#8][], by Takayuki YAMAGUCHI). * Allow reopening of a dbus_interface declaration ([#9][], by T. YAMAGUCHI). * Fixed ruby-1.9.2 compatibility again ([#12][]). * Fixed authentication on BSD ([#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" [#8]: https://github.com/mvidner/ruby-dbus/issue/8 [#9]: https://github.com/mvidner/ruby-dbus/issue/9 [#11]: https://github.com/mvidner/ruby-dbus/issue/11 [#12]: https://github.com/mvidner/ruby-dbus/issue/12 ## Ruby D-Bus 0.6.0 - 2010-12-11 Features: * Clients can access properties conveniently ([T#28][]). Bug fixes: * Service won't crash whan handling an unknown method or interface ([T#31][]). * Don't send an invalid error name when it originates from a NameError. [T#28]: https://trac.luon.net/ruby-dbus/ticket/28 [T#31]: https://trac.luon.net/ruby-dbus/ticket/31 ## 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 ([#6][]). * Converted the tutorial from Webgen to Markdown. Bug fixes: * Don't pass file descriptors to subprocesses. * Fixed InterfaceElement::validate_name ([T#38][], by Herwin Weststrate). * Fixed a typo in InvalidDestinationName description ([T#40][]). [#6]: https://github.com/mvidner/ruby-dbus/issue/6 [T#38]: https://trac.luon.net/ruby-dbus/ticket/38 [T#40]: https://trac.luon.net/ruby-dbus/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 ([T#36][]/[#5][]) * Ruby 1.9 compatibility ([T#37][], by Myra Nelson) [#5]: https://github.com/mvidner/ruby-dbus/issue/5 [T#36]: https://trac.luon.net/ruby-dbus/ticket/36 [T#37]: https://trac.luon.net/ruby-dbus/ticket/37 ## Ruby D-Bus 0.3.1 - 2010-07-22 Bug fixes: * Many on_signal could cause DBus.Error.LimitsExceeded [bsc#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 [#3][] * Re-added InterfaceElement#add_param for compatibility. * Handle more ways which tell us that a bus connection has died. [#3]: https://github.com/mvidner/ruby-dbus/issue/3 [bsc#617350]: https://bugzilla.novell.com/show_bug.cgi?id=617350 ## Ruby D-Bus 0.3.0 - 2010-03-28 Bug fixes: * Fixed "undefined method `get_node' for nil:NilClass" on Ubuntu Karmic ([T#34][]). * Get the session bus address even if unset in ENV ([#4][]). * Improved exceptions a bit: UndefinedInterface, InvalidMethodName, NoMethodError, no RuntimeException These are by Klaus Kaempf: * Make the signal dispatcher call all handlers ([#3][]). * Run on Ruby < 1.8.7 ([#2][]). * Avoid needless DBus::IncompleteBufferException ([T#33][]). * Don't ignore DBus Errors in request_service, raise them ([T#32][]). [#2]: https://github.com/mvidner/ruby-dbus/issue/2 [#3]: https://github.com/mvidner/ruby-dbus/issue/3 [#4]: https://github.com/mvidner/ruby-dbus/issue/4 [T#32]: https://trac.luon.net/ruby-dbus/ticket/32 [T#33]: https://trac.luon.net/ruby-dbus/ticket/33 [T#34]: https://trac.luon.net/ruby-dbus/ticket/34 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" ([T#25][] and [T#29][]). [T#25]: https://trac.luon.net/ruby-dbus/ticket/25 [T#29]: https://trac.luon.net/ruby-dbus/ticket/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 ([T#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. [T#31]: https://trac.luon.net/ruby-dbus/ticket/31 ## 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 ([bsc#537401][]). * Added details to PacketMarshaller exceptions ([bsc#538050][]). [bsc#537401]: https://bugzilla.novell.com/show_bug.cgi?id=537401 [bsc#538050]: https://bugzilla.novell.com/show_bug.cgi?id=538050 ## 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" ([T#30][]). * Fixed variant alignment ([T#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. [T#27]: https://trac.luon.net/ruby-dbus/ticket/27 [T#30]: https://trac.luon.net/ruby-dbus/ticket/30 ## 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. ruby-dbus-0.13.0/examples/0000755000004100000410000000000013004252346015354 5ustar www-datawww-dataruby-dbus-0.13.0/examples/doc/0000755000004100000410000000000013004252346016121 5ustar www-datawww-dataruby-dbus-0.13.0/examples/doc/_extract_examples0000755000004100000410000000136013004252346021556 0ustar www-datawww-data#!/usr/bin/env ruby if ARGV[0].nil? puts "Usage: #{$PROGRAM_NAME} file.md" exit end File.open(ARGV[0]) do |f| title = nil setup = "" example = "" f.each_line do |line| case line when /^#+ *(.*)/ new_title = Regexp.last_match(1) # write previous example unless example.empty? basename = title.downcase.gsub(/ +/, "_") if basename == "setting_up" setup = example else File.open("#{basename}.rb", "w") do |e| e.write setup e.write example e.chmod(0o755) end end end # set new title = new_title example = "" when /^ (.*)/ example << Regexp.last_match(1) << "\n" end end end ruby-dbus-0.13.0/examples/doc/README.md0000644000004100000410000000023213004252346017375 0ustar www-datawww-dataThis directory contains runnable examples extracted from [the reference documentation](../../doc/Reference.md). Run `rake doc:examples` to extract them. ruby-dbus-0.13.0/examples/rhythmbox/0000755000004100000410000000000013004252346017400 5ustar www-datawww-dataruby-dbus-0.13.0/examples/rhythmbox/playpause.rb0000755000004100000410000000100513004252346021727 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.13.0/examples/gdbus/0000755000004100000410000000000013004252346016460 5ustar www-datawww-dataruby-dbus-0.13.0/examples/gdbus/gdbus.glade0000644000004100000410000001005713004252346020565 0ustar www-datawww-data True False GD-Bus 500 400 True True True True True True in True True True False Session False True True in True True 1 True False System 1 False False True True True in True True False True ruby-dbus-0.13.0/examples/gdbus/gdbus0000755000004100000410000001625713004252346017525 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 "gtk2" ENABLE_SYSTEM = false class MethodCallWindow def initialize(pwindow, intf, meth) @intf = intf @meth = meth @entries = [] @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.send_sync_or_async(m) do |retm| if retm.is_a?(DBus::Error) puts "Error: #{retm.inspect}" else puts "Method #{m.member} returns: #{retm.params.inspect}" end end end end class DBusUI def initialize @glade = Gtk::Builder.new @glade << "gdbus.glade" @sessiontreeview = @glade.get_object("sessiontreeview") setup_treeview_renderer(@sessiontreeview, "D-Bus Objects") @sessiontreeview.selection.signal_connect("changed") do |selection| on_treeview_selection_changed(selection) end @systemtreeview = @glade.get_object("systemtreeview") setup_treeview_renderer(@systemtreeview, "D-Bus Objects") @systemtreeview.selection.signal_connect("changed") do |selection| on_treeview_selection_changed(selection) end @methsigtreeview = @glade.get_object("methsigtreeview") # ierk setup_methodview_renderer(@methsigtreeview) @methsigtreeview.signal_connect("row-activated") do |view, path, column| on_method_activated(view, path, column) end @window = @glade.get_object("window1") @window.show_all start_buses end def beautify_method(meth) # Damn, this need to be rewritten :p s = meth.name + "(" if meth.is_a?(DBus::Method) s += (meth.params.collect { |a| "in #{a[0]}:#{a[1]}" } + meth.rets.collect { |a| "out #{a[0]}:#{a[1]}" }).join(", ") elsif meth.is_a?(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] 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} ***" 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}" 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 return unless ENABLE_SYSTEM DBus::SystemBus.instance.proxy.ListNames do |_msg, names| fill_treeview(DBus::SystemBus.instance, @systemtreeview, names) 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 return if node.object.nil? node.object.interfaces.sort.each do |ifname| subiter = model.append(iter) subiter[0] = ifname subiter[1] = node.object[ifname] end end def introspect_services(model, bus) el = @introspect_array.shift if !(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 !@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.13.0/examples/gdbus/launch.sh0000755000004100000410000000010113004252346020261 0ustar www-datawww-data#!/bin/sh set -e # for the lazy typer ruby -w -I ../../lib gdbus ruby-dbus-0.13.0/examples/no-introspect/0000755000004100000410000000000013004252346020160 5ustar www-datawww-dataruby-dbus-0.13.0/examples/no-introspect/nm-test.rb0000755000004100000410000000107513004252346022102 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.13.0/examples/no-introspect/tracker-test.rb0000755000004100000410000000115713004252346023124 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.13.0/examples/service/0000755000004100000410000000000013004252346017014 5ustar www-datawww-dataruby-dbus-0.13.0/examples/service/service_newapi.rb0000755000004100000410000000220213004252346022343 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.13.0/examples/service/call_service.rb0000755000004100000410000000104513004252346021777 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") 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.13.0/examples/simple/0000755000004100000410000000000013004252346016645 5ustar www-datawww-dataruby-dbus-0.13.0/examples/simple/get_id.rb0000755000004100000410000000062113004252346020427 0ustar www-datawww-data#! /usr/bin/env ruby # find the library without external help $LOAD_PATH.unshift File.expand_path("../../../lib", __FILE__) require "dbus" bus = DBus::SystemBus.instance driver_svc = bus["org.freedesktop.DBus"] # p driver_svc driver_obj = driver_svc["/"] # p driver_obj driver_ifc = driver_obj["org.freedesktop.DBus"] # p driver_ifc bus_id = driver_ifc.GetId puts "The system bus id is #{bus_id}" ruby-dbus-0.13.0/examples/simple/call_introspect.rb0000755000004100000410000000137513004252346022370 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") 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.13.0/examples/simple/properties.rb0000755000004100000410000000073513004252346021376 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["/org/freedesktop/NetworkManager"] 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.13.0/examples/utils/0000755000004100000410000000000013004252346016514 5ustar www-datawww-dataruby-dbus-0.13.0/examples/utils/listnames.rb0000755000004100000410000000030513004252346021041 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.13.0/examples/utils/notify.rb0000755000004100000410000000053513004252346020357 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["org.freedesktop.Notifications"]["/org/freedesktop/Notifications"] i = o["org.freedesktop.Notifications"] i.Notify("notify.rb", 0, "info", ARGV[0], ARGV[1], [], {}, 2000) do |ret, param| end ruby-dbus-0.13.0/doc/0000755000004100000410000000000013004252346014303 5ustar www-datawww-dataruby-dbus-0.13.0/doc/Tutorial.md0000644000004100000410000004160013004252346016431 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.13.0/doc/Reference.md0000644000004100000410000001721213004252346016526 0ustar www-datawww-dataRuby D-Bus Reference ==================== This is a reference-style documentation. It's not [a tutorial for beginners](http://dbus.freedesktop.org/doc/dbus-tutorial.html), the reader should have knowledge of basic DBus concepts. Client Side ----------- This section should be enough if you only want to consume DBus APIs. ### Basic Concepts #### Setting Up Note that although the gem is named "ruby-dbus", the required name is simply "dbus" #! /usr/bin/env ruby require "dbus" #### Calling Methods 1. {DBus.session_bus Connect to the session bus}; 2. {DBus::Connection#[] get the screensaver service} 3. {DBus::Service#[] and its screensaver object}. 4. Call one of its methods in a loop, solving [xkcd#196](http://xkcd.com/196).   mybus = DBus.session_bus service = mybus["org.freedesktop.ScreenSaver"] object = service["/ScreenSaver"] loop do object.SimulateUserActivity sleep 5 * 60 end ##### Retrieving Return Values A method proxy simply returns a value. In this example SuspendAllowed returns a boolean: mybus = DBus.session_bus pm_s = mybus["org.freedesktop.PowerManagement"] pm_o = pm_s["/org/freedesktop/PowerManagement"] pm_i = pm_o["org.freedesktop.PowerManagement"] if pm_i.CanSuspend pm_i.Suspend end ###### Multiple Return Values In former versions of this library, a method proxy always returned an array of values. This was to accomodate the rare cases of a DBus method specifying more than one *out* parameter. For compatibility, the behavior is preserved if you construct a {DBus::ProxyObject} with {DBus::ApiOptions::A0}, which is what {DBus::Service#object} does. For nearly all methods you used `Method[0]` or `Method.first` ([I#30](https://github.com/mvidner/ruby-dbus/issues/30)). mybus = DBus.session_bus pm_s = mybus["org.freedesktop.PowerManagement"] # use legacy compatibility API pm_o = pm_s.object["/org/freedesktop/PowerManagement"] pm_i = pm_o["org.freedesktop.PowerManagement"] # wrong # if pm_i.CanSuspend # pm_i.Suspend # [false] is true! # end # right if pm_i.CanSuspend[0] pm_i.Suspend end #### Accessing Properties To access properties, think of the {DBus::ProxyObjectInterface interface} as a {DBus::ProxyObjectInterface#[] hash} keyed by strings, or use {DBus::ProxyObjectInterface#all_properties} to get an actual Hash of them. sysbus = DBus.system_bus upower_s = sysbus["org.freedesktop.UPower"] upower_o = upower_s["/org/freedesktop/UPower"] upower_i = upower_o["org.freedesktop.UPower"] on_battery = upower_i["OnBattery"] puts "Is the computer on battery now? #{on_battery}" (TODO a writable property example) Note that unlike for methods where the interface is inferred if unambiguous, for properties the interface must be explicitly chosen. That is because {DBus::ProxyObject} uses the {DBus::ProxyObject Hash#[]} API to provide the {DBus::ProxyObjectInterface interfaces}, not the properties. #### Asynchronous Operation If a method call has a block attached, it is asynchronous and the block is invoked on receiving a method_return message or an error message ##### Main Loop For asynchronous operation an event loop is necessary. Use {DBus::Main}: # [set up signal handlers...] main = DBus::Main.new main << mybus main.run Alternately, run the GLib main loop and add your DBus connections to it via {DBus::Connection#glibize}. #### Receiving Signals To receive signals for a specific object and interface, use {DBus::ProxyObjectInterface#on\_signal}(name, &block) or {DBus::ProxyObject#on_signal}(name, &block), for the default interface. sysbus = DBus.system_bus login_s = sysbus["org.freedesktop.login1"] # part of systemd login_o = login_s.object "/org/freedesktop/login1" login_o.default_iface = "org.freedesktop.login1.Manager" main = DBus::Main.new main << sysbus # to trigger this signal, login on the Linux console login_o.on_signal("SessionNew") do |name, opath| puts "New session: #{name}" session_o = login_s.object(opath) session_i = session_o["org.freedesktop.login1.Session"] uid, _user_opath = session_i["User"] puts "Its UID: #{uid}" main.quit end main.run ### Intermediate Concepts #### Names #### Types and Values, D-Bus -> Ruby D-Bus booleans, numbers, strings, arrays and dictionaries become their straightforward Ruby counterparts. Structs become arrays. Object paths become strings. Variants are simply unpacked to become their contained type. (ISSUE: prevents proper round-tripping!) #### Types and Values, Ruby -> D-Bus D-Bus has stricter typing than Ruby, so the library must decide which D-Bus type to choose. Most of the time the choice is dictated by the D-Bus signature. ##### Variants If the signature expects a Variant (which is the case for all Properties!) then an explicit mechanism is needed. 1. A pair [{DBus::Type::Type}, value] specifies to marshall *value* as that specified type. The pair can be produced by {DBus.variant}(signature, value) which gives the same result as [{DBus.type}(signature), value]. ISSUE: using something else than cryptic signatures is even more painful than remembering the signatures! foo_i["Bar"] = DBus.variant("au", [0, 1, 1, 2, 3, 5, 8]) 2. Other values are tried to fit one of these: Boolean, Double, Array of Variants, Hash of String keyed Variants, String, Int32, Int64. 3. **Deprecated:** A pair [String, value], where String is a valid signature of a single complete type, marshalls value as that type. This will hit you when you rely on method (2) but happen to have a particular string value in an array. ##### Byte Arrays If a byte array (`ay`) is expected you can pass a String too. The bytes sent are according to the string's [encoding](http://ruby-doc.org/core-2.0.0/Encoding.html). ##### nil `nil` is not allowed by D-Bus and attempting to send it raises an exception (but see [I#16](https://github.com/mvidner/ruby-dbus/issues/16)). #### Errors D-Bus calls can reply with an error instead of a return value. An error is translated to a Ruby exception, an instance of {DBus::Error}. nm_o = DBus.system_bus["org.freedesktop.NetworkManager"]["/org/freedesktop/NetworkManager"] nm = nm_o["org.freedesktop.NetworkManager"] begin nm.Sleep(false) rescue DBus::Error => e puts e unless e.name == "org.freedesktop.NetworkManager.AlreadyAsleepOrAwake" end #### Interfaces Methods, properties and signals of a D-Bus object always belong to one of its interfaces. Methods can be called without specifying their interface, as long as there is no ambiguity. There are two ways to resolve ambiguities: 1. assign an interface name to {DBus::ProxyObject#default_iface}. 2. get a specific {DBus::ProxyObjectInterface interface} of the object, with {DBus::ProxyObject#[]} and call methods from there. Signals and properties only work with a specific interface. #### Thread Safety Not there. An [incomplete attempt](https://github.com/mvidner/ruby-dbus/tree/multithreading) was made. ### Advanced Concepts #### Bus Addresses #### Without Introspection #### Name Overloading Service Side ------------ When you want to provide a DBus API. (check that client and service side have their counterparts) ### Basic #### Exporting a Method ##### Interfaces ##### Methods ##### Bus Names ##### Errors #### Exporting Properties ### Advanced #### Inheritance #### Names Specification Conformance ------------------------- This section lists the known deviations from version 0.19 of [the specification][spec]. [spec]: http://dbus.freedesktop.org/doc/dbus-specification.html 1. Properties support is basic. ruby-dbus-0.13.0/ruby-dbus.gemspec0000644000004100000410000000201713004252346017017 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 = "Pure Ruby module for interaction with D-Bus IPC system" 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,examples,lib,spec}/**/*", "COPYING", "NEWS.md", "Rakefile", "README.md", "ruby-dbus.gemspec", "VERSION", ".rspec"].to_a.sort s.require_path = "lib" s.required_ruby_version = ">= 2.0.0" # This is optional # s.add_runtime_dependency "nokogiri" s.add_development_dependency "coveralls" s.add_development_dependency "packaging_rake_tasks" s.add_development_dependency "rake" s.add_development_dependency "rspec", "~> 3" s.add_development_dependency "rubocop", "= 0.41.2" s.add_development_dependency "simplecov" end ruby-dbus-0.13.0/.rspec0000644000004100000410000000002513004252346014650 0ustar www-datawww-data--color --format doc ruby-dbus-0.13.0/spec/0000755000004100000410000000000013004252346014470 5ustar www-datawww-dataruby-dbus-0.13.0/spec/bus_spec.rb0000755000004100000410000000100613004252346016620 0ustar www-datawww-data#!/usr/bin/env rspec # Test the bus class require_relative "spec_helper" require "dbus" describe "BusTest" do before(:each) do @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") @svc.object("/").introspect end it "tests introspection not leaking" do # peek inside the object to see if a cleanup step worked or not some_hash = @bus.instance_eval { @method_call_replies || {} } # fail: "there are leftover method handlers" expect(some_hash.size).to eq(0) end end ruby-dbus-0.13.0/spec/session_bus_spec.rb0000755000004100000410000000515513004252346020374 0ustar www-datawww-data#!/usr/bin/env rspec require_relative "spec_helper" require "dbus" describe DBus::ASessionBus do subject(:dbus_session_bus_address) { "unix:abstract=/tmp/dbus-foo,guid=123" } describe "#session_bus_address" do around(:each) do |example| @original_dbus_session_bus_address = ENV["DBUS_SESSION_BUS_ADDRESS"] example.call ENV["DBUS_SESSION_BUS_ADDRESS"] = @original_dbus_session_bus_address end it "returns DBUS_SESSION_BUS_ADDRESS as it is" do ENV["DBUS_SESSION_BUS_ADDRESS"] = dbus_session_bus_address expect(DBus::ASessionBus.session_bus_address).to eq(dbus_session_bus_address) end end describe "#address_from_file" do let(:session_bus_file_path) { %r{\.dbus/session-bus/baz-\d} } before do # mocks of files for address_from_file method machine_id_path = File.expand_path("/etc/machine-id", __FILE__) expect(Dir).to receive(:[]).with(any_args) { [machine_id_path] } expect(File).to receive(:read).with(machine_id_path) { "baz" } expect(File).to receive(:exist?).with(session_bus_file_path) { true } end around(:each) do |example| with_env("DISPLAY", ":0.0") do example.call end end context "when DBUS_SESSION_BUS_ADDRESS from file is surrounded by quotation marks" do it "returns session bus address without single quotation marks" do expect(File).to receive(:open).with(session_bus_file_path) { <<-EOS.gsub(/^\s*/, "") } DBUS_SESSION_BUS_ADDRESS='#{dbus_session_bus_address}' DBUS_SESSION_BUS_PID=12345 DBUS_SESSION_BUS_WINDOWID=12345678 EOS expect(DBus::ASessionBus.address_from_file).to eq(dbus_session_bus_address) end it "returns session bus address without double quotation marks" do expect(File).to receive(:open).with(session_bus_file_path) { <<-EOS.gsub(/^\s*/, "") } DBUS_SESSION_BUS_ADDRESS="#{dbus_session_bus_address}" DBUS_SESSION_BUS_PID=12345 DBUS_SESSION_BUS_WINDOWID=12345678 EOS expect(DBus::ASessionBus.address_from_file).to eq(dbus_session_bus_address) end end context "when DBUS_SESSION_BUS_ADDRESS from file is not surrounded by any quotation marks" do it "returns session bus address as it is" do expect(File).to receive(:open).with(session_bus_file_path) { <<-EOS.gsub(/^\s*/, "") } DBUS_SESSION_BUS_ADDRESS=#{dbus_session_bus_address} DBUS_SESSION_BUS_PID=12345 DBUS_SESSION_BUS_WINDOWID=12345678 EOS expect(DBus::ASessionBus.address_from_file).to eq(dbus_session_bus_address) end end end end ruby-dbus-0.13.0/spec/value_spec.rb0000755000004100000410000000606713004252346017157 0ustar www-datawww-data#!/usr/bin/env rspec # -*- coding: utf-8 -*- require_relative "spec_helper" require "dbus" describe "ValueTest" do before(:each) do session_bus = DBus::ASessionBus.new @svc = session_bus.service("org.ruby.service") @obj = @svc.object("/org/ruby/MyInstance") @obj.default_iface = "org.ruby.SampleInterface" end it "tests passing an array of structs through a variant" do 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) end it "tests passing an array through a variant" do # 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 it "tests bouncing a variant" do expect(@obj.bounce_variant("cuckoo")[0]).to eq("cuckoo") expect(@obj.bounce_variant(["coucou", "kuku"])[0]).to eq(["coucou", "kuku"]) expect(@obj.bounce_variant([])[0]).to eq([]) empty_hash = {} expect(@obj.bounce_variant(empty_hash)[0]).to eq(empty_hash) end it "retrieves a single return value with API V1" do obj = @svc["/org/ruby/MyInstance"] obj.default_iface = "org.ruby.SampleInterface" expect(obj.bounce_variant("cuckoo")).to eq("cuckoo") expect(obj.bounce_variant(["coucou", "kuku"])).to eq(["coucou", "kuku"]) expect(obj.bounce_variant([])).to eq([]) empty_hash = {} expect(obj.bounce_variant(empty_hash)).to eq(empty_hash) end # these are ambiguous it "tests pairs with a string" do # deprecated expect(@obj.bounce_variant(["s", "foo"])[0]).to eq("foo") expect(@obj.bounce_variant(DBus.variant("s", "foo"))[0]).to eq("foo") expect(@obj.bounce_variant([DBus.type("s"), "foo"])[0]).to eq("foo") # 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 expect(@obj.variant_size(["s", "four"])[0]).to eq(4) # "av" is the simplest thing that will work, # shifting the heuristic from a pair to the individual items expect(@obj.variant_size(["av", ["s", "four"]])[0]).to eq(2) end it "tests marshalling an array of variants" do # 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 expect(@obj.Sybilla(choices)[0]).to eq("Do Plan A") # automatic typing expect(@obj.Sybilla(["Plan A", "Plan B"])[0]).to eq("Do Plan A") end it "tests service returning nonarray" do # "warning: default `to_a' will be obsolete" @obj.the_answer end it "tests multibyte string" do str = @obj.multibyte_string[0] expect(str).to eq("あいうえお") end end ruby-dbus-0.13.0/spec/server_spec.rb0000755000004100000410000000247013004252346017343 0ustar www-datawww-data#!/usr/bin/env rspec # Test that a server survives various error cases require_relative "spec_helper" 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 # 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 describe "ServerTest" do before(:each) do @bus = DBus::ASessionBus.new @svc = @bus.request_service "org.ruby.server-test" end after(:each) do @bus.proxy.ReleaseName "org.ruby.server-test" end it "tests unexporting an object" do obj = Foo.new "/org/ruby/Foo" @svc.export obj expect(@svc.unexport(obj)).to be_a DBus::Object end it "tests unexporting an object not exported" do obj = Foo.new "/org/ruby/Foo" expect(@svc.unexport(obj)).to be false end it "tests emiting signals" do obj = Foo.new "/org/ruby/Foo" @svc.export obj obj.signal_without_arguments obj.signal_with_argument(-0.1) end end ruby-dbus-0.13.0/spec/byte_array_spec.rb0000755000004100000410000000174613004252346020203 0ustar www-datawww-data#!/usr/bin/env rspec require_relative "spec_helper" require "dbus" describe "ByteArrayTest" do before(:each) do @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") @obj = @svc.object("/org/ruby/MyInstance") @obj.default_iface = "org.ruby.SampleInterface" end it "tests passing byte array" do data = [0, 77, 255] result = @obj.mirror_byte_array(data).first expect(result).to eq(data) end it "tests passing byte array from string" do data = "AAA" result = @obj.mirror_byte_array(data).first expect(result).to eq([65, 65, 65]) end it "tests passing byte array from hash" do # Hash is an Enumerable, but is caught earlier data = { "this will" => "fail" } expect { @obj.mirror_byte_array(data).first }.to raise_error(DBus::TypeException) end it "tests passing byte array from nonenumerable" do data = Time.now expect { @obj.mirror_byte_array(data).first }.to raise_error(DBus::TypeException) end end ruby-dbus-0.13.0/spec/service_newapi.rb0000755000004100000410000001541013004252346020024 0ustar www-datawww-data#!/usr/bin/env ruby # -*- coding: utf-8 -*- require_relative "spec_helper" SimpleCov.command_name "Service Tests" if Object.const_defined? "SimpleCov" # find the library without external help $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require "dbus" PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties".freeze class Test < DBus::Object INTERFACE = "org.ruby.SampleInterface".freeze 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| DBus.logger.debug variant.inspect 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 dbus_method :mirror_byte_array, "in bytes:ay, out mirrored:ay" do |bytes| [bytes] 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.Duplicates" do dbus_method :the_answer, "out answer:i" do [0] end dbus_method :interfaces, "out answer:i" do raise "This DBus method is currently shadowed by ProxyObject#interfaces" 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 self.LongTaskStart DBus.logger.debug "Long task began" task = Thread.new do DBus.logger.debug "Long task thread started (#{delay}s)" sleep delay DBus.logger.debug "Long task will signal end" self.LongTaskEnd end task.abort_on_exception = true # protect from test case bugs end dbus_signal :LongTaskStart 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| unless interface == INTERFACE raise DBus.error("org.freedesktop.DBus.Error.UnknownInterface"), "Interface '#{interface}' not found on object '#{@path}'" end case propname when "ReadMe" [@read_me] when "ReadOrWriteMe" [@read_or_write_me] when "WriteMe" raise DBus.error("org.freedesktop.DBus.Error.InvalidArgs"), "Property '#{interface}.#{propname}' (on object '#{@path}') is not readable" else # what should happen for unknown properties # plasma: InvalidArgs (propname), UnknownInterface (interface) raise DBus.error("org.freedesktop.DBus.Error.InvalidArgs"), "Property '#{interface}.#{propname}' not found on object '#{@path}'" end end dbus_method :Set, "in interface:s, in propname:s, in value:v" do |interface, propname, value| unless interface == INTERFACE raise DBus.error("org.freedesktop.DBus.Error.UnknownInterface"), "Interface '#{interface}' not found on object '#{@path}'" end case propname when "ReadMe" raise DBus.error("org.freedesktop.DBus.Error.InvalidArgs"), "Property '#{interface}.#{propname}' (on object '#{@path}') is not writable" when "ReadOrWriteMe" @read_or_write_me = value self.PropertiesChanged(interface, { propname => value }, []) when "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 end dbus_method :GetAll, "in interface:s, out value:a{sv}" do |interface| unless interface == INTERFACE raise DBus.error("org.freedesktop.DBus.Error.UnknownInterface"), "Interface '#{interface}' not found on object '#{@path}'" end [ { "ReadMe" => @read_me, "ReadOrWriteMe" => @read_or_write_me } ] 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? DBus.logger.debug "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.13.0/spec/introspection_spec.rb0000755000004100000410000000176513004252346020743 0ustar www-datawww-data#!/usr/bin/env rspec require_relative "spec_helper" require "dbus" describe "IntrospectionTest" do before(:each) do session_bus = DBus::ASessionBus.new svc = session_bus.service("org.ruby.service") @obj = svc.object("/org/ruby/MyInstance") @obj.introspect @obj.default_iface = "org.ruby.SampleInterface" end it "tests wrong number of arguments" do expect { @obj.test_variant "too", "many", "args" }.to raise_error(ArgumentError) # not enough expect { @obj.test_variant }.to raise_error(ArgumentError) end it "tests shortcut methods" do @obj.default_iface = nil expect(@obj.bounce_variant("varargs")).to eq(["varargs"]) # test for a duplicated method name expect { @obj.the_answer }.to raise_error(NoMethodError) # ensure istance methods of ProxyObject aren't overwritten by remote # methods expect { @obj.interfaces }.not_to raise_error @obj.default_iface = "org.ruby.SampleInterface" expect(@obj.the_answer).to eq([42]) end end ruby-dbus-0.13.0/spec/server_robustness_spec.rb0000755000004100000410000000416613004252346021636 0ustar www-datawww-data#!/usr/bin/env rspec # Test that a server survives various error cases require_relative "spec_helper" require "dbus" describe "ServerRobustnessTest" do before(:each) do @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") end # https://trac.luon.net/ruby-dbus/ticket/31 # the server should not crash it "tests no such path with introspection" do obj = @svc.object "/org/ruby/NotMyInstance" expect { obj.introspect }.to raise_error(DBus::Error) do |e| expect(e).to_not match(/timeout/) end end it "tests no such path without introspection" do obj = @svc.object "/org/ruby/NotMyInstance" ifc = DBus::ProxyObjectInterface.new(obj, "org.ruby.SampleInterface") ifc.define_method("the_answer", "out n:i") expect { ifc.the_answer }.to raise_error(DBus::Error) do |e| expect(e).to_not match(/timeout/) end end it "tests a method that raises" do obj = @svc.object "/org/ruby/MyInstance" obj.default_iface = "org.ruby.SampleInterface" expect { obj.will_raise }.to raise_error(DBus::Error) do |e| expect(e).to_not match(/timeout/) end end it "tests a method that raises name error" do obj = @svc.object "/org/ruby/MyInstance" obj.default_iface = "org.ruby.SampleInterface" expect { obj.will_raise_name_error }.to raise_error(DBus::Error) do |e| expect(e).to_not match(/timeout/) end end # https://trac.luon.net/ruby-dbus/ticket/31#comment:3 it "tests no such method without introspection" do obj = @svc.object "/org/ruby/MyInstance" ifc = DBus::ProxyObjectInterface.new(obj, "org.ruby.SampleInterface") ifc.define_method("not_the_answer", "out n:i") expect { ifc.not_the_answer }.to raise_error(DBus::Error) do |e| expect(e).to_not match(/timeout/) end end it "tests no such interface without introspection" do obj = @svc.object "/org/ruby/MyInstance" ifc = DBus::ProxyObjectInterface.new(obj, "org.ruby.NoSuchInterface") ifc.define_method("the_answer", "out n:i") expect { ifc.the_answer }.to raise_error(DBus::Error) do |e| expect(e).to_not match(/timeout/) end end end ruby-dbus-0.13.0/spec/thread_safety_spec.rb0000755000004100000410000000134313004252346020655 0ustar www-datawww-data#!/usr/bin/env rspec # Test thread safety require_relative "spec_helper" require "dbus" describe "ThreadSafetyTest" do it "tests thread competition" do 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.default_iface = "org.ruby.SampleInterface" 10.times do |i| print "#{i} " $stdout.flush expect(obj.the_answer[0]).to eq(42) sleep 0.1 * rand end end end jobs.each(&:join) end end ruby-dbus-0.13.0/spec/type_spec.rb0000755000004100000410000000067313004252346017021 0ustar www-datawww-data#!/usr/bin/env rspec require_relative "spec_helper" require "dbus" describe DBus do describe ".type" do %w{i ai a(ii) aai}.each do |s| it "parses some type #{s}" do expect(DBus.type(s).to_s).to be_eql s end end %w{aa (ii ii) hrmp}.each do |s| it "raises exception for invalid type #{s}" do expect { DBus.type(s).to_s }.to raise_error DBus::Type::SignatureException end end end end ruby-dbus-0.13.0/spec/variant_spec.rb0000755000004100000410000000370113004252346017477 0ustar www-datawww-data#!/usr/bin/env rspec # Test marshalling variants according to ruby types require_relative "spec_helper" require "dbus" describe "VariantTest" do before(:each) do @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") end def make_variant(a) DBus::PacketMarshaller.make_variant(a) end it "tests make variant scalar" do # special case: do not fail immediately, marshaller will do that expect(make_variant(nil)).to eq(["b", nil]) expect(make_variant(true)).to eq(["b", true]) # Integers # no byte expect(make_variant(42)).to eq(["i", 42]) # 3_000_000_000 can be u or x. # less specific test: just run it thru a loopback expect(make_variant(3_000_000_000)).to eq(["x", 3_000_000_000]) expect(make_variant(5_000_000_000)).to eq(["x", 5_000_000_000]) expect(make_variant(3.14)).to eq(["d", 3.14]) expect(make_variant("foo")).to eq(["s", "foo"]) expect(make_variant(:bar)).to eq(["s", "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 it "tests make variant array" do ai = [1, 2, 3] # as = ["one", "two", "three"] # which? # expect(make_variant(ai)).to eq(["ai", [1, 2, 3]]) expect(make_variant(ai)).to eq(["av", [["i", 1], ["i", 2], ["i", 3]]]) a0 = [] expect(make_variant(a0)).to eq(["av", []]) end it "tests make variant hash" do h = { "k1" => "v1", "k2" => "v2" } expect(make_variant(h)).to eq(["a{sv}", { "k1" => ["s", "v1"], "k2" => ["s", "v2"] }]) h0 = {} expect(make_variant(h0)).to eq(["a{sv}", {}]) end end ruby-dbus-0.13.0/spec/binding_spec.rb0000755000004100000410000000440013004252346017442 0ustar www-datawww-data#!/usr/bin/env rspec # Test the binding of dbus concepts to ruby concepts require_relative "spec_helper" require "dbus" describe "BindingTest" do before(:each) do @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") @base = @svc.object "/org/ruby/MyInstance" @base.default_iface = "org.ruby.SampleInterface" end # https://trac.luon.net/ruby-dbus/ticket/36#comment:3 it "tests class inheritance" do derived = @svc.object "/org/ruby/MyDerivedInstance" # it should inherit from the parent expect(derived["org.ruby.SampleInterface"]).not_to be_nil end # https://trac.luon.net/ruby-dbus/ticket/36 # Interfaces and methods/signals appeared on all classes it "tests separation of classes" do test2 = @svc.object "/org/ruby/MyInstance2" # it should have its own interface expect(test2["org.ruby.Test2"]).not_to be_nil # but not an interface of the Test class expect(test2["org.ruby.SampleInterface"]).to be_nil # and the parent should not get polluted by the child expect(@base["org.ruby.Test2"]).to be_nil end it "tests translating errors into exceptions" do # this is a generic call that will reply with the specified error expect { @base.Error "org.example.Fail", "as you wish" }.to raise_error(DBus::Error) do |e| expect(e.name).to eq("org.example.Fail") expect(e.message).to match(/as you wish/) end end it "tests generic dbus error" do # this is a generic call that will reply with the specified error expect { @base.will_raise_error_failed }.to raise_error(DBus::Error) do |e| expect(e.name).to eq("org.freedesktop.DBus.Error.Failed") expect(e.message).to match(/failed as designed/) end end it "tests dynamic interface definition" do # interfaces can be defined dynamicaly derived = DBus::Object.new "/org/ruby/MyDerivedInstance" # define a new interface derived.singleton_class.instance_eval do dbus_interface "org.ruby.DynamicInterface" do dbus_method :hello2, "in name:s, in name2:s" do |name, name2| puts "hello(#{name}, #{name2})" end end end # the object should have the new iface ifaces = derived.intfs expect(ifaces).to include "org.ruby.DynamicInterface" end end ruby-dbus-0.13.0/spec/err_msg_spec.rb0000755000004100000410000000257313004252346017477 0ustar www-datawww-data#!/usr/bin/env rspec # should report it missing on org.ruby.SampleInterface # (on object...) instead of on DBus::Proxy::ObjectInterface require_relative "spec_helper" require "dbus" describe "ErrMsgTest" do before(:each) do session_bus = DBus::ASessionBus.new svc = session_bus.service("org.ruby.service") @obj = svc.object("/org/ruby/MyInstance") @obj.default_iface = "org.ruby.SampleInterface" end it "tests report dbus interface" do # a specific exception... # mentioning DBus and the interface expect { @obj.NoSuchMethod } .to raise_error(NameError, /DBus interface.*#{@obj.default_iface}/) end it "tests report short struct" do expect { @obj.test_variant ["(ss)", ["too few"]] } .to raise_error(DBus::TypeException, /1 elements but type info for 2/) end it "tests report long struct" do expect { @obj.test_variant ["(ss)", ["a", "b", "too many"]] } .to raise_error(DBus::TypeException, /3 elements but type info for 2/) end it "tests report nil" do nils = [ ["(s)", [nil]], # would get disconnected ["i", nil], ["a{ss}", { "foo" => nil }] ] nils.each do |has_nil| # TODO: want backtrace from the perspective of the caller: # rescue/reraise in send_sync? expect { @obj.test_variant has_nil } .to raise_error(DBus::TypeException, /Cannot send nil/) end end end ruby-dbus-0.13.0/spec/spec_helper.rb0000644000004100000410000000510313004252346017305 0ustar www-datawww-datacoverage = if ENV["COVERAGE"] ENV["COVERAGE"] == "true" else # heuristics: enable for interactive builds (but not in OBS) # or in Travis ENV["DISPLAY"] || ENV["TRAVIS"] end if coverage require "simplecov" SimpleCov.root File.expand_path("../..", __FILE__) # do not cover specs SimpleCov.add_filter "_spec.rb" # do not cover the activesupport helpers SimpleCov.add_filter "/core_ext/" # use coveralls for on-line code coverage reporting at Travis CI if ENV["TRAVIS"] require "coveralls" end SimpleCov.start end $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) if Object.const_defined? "RSpec" # http://betterspecs.org/#expect RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = :expect end end end require "tempfile" require "timeout" TOPDIR = File.expand_path("../..", __FILE__) # path of config file for a private bus def config_file_path "#{TOPDIR}/spec/tools/dbus-limited-session.conf" end # set ENV[variable] to value and restore it after block is done def with_env(variable, value, &block) old_value = ENV[variable] ENV[variable] = value block.call ENV[variable] = old_value end # Set up a private session bus and run *block* with that. def with_private_bus(&block) address_file = Tempfile.new("dbus-address") pid_file = Tempfile.new("dbus-pid") output_file = Tempfile.new("dbus-output") # just in case temp_dir = Dir.mktmpdir with_env("XDG_DATA_DIRS", temp_dir) do cmd = "dbus-daemon --nofork --config-file=#{config_file_path} " \ "--print-address=3 3>#{address_file.path} " \ "--print-pid=4 4>#{pid_file.path} " \ ">#{output_file.path} 2>&1 &" system cmd # wait until dbus-daemon writes the info Timeout.timeout(10) do until File.size?(address_file) && File.size?(pid_file) sleep 0.1 end end address = address_file.read.chomp pid = pid_file.read.chomp.to_i with_env("DBUS_SESSION_BUS_ADDRESS", address) do block.call end Process.kill("TERM", pid) end FileUtils.rm_rf temp_dir end def with_service_by_activation(&block) name = "org.ruby.service" exec = "#{TOPDIR}/spec/service_newapi.rb" service_dir = "#{ENV["XDG_DATA_DIRS"]}/dbus-1/services" FileUtils.mkdir_p service_dir # file name actually does not need to match the service name File.open("#{service_dir}/#{name}.service", "w") do |f| s = < session unix:tmpdir=/tmp tcp:host=127.0.0.1 50 ruby-dbus-0.13.0/spec/tools/test_server0000755000004100000410000000136413004252346020127 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.13.0/spec/tools/dbus-launch-simple0000755000004100000410000000210213004252346021245 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=$(dirname $0)/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.13.0/spec/tools/test_env0000755000004100000410000000045513004252346017411 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" DIR=$(dirname $0) $DIR/test_server \ $DIR/../service_newapi.rb \ -- \ $DIR/dbus-launch-simple \ "$@" ruby-dbus-0.13.0/spec/bus_driver_spec.rb0000755000004100000410000000130713004252346020177 0ustar www-datawww-data#!/usr/bin/env rspec require_relative "spec_helper" require "dbus" describe DBus::Service do context "when a private bus is set up" do around(:each) do |example| with_private_bus do with_service_by_activation(&example) end end let(:bus) { DBus::ASessionBus.new } describe "#exists?" do it "is true for an existing service" do svc = bus.service("org.ruby.service") svc.object("/").introspect # must activate the service first :-/ expect(svc.exists?).to be true end it "is false for a nonexisting service" do svc = bus.service("org.ruby.nosuchservice") expect(svc.exists?).to be false end end end end ruby-dbus-0.13.0/spec/property_spec.rb0000755000004100000410000000360213004252346017717 0ustar www-datawww-data#!/usr/bin/env rspec require_relative "spec_helper" require "dbus" describe "PropertyTest" do before(:each) do session_bus = DBus::ASessionBus.new @svc = session_bus.service("org.ruby.service") @obj = @svc.object("/org/ruby/MyInstance") @iface = @obj["org.ruby.SampleInterface"] end it "tests property reading" do expect(@iface["ReadMe"]).to eq("READ ME") end it "tests property reading on a V1 object" do obj = @svc["/org/ruby/MyInstance"] iface = obj["org.ruby.SampleInterface"] expect(iface["ReadMe"]).to eq("READ ME") end it "tests property nonreading" do expect { @iface["WriteMe"] }.to raise_error(DBus::Error, /not readable/) end it "tests property writing" do @iface["ReadOrWriteMe"] = "VALUE" expect(@iface["ReadOrWriteMe"]).to eq("VALUE") end # https://github.com/mvidner/ruby-dbus/pull/19 it "tests service select timeout" do @iface["ReadOrWriteMe"] = "VALUE" expect(@iface["ReadOrWriteMe"]).to eq("VALUE") # wait for the service to become idle sleep 6 # fail: "Property value changed; perhaps the service died and got restarted" expect(@iface["ReadOrWriteMe"]).to eq("VALUE") end it "tests property nonwriting" do expect { @iface["ReadMe"] = "WROTE" }.to raise_error(DBus::Error, /not writable/) end it "tests get all" do all = @iface.all_properties expect(all.keys.sort).to eq(["ReadMe", "ReadOrWriteMe"]) end it "tests get all on a V1 object" do obj = @svc["/org/ruby/MyInstance"] iface = obj["org.ruby.SampleInterface"] all = iface.all_properties expect(all.keys.sort).to eq(["ReadMe", "ReadOrWriteMe"]) end it "tests unknown property reading" do expect { @iface["Spoon"] }.to raise_error(DBus::Error, /not found/) end it "tests unknown property writing" do expect { @iface["Spoon"] = "FPRK" }.to raise_error(DBus::Error, /not found/) end end ruby-dbus-0.13.0/spec/proxy_object_spec.rb0000644000004100000410000000221513004252346020536 0ustar www-datawww-data#!/usr/bin/env rspec require_relative "spec_helper" require "dbus" describe DBus::ProxyObject do around(:each) do |example| with_private_bus do with_service_by_activation(&example) end end let(:bus) { DBus::ASessionBus.new } context "when calling org.ruby.service" do let(:svc) { bus["org.ruby.service"] } context "when introspection mode is not specified" do describe "#bounce_variant" do it "works without an explicit #introspect call" do obj = svc["/org/ruby/MyInstance"] ifc = obj["org.ruby.SampleInterface"] expect(ifc.bounce_variant(42)).to be_eql 42 end it "works with one #introspect call" do obj = svc["/org/ruby/MyInstance"] obj.introspect ifc = obj["org.ruby.SampleInterface"] expect(ifc.bounce_variant(42)).to be_eql 42 end it "works with two #introspect calls" do obj = svc["/org/ruby/MyInstance"] obj.introspect obj.introspect ifc = obj["org.ruby.SampleInterface"] expect(ifc.bounce_variant(42)).to be_eql 42 end end end end end ruby-dbus-0.13.0/spec/session_bus_spec_manual.rb0000755000004100000410000000056513004252346021731 0ustar www-datawww-data#!/usr/bin/env rspec require_relative "spec_helper" require "dbus" describe DBus::ASessionBus do context "when DBUS_SESSION_BUS_ADDRESS is unset in ENV (Issue#4)" do ENV.delete "DBUS_SESSION_BUS_ADDRESS" it "can connect" do bus = DBus::ASessionBus.new svc = bus.service("org.freedesktop.DBus") expect(svc.exists?).to be true end end end ruby-dbus-0.13.0/spec/async_spec.rb0000755000004100000410000000202513004252346017146 0ustar www-datawww-data#!/usr/bin/env rspec # Test the binding of dbus concepts to ruby concepts require_relative "spec_helper" require "dbus" describe "AsyncTest" do before(:each) do @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") @obj = @svc.object "/org/ruby/MyInstance" @obj.default_iface = "org.ruby.SampleInterface" end # https://github.com/mvidner/ruby-dbus/issues/13 it "tests async_call_to_default_interface" do loop = DBus::Main.new loop << @bus immediate_answer = @obj.the_answer do |_msg, retval| expect(retval).to eq(42) loop.quit end expect(immediate_answer).to be_nil # wait for the async reply loop.run end it "tests async_call_to_explicit_interface" do loop = DBus::Main.new loop << @bus ifc = @obj["org.ruby.AnotherInterface"] immediate_answer = ifc.Reverse("abcd") do |_msg, retval| expect(retval).to eq("dcba") loop.quit end expect(immediate_answer).to be_nil # wait for the async reply loop.run end end ruby-dbus-0.13.0/spec/introspect_xml_parser_spec.rb0000755000004100000410000000113513004252346022460 0ustar www-datawww-data#!/usr/bin/env rspec require_relative "spec_helper" require "dbus" describe "IntrospectXMLParserTest" do it "tests split interfaces" do xml = < EOS interfaces, _subnodes = DBus::IntrospectXMLParser.new(xml).parse foo = interfaces.find { |i| i.name == "org.example.Foo" } expect(foo.methods.keys.size).to eq(2) end end ruby-dbus-0.13.0/spec/bus_and_xml_backend_spec.rb0000755000004100000410000000232713004252346022000 0ustar www-datawww-data#!/usr/bin/env rspec # Test the bus class require_relative "spec_helper" require "rubygems" require "nokogiri" require "dbus" describe "BusAndXmlBackendTest" do before(:each) do @bus = DBus::ASessionBus.new end it "tests introspection reading rexml" do DBus::IntrospectXMLParser.backend = DBus::IntrospectXMLParser::REXMLParser @svc = @bus.service("org.ruby.service") obj = @svc.object("/org/ruby/MyInstance") obj.default_iface = "org.ruby.SampleInterface" # "should respond to :the_answer" expect(obj.the_answer[0]).to eq(42) # "should work with multiple interfaces" expect(obj["org.ruby.AnotherInterface"].Reverse("foo")[0]).to eq("oof") end it "tests introspection reading nokogiri" do # peek inside the object to see if a cleanup step worked or not DBus::IntrospectXMLParser.backend = DBus::IntrospectXMLParser::NokogiriParser @svc = @bus.service("org.ruby.service") obj = @svc.object("/org/ruby/MyInstance") obj.default_iface = "org.ruby.SampleInterface" # "should respond to :the_answer" expect(obj.the_answer[0]).to eq(42) # "should work with multiple interfaces" expect(obj["org.ruby.AnotherInterface"].Reverse("foo")[0]).to eq("oof") end end ruby-dbus-0.13.0/spec/main_loop_spec.rb0000755000004100000410000000442713004252346020016 0ustar www-datawww-data#!/usr/bin/env rspec # Test the main loop require_relative "spec_helper" require "dbus" describe "MainLoopTest" do before(:each) do @session_bus = DBus::ASessionBus.new svc = @session_bus.service("org.ruby.service") @obj = svc.object("/org/ruby/MyInstance") @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_method :wait_for_message_orig, :wait_for_message def wait_for_message_lazy DBus.logger.debug "I am so lazy" sleep 1 # Give the server+bus a chance to join the messages wait_for_message_orig end alias_method :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_method :wait_for_message, :wait_for_message_orig end end def test_loop_quit(delay) @obj.on_signal "LongTaskEnd" do DBus.logger.debug "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 DBus.logger.debug "Dynamite burning" sleep 2 DBus.logger.debug "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 DBus.logger.debug "Defusing dynamite" # if we get here, defuse the bomb dynamite.exit # remove signal handler @obj.on_signal "LongTaskEnd" end it "tests loop quit" do test_loop_quit 1 end # https://bugzilla.novell.com/show_bug.cgi?id=537401 it "tests loop drained socket" do test_loop_quit 0 end end ruby-dbus-0.13.0/spec/signal_spec.rb0000755000004100000410000000430713004252346017313 0ustar www-datawww-data#!/usr/bin/env rspec # Test the signal handlers require_relative "spec_helper" require "dbus" describe "SignalHandlerTest" do before(:each) do @session_bus = DBus::ASessionBus.new svc = @session_bus.service("org.ruby.service") @obj = svc.object("/org/ruby/MyInstance") @obj.default_iface = "org.ruby.Loop" @intf = @obj["org.ruby.Loop"] @loop = DBus::Main.new @loop << @session_bus end # testing for commit 017c83 (kkaempf) it "tests overriding a handler" do DBus.logger.debug "Inside test_overriding_a_handler" counter = 0 @obj.on_signal "LongTaskEnd" do DBus.logger.debug "+10" counter += 10 end @obj.on_signal "LongTaskEnd" do DBus.logger.debug "+1" counter += 1 end DBus.logger.debug "will begin" @obj.LongTaskBegin 3 quitter = Thread.new do DBus.logger.debug "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 DBus.logger.debug "will quit" @loop.quit end @loop.run quitter.join expect(counter).to eq(1) end it "tests on signal overload" do DBus.logger.debug "Inside test_on_signal_overload" counter = 0 started = false @intf.on_signal "LongTaskStart" do started = true end # Same as intf.on_signal("LongTaskEnd"), just the old way @intf.on_signal @obj.bus, "LongTaskEnd" do counter += 1 end @obj.LongTaskBegin 3 quitter = Thread.new do DBus.logger.debug "sleep before quit" sleep 1 DBus.logger.debug "will quit" @loop.quit end @loop.run quitter.join expect(started).to eq(true) expect(counter).to eq(1) expect { @intf.on_signal }.to raise_error(ArgumentError) # not enough expect { @intf.on_signal "to", "many", "yarrrrr!" }.to raise_error(ArgumentError) end it "tests too many rules" do 100.times do @obj.on_signal "Whichever" do puts "not called" end end end it "tests removing a nonexistent rule" do @obj.on_signal "DoesNotExist" end end ruby-dbus-0.13.0/lib/0000755000004100000410000000000013004252346014304 5ustar www-datawww-dataruby-dbus-0.13.0/lib/dbus.rb0000644000004100000410000000412213004252346015565 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_relative "dbus/api_options" require_relative "dbus/auth" require_relative "dbus/bus" require_relative "dbus/core_ext/class/attribute" require_relative "dbus/error" require_relative "dbus/export" require_relative "dbus/introspect" require_relative "dbus/logger" require_relative "dbus/marshall" require_relative "dbus/matchrule" require_relative "dbus/message" require_relative "dbus/message_queue" require_relative "dbus/proxy_object" require_relative "dbus/proxy_object_factory" require_relative "dbus/proxy_object_interface" require_relative "dbus/type" require_relative "dbus/xml" require "socket" require "thread" # = 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".freeze # Byte signifying big endianness. BIG_END = "B".freeze # Byte signifying little endianness. LIL_END = "l".freeze # 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 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 an invalid method name is used. # FIXME: use NameError class InvalidMethodName < Exception end # Exception raised when invalid introspection data is parsed/used. class InvalidIntrospectionData < Exception end end # module DBus ruby-dbus-0.13.0/lib/dbus/0000755000004100000410000000000013004252346015241 5ustar www-datawww-dataruby-dbus-0.13.0/lib/dbus/export.rb0000644000004100000410000001101313004252346017103 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. Hash: String => Interface class_attribute :intfs self.intfs = {} # 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 # Dispatch a message _msg_ to call exported methods def dispatch(msg) case msg.message_type when Message::METHOD_CALL reply = nil begin if !intfs[msg.interface] raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"), "Interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist" end meth = intfs[msg.interface].methods[msg.member.to_sym] if !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 dbus_msg_exc = msg.annotate_exception(ex) reply = ErrorMessage.from_exception(dbus_msg_exc).reply_to(msg) end @service.bus.message_queue.push(reply) end end # Select (and create) the interface that the following defined methods # belong to. def self.dbus_interface(s) @@intfs_mutex.synchronize do @@cur_intf = intfs[s] if !@@cur_intf @@cur_intf = Interface.new(s) # As this is a mutable class_attr, we cannot use # self.intfs[s] = @@cur_intf # Hash#[]= # as that would modify parent class attr in place. # Using the setter lets a subclass have the new value # while the superclass keeps the old one. self.intfs = 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 #################################################################### # Helper method that returns a method name generated from the interface # name _intfname_ and method name _methname_. # @api private def self.make_method_name(intfname, methname) "#{intfname}%%#{methname}" end end # class Object end # module DBus ruby-dbus-0.13.0/lib/dbus/core_ext/0000755000004100000410000000000013004252346017051 5ustar www-datawww-dataruby-dbus-0.13.0/lib/dbus/core_ext/module/0000755000004100000410000000000013004252346020336 5ustar www-datawww-dataruby-dbus-0.13.0/lib/dbus/core_ext/module/remove_method.rb0000644000004100000410000000071113004252346023517 0ustar www-datawww-data# copied from activesupport/core_ext from Rails, MIT license # https://github.com/rails/rails/tree/5aa869861c192daceafe3a3ee50eb93f5a2b7bd2/activesupport/lib/active_support/core_ext class Module def remove_possible_method(method) if method_defined?(method) || private_method_defined?(method) undef_method(method) end end def redefine_method(method, &block) remove_possible_method(method) define_method(method, &block) end end ruby-dbus-0.13.0/lib/dbus/core_ext/array/0000755000004100000410000000000013004252346020167 5ustar www-datawww-dataruby-dbus-0.13.0/lib/dbus/core_ext/array/extract_options.rb0000644000004100000410000000172113004252346023742 0ustar www-datawww-data# copied from activesupport/core_ext from Rails, MIT license # https://github.com/rails/rails/tree/5aa869861c192daceafe3a3ee50eb93f5a2b7bd2/activesupport/lib/active_support/core_ext class Hash # By default, only instances of Hash itself are extractable. # Subclasses of Hash may implement this method and return # true to declare themselves as extractable. If a Hash # is extractable, Array#extract_options! pops it from # the Array when it is the last element of the Array. def extractable_options? instance_of?(Hash) end end class Array # Extracts options from a set of arguments. Removes and returns the last # element in the array if it's a hash, otherwise returns a blank hash. # # def options(*args) # args.extract_options! # end # # options(1, 2) # => {} # options(1, 2, a: :b) # => {:a=>:b} def extract_options! if last.is_a?(Hash) && last.extractable_options? pop else {} end end end ruby-dbus-0.13.0/lib/dbus/core_ext/kernel/0000755000004100000410000000000013004252346020331 5ustar www-datawww-dataruby-dbus-0.13.0/lib/dbus/core_ext/kernel/singleton_class.rb0000644000004100000410000000053613004252346024051 0ustar www-datawww-data# copied from activesupport/core_ext from Rails, MIT license # https://github.com/rails/rails/tree/5aa869861c192daceafe3a3ee50eb93f5a2b7bd2/activesupport/lib/active_support/core_ext module Kernel # class_eval on an object acts like singleton_class.class_eval. def class_eval(*args, &block) singleton_class.class_eval(*args, &block) end end ruby-dbus-0.13.0/lib/dbus/core_ext/class/0000755000004100000410000000000013004252346020156 5ustar www-datawww-dataruby-dbus-0.13.0/lib/dbus/core_ext/class/attribute.rb0000644000004100000410000001045413004252346022512 0ustar www-datawww-data# copied from activesupport/core_ext from Rails, MIT license # https://github.com/rails/rails/tree/5aa869861c192daceafe3a3ee50eb93f5a2b7bd2/activesupport/lib/active_support/core_ext require 'dbus/core_ext/kernel/singleton_class' require 'dbus/core_ext/module/remove_method' require 'dbus/core_ext/array/extract_options' class Class # Declare a class-level attribute whose value is inheritable by subclasses. # Subclasses can change their own value and it will not impact parent class. # # class Base # class_attribute :setting # end # # class Subclass < Base # end # # Base.setting = true # Subclass.setting # => true # Subclass.setting = false # Subclass.setting # => false # Base.setting # => true # # In the above case as long as Subclass does not assign a value to setting # by performing Subclass.setting = _something_ , Subclass.setting # would read value assigned to parent class. Once Subclass assigns a value then # the value assigned by Subclass would be returned. # # This matches normal Ruby method inheritance: think of writing an attribute # on a subclass as overriding the reader method. However, you need to be aware # when using +class_attribute+ with mutable structures as +Array+ or +Hash+. # In such cases, you don't want to do changes in places but use setters: # # Base.setting = [] # Base.setting # => [] # Subclass.setting # => [] # # # Appending in child changes both parent and child because it is the same object: # Subclass.setting << :foo # Base.setting # => [:foo] # Subclass.setting # => [:foo] # # # Use setters to not propagate changes: # Base.setting = [] # Subclass.setting += [:foo] # Base.setting # => [] # Subclass.setting # => [:foo] # # For convenience, an instance predicate method is defined as well. # To skip it, pass instance_predicate: false. # # Subclass.setting? # => false # # Instances may overwrite the class value in the same way: # # Base.setting = true # object = Base.new # object.setting # => true # object.setting = false # object.setting # => false # Base.setting # => true # # To opt out of the instance reader method, pass instance_reader: false. # # object.setting # => NoMethodError # object.setting? # => NoMethodError # # To opt out of the instance writer method, pass instance_writer: false. # # object.setting = false # => NoMethodError # # To opt out of both instance methods, pass instance_accessor: false. def class_attribute(*attrs) options = attrs.extract_options! instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true) instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true) instance_predicate = options.fetch(:instance_predicate, true) attrs.each do |name| define_singleton_method(name) { nil } define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate ivar = "@#{name}" define_singleton_method("#{name}=") do |val| singleton_class.class_eval do remove_possible_method(name) define_method(name) { val } end if singleton_class? class_eval do remove_possible_method(name) define_method(name) do if instance_variable_defined? ivar instance_variable_get ivar else singleton_class.send name end end end end val end if instance_reader remove_possible_method name define_method(name) do if instance_variable_defined?(ivar) instance_variable_get ivar else self.class.public_send name end end define_method("#{name}?") { !!public_send(name) } if instance_predicate end attr_writer name if instance_writer end end private unless respond_to?(:singleton_class?) def singleton_class? ancestors.first != self end end end ruby-dbus-0.13.0/lib/dbus/proxy_object.rb0000644000004100000410000001214513004252346020300 0ustar www-datawww-data# This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # Copyright (C) 2009-2014 Martin Vidner # # 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 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 # @return [String] The name of the default interface of the object. attr_accessor :default_iface # @api private # @return [ApiOptions] attr_reader :api # Creates a new proxy object living on the given _bus_ at destination _dest_ # on the given _path_. def initialize(bus, dest, path, api: ApiOptions::CURRENT) @bus = bus @destination = dest @path = path @introspected = false @interfaces = {} @subnodes = [] @api = api end # Returns the interfaces of the object. def interfaces introspect unless introspected @interfaces.keys end # Retrieves an interface of the proxy object # @param [String] intfname # @return [ProxyObjectInterface] def [](intfname) introspect unless introspected @interfaces[intfname] end # Maps the given interface name _intfname_ to the given interface _intf. # @param [String] intfname # @param [ProxyObjectInterface] intf # @return [ProxyObjectInterface] # @api private 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) define_shortcut_methods xml end # For each non duplicated method name in any interface present on the # caller, defines a shortcut method dynamically. # This function is automatically called when a {ProxyObject} is # introspected. def define_shortcut_methods # builds a list of duplicated methods dup_meths = [] univocal_meths = {} @interfaces.each_value do |intf| intf.methods.each_value do |meth| name = meth.name.to_sym # don't overwrite instance methods! next if dup_meths.include?(name) next if self.class.instance_methods.include?(name) if univocal_meths.include? name univocal_meths.delete name dup_meths << name else univocal_meths[name] = intf end end end univocal_meths.each do |name, intf| # creates a shortcut function that forwards each call to the method on # the appropriate intf singleton_class.class_eval do redefine_method name do |*args, &reply_handler| intf.method(name).call(*args, &reply_handler) end end end end # Returns whether the object has an interface with the given _name_. def has_iface?(name) introspect unless introspected @interfaces.key?(name) end # Registers a handler, the code block, for a signal with the given _name_. # It uses _default_iface_ which must have been set. # @return [void] def on_signal(name, &block) # TODO: improve raise NoMethodError unless @default_iface && has_iface?(@default_iface) @interfaces[@default_iface].on_signal(name, &block) end #################################################### private # Handles all unkown methods, mostly to route method calls to the # default interface. def method_missing(name, *args, &reply_handler) unless @default_iface && has_iface?(@default_iface) # 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 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 end end # class ProxyObject end ruby-dbus-0.13.0/lib/dbus/bus.rb0000644000004100000410000005503513004252346016367 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 caan 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" # = 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 = name @bus = 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 raise NotImplementedError if block_given? rec_introspect(@root, "/") self end # Retrieves an object at the given _path_. # @return [ProxyObject] def [](path) object(path, api: ApiOptions::A1) end # Retrieves an object at the given _path_ # whose methods always return an array. # @return [ProxyObject] def object(path, api: ApiOptions::A0) node = get_node(path, _create = true) if node.object.nil? || node.object.api != api node.object = ProxyObject.new( @bus, @name, path, api: api ) 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, "DBus::Service#unexport() expects a DBus::Object argument" unless obj.is_a?(DBus::Object) return false unless obj.path last_path_separator_idx = obj.path.rindex("/") parent_path = obj.path[1..last_path_separator_idx - 1] node_name = obj.path[last_path_separator_idx + 1..-1] parent_node = get_node(parent_path, false) return false unless parent_node obj.service = nil parent_node.delete(node_name).object 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(%r{^/}, "").split("/").each do |elem| if !(n[elem]) return nil if !create n[elem] = Node.new(elem) end n = n[elem] end if n.nil? DBus.logger.debug "Warning, unknown object #{path}" 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) subpath = if path == "/" "/" + nodename else path + "/" + nodename end rec_introspect(subnode, subpath) end return if intfs.empty? node.object = ProxyObjectFactory.new(xml, @bus, @name, path).build 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 = ' ' 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 !@object.nil? s += format("%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 # pop and push messages here attr_reader :message_queue # 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) @message_queue = MessageQueue.new(path) @unique_name = nil @method_call_replies = {} @method_call_msgs = {} @signal_matchrules = {} @proxy = nil @object_root = Node.new("/") end # Dispatch all messages that are available in the queue, # but do not block on the queue. # Called by a main loop when something is available in the queue def dispatch_message_queue while (msg = @message_queue.pop(:non_block)) # FIXME: EOFError process(msg) end end # Tell a bus to register itself on the glib main loop def glibize require "glib2" # Circumvent a ruby-glib bug @channels ||= [] gio = GLib::IOChannel.new(@message_queue.socket.fileno) @channels << gio gio.add_watch(GLib::IOChannel::IN) do |_c, _ch| dispatch_message_queue 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 = ' '.freeze # This apostroph is for syntax highlighting editors confused by above xml: " # @api private # 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| raise rmsg if rmsg.is_a?(Error) ret = rmsg.params 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 @message_queue.push(message) end ret end # @api private 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 # @api private # 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 !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. # @return [Service] 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| # check and report errors first raise rmsg if rmsg.is_a?(Error) raise NameRequestError unless r == REQUEST_NAME_REPLY_PRIMARY_OWNER end @service = Service.new(name, self) @service end # Set up a ProxyObject for the bus itself, since the bus is introspectable. # @return [ProxyObject] that always returns an array # ({DBus::ApiOptions#proxy_method_returns_array}) # Returns the object. def proxy if @proxy.nil? path = "/org/freedesktop/DBus" dest = "org.freedesktop.DBus" pof = DBus::ProxyObjectFactory.new( DBUSXMLINTRO, self, dest, path, api: ApiOptions::A0 ) @proxy = pof.build["org.freedesktop.DBus"] end @proxy end # @api private # Wait for a message to arrive. Return it once it is available. def wait_for_message @message_queue.pop # FIXME: EOFError end # @api private # 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 @message_queue.push(m) @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.key? m.serial retm = wait_for_message process(retm) end end # @api private # 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 DBus.logger.debug "#{@signal_matchrules.size} rules, adding #{mrs.inspect}" # don't ask for the same match if we override it unless @signal_matchrules.key?(mrs) DBus.logger.debug "Asked for a new match" proxy.AddMatch(mrs) end @signal_matchrules[mrs] = slot end def remove_match(mr) mrs = mr.to_s rule_existed = @signal_matchrules.delete(mrs).nil? # don't remove nonexisting matches. return if rule_existed # 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 # @api private # 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 !mcs DBus.logger.debug "no return code for mcs: #{mcs.inspect} m: #{m.inspect}" 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" DBus.logger.debug "Got method call on /org/freedesktop/DBus" end node = @service.get_node(m.path) if !node reply = Message.error(m, "org.freedesktop.DBus.Error.UnknownObject", "Object #{m.path} doesn't exist") @message_queue.push(reply) # handle introspectable as an exception: elsif m.interface == "org.freedesktop.DBus.Introspectable" && m.member == "Introspect" reply = Message.new(Message::METHOD_RETURN).reply_to(m) reply.sender = @unique_name reply.add_param(Type::STRING, node.to_xml) @message_queue.push(reply) else obj = node.object return if obj.nil? # FIXME, pushes 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 DBus.logger.debug "Unknown message type: #{m.message_type}" end rescue Exception => ex raise m.annotate_exception(ex) end # Retrieves the Service with the given _name_. # @return [Service] def service(name) # The service might not exist at this time so we cannot really check # anything Service.new(name, self) end alias [] service # @api private # 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 @message_queue.push(m) 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 DBus.logger.debug "Got hello reply. Our unique_name is #{@unique_name}" end @service = Service.new(@unique_name, self) 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(self.class.session_bus_address) send_hello end def self.session_bus_address ENV["DBUS_SESSION_BUS_ADDRESS"] || address_from_file || "launchd:env=DBUS_LAUNCHD_SESSION_BUS_SOCKET" end def self.address_from_file # systemd uses /etc/machine-id # traditional dbus uses /var/lib/dbus/machine-id machine_id_path = Dir["{/etc,/var/lib/dbus,/var/db/dbus}/machine-id"].first return nil unless machine_id_path machine_id = File.read(machine_id_path).chomp display = ENV["DISPLAY"][/:(\d+)\.?/, 1] bus_file_path = File.join(ENV["HOME"], "/.dbus/session-bus/#{machine_id}-#{display}") return nil unless File.exist?(bus_file_path) File.open(bus_file_path).each_line do |line| if line =~ /^DBUS_SESSION_BUS_ADDRESS=(.*)/ address = Regexp.last_match(1) return address[/\A'(.*)'\z/, 1] || address[/\A"(.*)"\z/, 1] || address 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) 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) send_hello end end # See ASystemBus class SystemBus < ASystemBus include Singleton end # Shortcut for the {SystemBus} instance # @return [Connection] def self.system_bus SystemBus.instance end # Shortcut for the {SessionBus} instance # @return [Connection] def self.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 = {} @quitting = false end # Add a _bus_ to the list of buses to watch for events. def <<(bus) @buses[bus.message_queue.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.message_queue.message_from_buffer_nonblock) b.process(m) end end while !@quitting && !@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.message_queue.buffer_from_socket_nonblock rescue EOFError, SystemCallError @buses.delete socket # this bus died next end while (m = b.message_queue.message_from_buffer_nonblock) b.process(m) end end end end end # class Main end # module DBus ruby-dbus-0.13.0/lib/dbus/marshall.rb0000644000004100000410000003156413004252346017402 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 = buffer.dup @endianness = 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 = [] 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 read(nbytes) raise IncompleteBufferException if @idx + nbytes > @buffy.bytesize ret = @buffy.slice(@idx, nbytes) @idx += nbytes ret end # Read the string length and string itself from the buffer. # Return the string. def read_string align(4) str_sz = read(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 # Read the signature length and signature itself from the buffer. # Return the signature. def read_signature str_sz = read(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 = read(1).unpack("C")[0] when Type::UINT16 align(2) packet = read(2).unpack(@uint16)[0] when Type::INT16 align(4) packet = read(4).unpack(@uint16)[0] if (packet & 0x8000) != 0 packet -= 0x10000 end when Type::UINT32, Type::UNIX_FD align(4) packet = read(4).unpack(@uint32)[0] when Type::INT32 align(4) packet = read(4).unpack(@uint32)[0] if (packet & 0x80000000) != 0 packet -= 0x100000000 end when Type::UINT64 align(8) packet_l = read(4).unpack(@uint32)[0] packet_h = read(4).unpack(@uint32)[0] packet = if @endianness == LIL_END packet_l + packet_h * 2**32 else packet_l * 2**32 + packet_h end when Type::INT64 align(8) packet_l = read(4).unpack(@uint32)[0] packet_h = read(4).unpack(@uint32)[0] packet = if @endianness == LIL_END packet_l + packet_h * 2**32 else packet_l * 2**32 + packet_h end if (packet & 0x8000000000000000) != 0 packet -= 0x10000000000000000 end when Type::DOUBLE align(8) packet = read(8).unpack(@double)[0] when Type::BOOLEAN align(4) v = read(4).unpack(@uint32)[0] raise InvalidPacketException if ![0, 1].member?(v) packet = (v == 1) when Type::ARRAY align(4) # checks please array_sz = read(4).unpack(@uint32)[0] raise InvalidPacketException if array_sz > 67_108_864 align(signature.child.alignment) raise IncompleteBufferException if @idx + array_sz > @buffy.bytesize packet = [] start_idx = @idx while @idx - start_idx < array_sz packet << do_parse(signature.child) end if signature.child.sigtype == Type::DICT_ENTRY packet = Hash[packet] end when Type::STRUCT align(8) packet = [] signature.members.each do |elem| packet << do_parse(elem) end when Type::VARIANT string = read_signature # error checking please sig = Type::Parser.new(string).parse[0] align(sig.alignment) packet = do_parse(sig) when Type::OBJECT_PATH packet = read_string when Type::STRING packet = read_string packet.force_encoding("UTF-8") when Type::SIGNATURE packet = read_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 > 67_108_864 @packet[sizeidx...sizeidx + 4] = [sz].pack("L") end # Align and allow for appending struct fields. def struct align(8) yield 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.is_a?(Fixnum) type = Type::Parser.new(type).parse[0] if type.is_a?(String) case type.sigtype when Type::BYTE @packet += val.chr when Type::UINT32, Type::UNIX_FD 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) @packet += if val [1].pack("L") else [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) && 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.is_a?(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 string is recieved and ay is expected, explode the string if val.is_a?(String) && type.child.sigtype == Type::BYTE val = val.bytes end if !val.is_a?(Enumerable) raise TypeException, "Expected an Enumerable of #{type.child.inspect} 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 !val.is_a?(Array) if type.sigtype == Type::DICT_ENTRY && 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.13.0/lib/dbus/xml.rb0000644000004100000410000001064013004252346016367 0ustar www-datawww-data# dbus/xml.rb - introspection parser, rexml/nokogiri abstraction # # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # Copyright (C) 2012 Geoff Youngs # # 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" begin require "nokogiri" rescue LoadError end module DBus # = 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 class << self attr_accessor :backend end # Creates a new parser for XML data in string _xml_. def initialize(xml) @xml = xml end class AbstractXML def self.have_nokogiri? Object.const_defined?("Nokogiri") end class Node def initialize(node) @node = node end # required methods # returns node attribute value def [](key) end # yields child nodes which match xpath of type AbstractXML::Node def each(xpath) end end # required methods # initialize parser with xml string def initialize(xml) end # yields nodes which match xpath of type AbstractXML::Node def each(xpath) end end class NokogiriParser < AbstractXML class NokogiriNode < AbstractXML::Node def [](key) @node[key] end def each(path, &block) @node.search(path).each { |node| block.call NokogiriNode.new(node) } end end def initialize(xml) @doc = Nokogiri.XML(xml) end def each(path, &block) @doc.search("//#{path}").each { |node| block.call NokogiriNode.new(node) } end end class REXMLParser < AbstractXML class REXMLNode < AbstractXML::Node def [](key) @node.attributes[key] end def each(path, &block) @node.elements.each(path) { |node| block.call REXMLNode.new(node) } end end def initialize(xml) @doc = REXML::Document.new(xml) end def each(path, &block) @doc.elements.each(path) { |node| block.call REXMLNode.new(node) } end end @backend = if AbstractXML.have_nokogiri? NokogiriParser else REXMLParser end # @return [Array(Array,Array)] # a pair: [list of Interfaces, list of direct subnode names] def parse # Using a Hash instead of a list helps merge split-up interfaces, # a quirk observed in ModemManager (I#41). interfaces = Hash.new do |hash, missing_key| hash[missing_key] = Interface.new(missing_key) end subnodes = [] t = Time.now d = IntrospectXMLParser.backend.new(@xml) d.each("node/node") do |e| subnodes << e["name"] end d.each("node/interface") do |e| i = interfaces[e["name"]] e.each("method") do |me| m = Method.new(me["name"]) parse_methsig(me, m) i << m end e.each("signal") do |se| s = Signal.new(se["name"]) parse_methsig(se, s) i << s end end d = Time.now - t if d > 2 DBus.logger.debug "Some XML took more that two secs to parse. Optimize me!" end [interfaces.values, subnodes] end ###################################################################### private # Parses a method signature XML element _e_ and initialises # method/signal _m_. def parse_methsig(e, m) e.each("arg") do |ae| name = ae["name"] dir = ae["direction"] sig = ae["type"] if m.is_a?(DBus::Signal) # Direction can only be "out", ignore it m.add_fparam(name, sig) elsif m.is_a?(DBus::Method) case dir # This is a method, so dir defaults to "in" when "in", nil m.add_fparam(name, sig) when "out" m.add_return(name, sig) end else raise NotImplementedError, dir end end end end # class IntrospectXMLParser end # module DBus ruby-dbus-0.13.0/lib/dbus/message_queue.rb0000644000004100000410000001140013004252346020412 0ustar www-datawww-data# This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # Copyright (C) 2009-2014 Martin Vidner # # 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 "fcntl" require "socket" module DBus class MessageQueue # The socket that is used to connect with the bus. attr_reader :socket def initialize(address) @address = address @buffer = "" @is_tcp = false connect end # TODO: failure modes # # If _non_block_ is true, return nil instead of waiting # EOFError may be raised def pop(non_block = false) buffer_from_socket_nonblock message = message_from_buffer_nonblock unless non_block # we can block while message.nil? r, _d, _d = IO.select([@socket]) if r && r[0] == @socket buffer_from_socket_nonblock message = message_from_buffer_nonblock end end end message end def push(message) @socket.write(message.marshall) end alias << push private # Connect to the bus and initialize the connection. def connect addresses = @address.split ";" # connect to first one that succeeds worked = addresses.find do |a| transport, keyvaluestring = a.split ":" kv_list = keyvaluestring.split "," kv_hash = {} kv_list.each do |kv| key, escaped_value = kv.split "=" value = escaped_value.gsub(/%(..)/) { |_m| [Regexp.last_match(1)].pack "H2" } kv_hash[key] = value end case transport when "unix" connect_to_unix kv_hash when "tcp" connect_to_tcp kv_hash when "launchd" connect_to_launchd 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") && 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 Exception => e puts "Oops:", e puts "Error: Could not establish connection to: #{@path}, will now exit." exit(1) # 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? sockaddr = if HOST_END == LIL_END "\1\0\0#{params["abstract"]}" else "\0\1\0#{params["abstract"]}" end elsif !params["path"].nil? sockaddr = Socket.pack_sockaddr_un(params["path"]) end @socket.connect(sockaddr) init_connection end def connect_to_launchd(params) socket_var = params["env"] socket = `launchctl getenv #{socket_var}`.chomp connect_to_unix "path" => socket end # Initialize the connection to the bus. def init_connection @client = Client.new(@socket) @client.authenticate end public # FIXME: fix Main loop instead # Get and remove one message from the buffer. # Return the message or nil. def message_from_buffer_nonblock return nil if @buffer.empty? ret = nil begin ret, size = Message.new.unmarshall_buffer(@buffer) @buffer.slice!(0, size) rescue IncompleteBufferException # fall through, let ret be null end ret end # The buffer size for messages. MSG_BUF_SIZE = 4096 # Fill (append) the buffer from data that might be available on the # socket. # EOFError may be raised def buffer_from_socket_nonblock @buffer += @socket.read_nonblock(MSG_BUF_SIZE) rescue EOFError raise # the caller expects it rescue Errno::EAGAIN # fine, would block 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 end end ruby-dbus-0.13.0/lib/dbus/proxy_object_factory.rb0000644000004100000410000000303513004252346022025 0ustar www-datawww-data# This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # Copyright (C) 2009-2014 Martin Vidner # # 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 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, api: ApiOptions::CURRENT) @xml = xml @bus = bus @path = path @dest = dest @api = api end # Investigates the sub-nodes of the proxy object _po_ based on the # introspection XML data _xml_ and sets them up recursively. def self.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, api: @api) ProxyObjectFactory.introspect_into(po, @xml) po end end # class ProxyObjectFactory end ruby-dbus-0.13.0/lib/dbus/logger.rb0000644000004100000410000000147713004252346017056 0ustar www-datawww-data# dbus/logger.rb - debug logging # # This file is part of the ruby-dbus project # Copyright (C) 2012 Martin Vidner # # 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 "logger" module DBus # Get the logger for the DBus module. # The default one logs to STDERR, # with DEBUG if $DEBUG is set, otherwise INFO. def logger unless defined? @logger @logger = Logger.new(STDERR) @logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO end @logger end module_function :logger # Set the logger for the DBus module def logger=(logger) @logger = logger end module_function :logger= end ruby-dbus-0.13.0/lib/dbus/proxy_object_interface.rb0000644000004100000410000001044513004252346022321 0ustar www-datawww-data# This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # Copyright (C) 2009-2014 Martin Vidner # # 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 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 = object @name = name @methods = {} @signals = {} end # Returns the string representation of the interface (the name). def to_str @name end # Defines a method on the interface from the Method descriptor _m_. def define_method_from_descriptor(m) m.params.each do |fpar| par = fpar.type # This is the signature validity check Type::Parser.new(par).parse end singleton_class.class_eval do define_method m.name do |*args, &reply_handler| if m.params.size != args.size raise ArgumentError, "wrong number of arguments (#{args.size} for #{m.params.size})" end 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 m.params.each do |fpar| par = fpar.type msg.add_param(par, args.shift) end ret = @object.bus.send_sync_or_async(msg, &reply_handler) if ret.nil? || @object.api.proxy_method_returns_array ret else m.rets.size == 1 ? ret.first : ret end end end @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.is_a?(Method) define_method_from_descriptor(m) elsif m.is_a?(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 # @overload on_signal(name, &block) # @overload on_signal(bus, name, &block) # Registers a handler (code block) for a signal with _name_ arriving # over the given _bus_. If no block is given, the signal is unregistered. # Note that specifying _bus_ is discouraged and the option is kept only for # backward compatibility. # @return [void] def on_signal(bus = @object.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".freeze # Read a property. # @param propname [String] def [](propname) ret = object[PROPERTY_INTERFACE].Get(name, propname) # this method always returns the single property if @object.api.proxy_method_returns_array ret[0] else ret end end # Write a property. # @param propname [String] # @param value [Object] def []=(propname, value) object[PROPERTY_INTERFACE].Set(name, propname, value) end # Read all properties at once, as a hash. # @return [Hash{String}] def all_properties ret = object[PROPERTY_INTERFACE].GetAll(name) # this method always returns the single property if @object.api.proxy_method_returns_array ret[0] else ret end end end # class ProxyObjectInterface end ruby-dbus-0.13.0/lib/dbus/auth.rb0000644000004100000410000002001013004252346016520 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. 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 # = Anonymous authentication class class Anonymous < Authenticator def authenticate "527562792044427573" # Hex encoded version of "Ruby DBus" 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. Process.uid.to_s.split(//).map { |d| d.ord.to_s(16) }.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 hex_encode(Etc.getlogin).to_s # server expects it to be binary end # returns the modules name def name "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 = 1.upto(s_challenge.bytesize / 2).map { rand(255).to_s }.join # Search cookie file for id path = File.join(ENV["HOME"], ".dbus-keyrings", context) DBus.logger.debug "path: #{path.inspect}" File.foreach(path) do |line| if line.index(id) == 0 # Right line of file, read cookie cookie = line.split(" ")[2].chomp DBus.logger.debug "cookie: #{cookie.inspect}" # 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, Anonymous] end # Start the authentication process. def authenticate if RbConfig::CONFIG["target_os"] =~ /freebsd/ @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 !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 raise AuthenticationFailed if @auth_list.empty? @authenticator = @auth_list.shift.new auth_msg = ["AUTH", @authenticator.name, @authenticator.authenticate] DBus.logger.debug "auth_msg: #{auth_msg.inspect}" send(auth_msg) rescue AuthenticationFailed @socket.close raise 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(" ") DBus.logger.debug "readline: #{readline.inspect}" readline end # # 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 # Try to reach the next state based on the current state. def next_state msg = next_msg if @state == :Starting DBus.logger.debug ":Starting msg: #{msg[0].inspect}" 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 DBus.logger.debug "state: #{@state}" case @state when :WaitingForData DBus.logger.debug ":WaitingForData msg: #{msg[0].inspect}" case msg[0] when "DATA" chall = msg[1] resp, chall = @authenticator.data(chall) DBus.logger.debug ":WaitingForData/DATA resp: #{resp.inspect}" 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 DBus.logger.debug ":WaitingForOk msg: #{msg[0].inspect}" 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 DBus.logger.debug ":WaitingForReject msg: #{msg[0].inspect}" case msg[0] when "REJECT" next_authenticator @state = :WaitingForOk else @socket.close return false end end true end # def next_state end # class Client end # module D-Bus ruby-dbus-0.13.0/lib/dbus/introspect.rb0000644000004100000410000001431613004252346017765 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. module DBus # Regular expressions that should match all method names. METHOD_SIGNAL_RE = /^[A-Za-z][A-Za-z0-9_]*$/ # Regular expressions that should match all interface names. INTERFACE_ELEMENT_RE = /^[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 = {} end # Validates a service _name_. def validate_name(name) raise InvalidIntrospectionData if name.bytesize > 255 raise InvalidIntrospectionData if name =~ /^\./ || name =~ /\.$/ raise InvalidIntrospectionData if name =~ /\.\./ raise InvalidIntrospectionData if !(name =~ /\./) name.split(".").each do |element| raise InvalidIntrospectionData if !(element =~ INTERFACE_ELEMENT_RE) end end # Helper method for defining a method _m_. def define(m) if m.is_a?(Method) @methods[m.name.to_sym] = m elsif m.is_a?(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 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) return if (name =~ METHOD_SIGNAL_RE) && (name.bytesize <= 255) raise InvalidMethodName, name end # Creates a new element with the given _name_. def initialize(name) validate_name(name.to_s) @name = name @params = [] 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 = [] 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 end # module DBus ruby-dbus-0.13.0/lib/dbus/message.rb0000644000004100000410000002025113004252346017212 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)".freeze # 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 = "" @@serial_mutex.synchronize do @serial = @@serial @@serial += 1 end @params = [] @destination = nil @interface = nil @error_name = nil @member = nil @path = nil @reply_serial = nil @flags = NO_REPLY_EXPECTED if mtype == METHOD_RETURN end def to_s "#{message_type} sender=#{sender} -> dest=#{destination} " \ "serial=#{serial} reply_serial=#{reply_serial} " \ "path=#{path}; interface=#{interface}; member=#{member} " \ "error_name=#{error_name}" 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.is_a?(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) headers = [] headers << [PATH, ["o", @path]] if @path headers << [INTERFACE, ["s", @interface]] if @interface headers << [MEMBER, ["s", @member]] if @member headers << [ERROR_NAME, ["s", @error_name]] if @error_name headers << [REPLY_SERIAL, ["u", @reply_serial]] if @reply_serial headers << [DESTINATION, ["s", @destination]] if @destination # SENDER is not sent, the message bus fills it in instead headers << [SIGNATURE, ["g", @signature]] if @signature != "" marshaller.append("a(yv)", headers) 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 [Array(Message,Integer)] # the detected message (self) and # the index pointer of the buffer where the message data ended. def unmarshall_buffer(buf) buf = buf.dup endianness = if buf[0] == "l" LIL_END else BIG_END end pu = PacketUnmarshaller.new(buf, endianness) mdata = pu.unmarshall(MESSAGE_SIGNATURE) _, @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 && @signature @params = pu.unmarshall(@signature, @body_length) end [self, pu.idx] end # Make a new exception from ex, mark it as being caused by this message # @api private def annotate_exception(ex) new_ex = ex.exception("#{ex}; caused by #{self}") new_ex.set_backtrace(ex.backtrace) new_ex 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 add_param(Type::STRING, description) unless description.nil? 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 = new(name, description) msg.add_param(DBus.type("as"), ex.backtrace) msg end end end # module DBus ruby-dbus-0.13.0/lib/dbus/api_options.rb0000644000004100000410000000121413004252346020110 0ustar www-datawww-data# This file is part of the ruby-dbus project # Copyright (C) 2016 Martin Vidner # # 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 class ApiOptions # https://github.com/mvidner/ruby-dbus/issues/30 attr_accessor :proxy_method_returns_array A0 = ApiOptions.new A0.proxy_method_returns_array = true A0.freeze A1 = ApiOptions.new A1.proxy_method_returns_array = false A1.freeze CURRENT = A1 end end ruby-dbus-0.13.0/lib/dbus/matchrule.rb0000644000004100000410000000621113004252346017552 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].freeze # 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 !["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 present_rules = FILTERS.select { |sym| method(sym).call } present_rules.map! { |sym| "#{sym}='#{method(sym).call}'" } present_rules.join(",") end # Parses a match rule string _s_ and sets the filters on the object. def from_s(str) str.split(",").each do |eq| next unless eq =~ /^(.*)='([^']*)'$/ # " name = Regexp.last_match(1) val = Regexp.last_match(2) raise MatchRuleException, name unless FILTERS.member?(name.to_sym) method(name + "=").call(val) 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 && @interface != msg.interface return false if @member && @member != msg.member return false if @path && @path != msg.path # FIXME: sender and destination are ignored true end end # class MatchRule end # module D-Bus ruby-dbus-0.13.0/lib/dbus/type.rb0000644000004100000410000001222313004252346016547 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 # Mapping from type number to name and alignment. TypeMapping = { 0 => ["INVALID", nil], "y" => ["BYTE", 1], "b" => ["BOOLEAN", 4], "n" => ["INT16", 2], "q" => ["UINT16", 2], "i" => ["INT32", 4], "u" => ["UINT32", 4], "x" => ["INT64", 8], "t" => ["UINT64", 8], "d" => ["DOUBLE", 8], "r" => ["STRUCT", 8], "a" => ["ARRAY", 4], "v" => ["VARIANT", 1], "o" => ["OBJECT_PATH", 4], "s" => ["STRING", 4], "g" => ["SIGNATURE", 1], "e" => ["DICT_ENTRY", 8], "h" => ["UNIX_FD", 4] }.freeze # Defines the set of constants TypeMapping.each_pair do |key, value| Type.const_set(value.first, key) end # 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 !TypeMapping.keys.member?(sigtype) raise SignatureException, "Unknown key in signature: #{sigtype.chr}" end @sigtype = sigtype @members = [] end # Return the required alignment for the type. def alignment TypeMapping[@sigtype].last end # Return a string representation of the type according to the # D-Bus specification. def to_s case @sigtype when STRUCT "(" + @members.collect(&:to_s).join + ")" when ARRAY "a" + child.to_s when DICT_ENTRY "{" + @members.collect(&:to_s).join + "}" else if !TypeMapping.keys.member?(@sigtype) raise NotImplementedError end @sigtype.chr end end # Add a new member type _a_. def <<(a) if ![STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype) raise SignatureException end raise SignatureException if @sigtype == ARRAY && !@members.empty? if @sigtype == DICT_ENTRY if @members.size == 2 raise SignatureException, "Dict entries have exactly two members" end if @members.empty? 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 = TypeMapping[@sigtype].first 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) c = nextchar raise SignatureException, "Parse error in #{@signature}" if c.nil? child = parse_one(c) res << child when "(" res = Type.new(STRUCT) while (c = nextchar) && c != ")" res << parse_one(c) end raise SignatureException, "Parse error in #{@signature}" if c.nil? when "{" res = Type.new(DICT_ENTRY) while (c = nextchar) && 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 = [] 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.13.0/lib/dbus/error.rb0000644000004100000410000000301713004252346016720 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 # @example raise a generic error # raise DBus.error, "message" # @example raise a specific error # 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.13.0/VERSION0000644000004100000410000000000713004252346014603 0ustar www-datawww-data0.13.0 ruby-dbus-0.13.0/README.md0000644000004100000410000000545613004252346015027 0ustar www-datawww-data# Ruby D-Bus [D-Bus](http://dbus.freedesktop.org) is an interprocess communication mechanism for Linux. Ruby D-Bus is a pure Ruby library for writing clients and services for D-Bus. [![Gem Version][GV img]][Gem Version] [![Build Status][BS img]][Build Status] [![Dependency Status][DS img]][Dependency Status] [![Code Climate][CC img]][Code Climate] [![Coverage Status][CS img]][Coverage Status] [Gem Version]: https://rubygems.org/gems/ruby-dbus [Build Status]: https://travis-ci.org/mvidner/ruby-dbus [travis pull requests]: https://travis-ci.org/mvidner/ruby-dbus/pull_requests [Dependency Status]: https://gemnasium.com/mvidner/ruby-dbus [Code Climate]: https://codeclimate.com/github/mvidner/ruby-dbus [Coverage Status]: https://coveralls.io/r/mvidner/ruby-dbus [GV img]: https://badge.fury.io/rb/ruby-dbus.png [BS img]: https://travis-ci.org/mvidner/ruby-dbus.png [DS img]: https://gemnasium.com/mvidner/ruby-dbus.png [CC img]: https://codeclimate.com/github/mvidner/ruby-dbus.png [CS img]: https://coveralls.io/repos/mvidner/ruby-dbus/badge.png?branch=master ## Example Check whether the system is on battery power via [UPower](http://upower.freedesktop.org/docs/UPower.html#UPower:OnBattery) require "dbus" sysbus = DBus.system_bus upower_service = sysbus["org.freedesktop.UPower"] upower_object = upower_service["/org/freedesktop/UPower"] upower_object.introspect upower_interface = upower_object["org.freedesktop.UPower"] on_battery = upower_interface["OnBattery"] if on_battery puts "The computer IS on battery power." else puts "The computer IS NOT on battery power." end ## Requirements - Ruby 2.0 or newer. ## Installation - `gem install ruby-dbus` ## Features 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.md or view it online on . ## 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.13.0/COPYING0000644000004100000410000006350413004252346014601 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!