ruby-dbus-0.25.0/0000755000004100000410000000000015000117217013534 5ustar www-datawww-dataruby-dbus-0.25.0/NEWS.md0000644000004100000410000005167115000117217014644 0ustar www-datawww-data# Ruby D-Bus NEWS ## Unreleased ## Ruby D-Bus 0.25.0 - 2025-04-03 Bug fixes: * Mention qualified property name in Get or Set errors ([#147][]). * Fix declaring logger and ostruct gems for Ruby 3.5 [#147]: https://github.com/mvidner/ruby-dbus/pull/147 ## Ruby D-Bus 0.24.0 - 2025-01-02 Bug fixes: * Adapted for Ruby 3.4, which uses a single quote instead of a backtick in exceptions ([#145][], by Mamoru TASAKA). [#145]: https://github.com/mvidner/ruby-dbus/pull/145 ## Ruby D-Bus 0.23.1 - 2023-10-03 API: * Add DBus::Object.dbus_reader_attr_accessor to declare a common use case with a single call ([#140][]). * BusConnection#request_name defaults to the simple use case: single owner without queuing, failing fast; documented the complex use cases. [#140]: https://github.com/mvidner/ruby-dbus/pull/140 ## Ruby D-Bus 0.23.0.beta2 - 2023-06-23 License: * clarified to be LGPL-2.1-or-later API: * DBus::Object#object_server replaces @service (which still works) and the short-lived @connection * ObjectServer#export will raise if the path is already taken by an object * ObjectServer#unexport now also accepts an object path * Connection#object_server can export objects even without requesting any service name ([#49][], in beta1 already). * Add PeerConnection for connections without a bus, useful for PulseAudio. Fix listening for signals there ([#44][]). * Moved from Connection to BusConnection: #unique_name, #proxy, #service. Call send_hello in BusConnection#initialize already. Bug fixes: * Fixed a refactoring crasher bug in ProxyService#introspect (oops). * Fix crash on #unexport of /child_of_root or even / [#44]: https://github.com/mvidner/ruby-dbus/issues/44 [#49]: https://github.com/mvidner/ruby-dbus/issues/49 ## Ruby D-Bus 0.23.0.beta1 - 2023-06-05 Bug fixes: * A service can now have more than one name ([#69][]). Connection#request_service is deprecated in favor of Connection#object_server and BusConnection#request_name [#69]: https://github.com/mvidner/ruby-dbus/issues/69 API: * Remove Service, splitting it into ProxyService and ObjectServer * Split off BusConnection from Connection * DBus::Object @service replaced by @connection ## Ruby D-Bus 0.22.1 - 2023-05-17 Bug fixes: * Fix OBS building by disabling IPv6 tests, [#134][]. [#134]: https://github.com/mvidner/ruby-dbus/pull/134 ## Ruby D-Bus 0.22.0 - 2023-05-08 Features: * Enable using nokogiri without rexml (by Dominik Andreas Schorpp, [#132][]) Bug fixes: * Respect DBUS_SYSTEM_BUS_ADDRESS environment variable. Other: * For NameRequestError, mention who is the other owner. * Session bus autolaunch still does not work, but: don't try launchd except on macOS, and improve the error message. * examples/gdbus split off to its own repository, https://github.com/mvidner/dbus-gui-gtk [#132]: https://github.com/mvidner/ruby-dbus/pull/132 ## Ruby D-Bus 0.21.0 - 2023-04-08 Features: * Respect env RUBY_DBUS_ENDIANNESS=B (or =l) for outgoing messages. Bug fixes: * Reduce socket buffer allocations ([#129][]). * Message#marshall speedup: don't marshall the body twice. [#129]: https://github.com/mvidner/ruby-dbus/pull/129 ## Ruby D-Bus 0.20.0 - 2023-03-21 Features: * For EXTERNAL authentication, try also without the user id, to work with containers ([#126][]). * Thread safety, as long as the non-main threads only send signals. [#126]: https://github.com/mvidner/ruby-dbus/issues/126 ## Ruby D-Bus 0.19.0 - 2023-01-18 API: * Added a ObjectManager mix-in to implement the service-side [ObjectManager][objmgr] interface. Bug fixes: * dbus_attr_accessor and friends validate the signature ([#120][]). * Declare the Introspectable interface in exported objects([#99][]). * Do reply with an error when calling a nonexisting object with an existing path prefix([#121][]). [#120]: https://github.com/mvidner/ruby-dbus/issues/120 [#99]: https://github.com/mvidner/ruby-dbus/issues/99 [#121]: https://github.com/mvidner/ruby-dbus/issues/121 [objmgr]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager ## Ruby D-Bus 0.18.1 - 2022-07-13 No changes since 0.18.0.beta8. Repeating the most important changes since 0.17.0: API: * Introduced DBus::Data classes, use them in Properties.Get, Properties.GetAll to return correct types as declared ([#97][]). * Introduced Object#dbus_properties_changed to send correctly typed property values ([#115][]). Avoid calling PropertiesChanged directly as it will guess the types. * Service side `emits_changed_signal` to control emission of PropertiesChanged: can be assigned within `dbus_interface` or as an option when declaring properties ([#117][]). * DBus.variant(type, value) is deprecated in favor of Data::Variant.new(value, member_type:) * Added type factories * Type::Array[type] * Type::Hash[key_type, value_type] * Type::Struct[type1, type2...] Bug fixes: * Fix Object.dbus_reader to work with attr_accessor and automatically produce dbus_properties_changed for properties that are read-write at implementation side and read-only at D-Bus side ([#96][]) * Service-side properties: Fix Properties.Get, Properties.GetAll to use the specific property signature, not the generic Variant ([#97][], [#105][], [#109][]). * Client-side properties: When calling Properties.Set in ProxyObjectInterface#[]=, use the correct type ([#108][]). * Added thorough tests (`spec/data/marshall.yaml`) to detect nearly all invalid data at unmarshalling time. Requirements: * Require Ruby 2.4, because of RuboCop 1.0. ## Ruby D-Bus 0.18.0.beta8 - 2022-06-21 Bug fixes: * Introduced Object#dbus_properties_changed to send correctly typed property values ([#115][]). Avoid calling PropertiesChanged directly as it will guess the types. * Fix Object.dbus_reader to work with attr_accessor and automatically produce dbus_properties_changed for properties that are read-write at implementation side and read-only at D-Bus side ([#96][]) [#96]: https://github.com/mvidner/ruby-dbus/issues/96 API: * Service side `emits_changed_signal` to control emission of PropertiesChanged: can be assigned within `dbus_interface` or as an option when declaring properties ([#117][]). [#115]: https://github.com/mvidner/ruby-dbus/issues/115 [#117]: https://github.com/mvidner/ruby-dbus/pull/117 ## Ruby D-Bus 0.18.0.beta7 - 2022-05-29 API: * DBus.variant(type, value) is deprecated in favor of Data::Variant.new(value, member_type:) Bug fixes: * Client-side properties: When calling Properties.Set in ProxyObjectInterface#[]=, use the correct type ([#108][]). [#108]: https://github.com/mvidner/ruby-dbus/issues/108 ## Ruby D-Bus 0.18.0.beta6 - 2022-05-25 API: * Data::Base#value returns plain Ruby types; Data::Container#exact_value contains Data::Base ([#114][]). * Data::Base#initialize and .from_typed allow plain or exact values, validate argument types. * Implement #== (converting) and #eql? (strict) for Data::Base and DBus::Type. [#114]: https://github.com/mvidner/ruby-dbus/pull/114 ## Ruby D-Bus 0.18.0.beta5 - 2022-04-27 API: * DBus::Type instances are frozen. * Data::Container classes (Array, Struct, DictEntry, but not Variant) constructors (#initialize, .from_items, .from_typed) changed to have a *type* argument instead of *member_type* or *member_types*. * Added type factories * Type::Array[type] * Type::Hash[key_type, value_type] * Type::Struct[type1, type2...] Bug fixes: * Properties containing Variants would return them doubly wrapped ([#111][]). [#111]: https://github.com/mvidner/ruby-dbus/pull/111 ## Ruby D-Bus 0.18.0.beta4 - 2022-04-21 Bug fixes: * Service-side properties: Fix Properties.Get, Properties.GetAll for properties that contain arrays, on other than outermost level ([#109][]). * Sending variants: fixed make_variant to correctly guess the signature for UInt64 and number-keyed hashes/dictionaries. [#109]: https://github.com/mvidner/ruby-dbus/pull/109 ## Ruby D-Bus 0.18.0.beta3 - 2022-04-10 Bug fixes: * Service-side properties: Fix Properties.Get, Properties.GetAll for Array, Dict, and Variant types ([#105][]). [#105]: https://github.com/mvidner/ruby-dbus/pull/105 ## Ruby D-Bus 0.18.0.beta2 - 2022-04-04 API: * Renamed the DBus::Type::Type class to DBus::Type (which was previously a module). * Introduced DBus::Data classes, use them in Properties.Get, Properties.GetAll to return correct types as declared (still [#97][]). Bug fixes: * Signature validation: Ensure DBus.type produces a valid Type * Detect more malformed messages: non-NUL padding bytes, variants with multiple or no value. * Added thorough tests (`spec/data/marshall.yaml`) to detect nearly all invalid data at unmarshalling time. ## Ruby D-Bus 0.18.0.beta1 - 2022-02-24 API: * D-Bus structs have been passed as Ruby arrays. Now these arrays are frozen. * Ruby structs can be used as D-Bus structs. Bug fixes: * Returning the value for o.fd.DBus.Properties.Get, use the specific property signature, not the generic Variant ([#97][]). Requirements: * Require Ruby 2.4, because of RuboCop 1.0. [#97]: https://github.com/mvidner/ruby-dbus/issues/97 ## Ruby D-Bus 0.17.0 - 2022-02-11 API: * Export properties with `dbus_attr_accessor`, `dbus_reader` etc. ([#86][]). Bug fixes: * Depend on rexml which is separate since Ruby 3.0 ([#87][], by Toshiaki Asai). Nokogiri is faster but bigger so it remains optional. * Fix connection in case ~/.dbus-keyrings has multiple cookies, showing as "Oops: undefined method `zero?' for nil:NilClass". * Add the missing name to the root introspection node. [#86]: https://github.com/mvidner/ruby-dbus/pull/86 [#87]: https://github.com/mvidner/ruby-dbus/pull/87 ## Ruby D-Bus 0.16.0 - 2019-10-15 API: * An invalid service name or an invalid object path will raise instead of being sent to the bus. The bus would then drop the connection, producing EOFError here ([#80][]). [#80]: https://github.com/mvidner/ruby-dbus/issues/80 ## Ruby D-Bus 0.15.0 - 2018-04-30 API: * Accessing an unknown interface will raise instead of returning nil ([#74][]). Bug fixes: * Fixed a conflict with activesupport 5.2 ([#71]) [#71]: https://github.com/mvidner/ruby-dbus/issues/71 [#74]: https://github.com/mvidner/ruby-dbus/pull/74 ## Ruby D-Bus 0.14.1 - 2018-01-05 Bug fixes: * Allow registering signal handlers while a signal is being handled ([#70][], Jan Biniok). [#70]: https://github.com/mvidner/ruby-dbus/pull/70 ## Ruby D-Bus 0.14.0 - 2017-10-13 Bug fixes: * Sending 16-bit signed integers ("n") did not work at all ([#68][]). Requirements: * Stopped supporting ruby 2.0.0, because of Nokogiri. [#68]: https://github.com/mvidner/ruby-dbus/issues/68 ## 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.suse.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.suse.com/show_bug.cgi?id=537401 [bsc#538050]: https://bugzilla.suse.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.25.0/doc/0000755000004100000410000000000015000117217014301 5ustar www-datawww-dataruby-dbus-0.25.0/doc/Tutorial.md0000644000004100000410000004160015000117217016427 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.25.0/doc/Reference.md0000644000004100000410000002450615000117217016530 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::BusConnection#[] get the screensaver service} 3. {DBus::ProxyService#[] 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::ProxyService#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 frozen 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. For exact representation of D-Bus data types, use subclasses of {DBus::Data::Base}, such as {DBus::Data::Int16} or {DBus::Data::UInt64}. ##### Variants If the signature expects a Variant (which is the case for all Properties!) then an explicit mechanism is needed. 1. Any {DBus::Data::Base}. 2. A {DBus::Data::Variant} made by {DBus.variant}(signature, value). (Formerly this produced the type+value pair below, now it is just an alias to the Variant constructor.) 3. A pair [{DBus::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])` 4. Other values are tried to fit one of these: Boolean, Double, Array of Variants, Hash of String keyed Variants, String, Int32, Int64. 5. **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 (4) but happen to have a particular string value in an array. ##### Structs If a **STRUCT** `(...)` is expected you may pass - an [Array](https://ruby-doc.org/core/Array.html) (frozen is fine) - a [Struct](https://ruby-doc.org/core/Struct.html) ##### 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/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 Similar to plain Ruby attributes, declared with - {https://docs.ruby-lang.org/en/3.1/Module.html#method-i-attr_accessor attr_accessor} - {https://docs.ruby-lang.org/en/3.1/Module.html#method-i-attr_reader attr_reader} - {https://docs.ruby-lang.org/en/3.1/Module.html#method-i-attr_writer attr_writer} These methods declare the attributes and export them as properties: - {DBus::Object.dbus_attr_accessor} - {DBus::Object.dbus_attr_reader} - {DBus::Object.dbus_attr_writer} For making properties out of Ruby methods (which are not attributes), use: - {DBus::Object.dbus_accessor} - {DBus::Object.dbus_reader} - {DBus::Object.dbus_writer} Note that the properties are declared in the Ruby naming convention with `snake_case` and D-Bus sees them `CamelCased`. Use the `dbus_name` argument for overriding this.   class Note < DBus::Object dbus_interface "net.vidner.Example.Properties" do # A read-write property "Title", # with `title` and `title=` accessing @title. dbus_attr_accessor :title, DBus::Type::STRING # A read-only property "Author" # (type specified via DBus signature) # with `author` reading `@author` dbus_attr_reader :author, "s" # A read-only property `Clock` def clock Time.now.to_s end dbus_reader :clock, "s" # Name mapping: `CreationTime` def creation_time "1993-01-01 00:00:00 +0100" end dbus_reader :creation_time, "s" dbus_attr_accessor :book_volume, DBus::Type::VARIANT, dbus_name: "Volume" end dbus_interface "net.vidner.Example.Audio" do dbus_attr_accessor :speaker_volume, DBus::Type::BYTE, dbus_name: "Volume" end # Must assign values because `nil` would crash our connection def initialize(opath) super @title = "Ahem" @author = "Martin" @book_volume = 1 @speaker_volume = 11 end end obj = Note.new("/net/vidner/Example/Properties") bus = DBus::SessionBus.instance bus.object_server.export(obj) bus.request_name("net.vidner.Example") main = DBus::Main.new main << bus main.run ### 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.25.0/lib/0000755000004100000410000000000015000117217014302 5ustar www-datawww-dataruby-dbus-0.25.0/lib/dbus.rb0000644000004100000410000000502315000117217015564 0ustar www-datawww-data# frozen_string_literal: true # 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. module DBus # Protocol character signifying big endianness. BIG_END = "B" # Protocol character signifying little endianness. LIL_END = "l" # Protocol character signifying the host's endianness. # "S": unpack as uint16, native endian HOST_END = { 1 => BIG_END, 256 => LIL_END }.fetch("\x00\x01".unpack1("S")) end # ^ That's because dbus/message needs HOST_END early require_relative "dbus/api_options" require_relative "dbus/auth" require_relative "dbus/bus" require_relative "dbus/bus_name" require_relative "dbus/connection" require_relative "dbus/data" require_relative "dbus/emits_changed_signal" require_relative "dbus/error" require_relative "dbus/introspect" require_relative "dbus/logger" require_relative "dbus/main" require_relative "dbus/marshall" require_relative "dbus/matchrule" require_relative "dbus/message" require_relative "dbus/message_queue" require_relative "dbus/node_tree" require_relative "dbus/object" require_relative "dbus/object_manager" require_relative "dbus/object_path" require_relative "dbus/object_server" require_relative "dbus/platform" require_relative "dbus/proxy_object" require_relative "dbus/proxy_object_factory" require_relative "dbus/proxy_object_interface" require_relative "dbus/proxy_service" require_relative "dbus/raw_message" require_relative "dbus/type" require_relative "dbus/xml" require "socket" # = D-Bus main module # # Module containing all the D-Bus modules and classes. module DBus # Comparing symbols is faster than strings # @return [:little,:big] HOST_ENDIANNESS = RawMessage.endianness(HOST_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 ruby-dbus-0.25.0/lib/dbus/0000755000004100000410000000000015000117217015237 5ustar www-datawww-dataruby-dbus-0.25.0/lib/dbus/proxy_service.rb0000644000004100000410000000641015000117217020466 0ustar www-datawww-data# frozen_string_literal: true # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # Copyright (C) 2023 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_relative "node_tree" module DBus # Used by clients to represent a named service on the other side of the bus. # # Formerly this class was intermixed with {ObjectServer} as Service. # # @example Usage # svc = DBus.system_bus["org.freedesktop.machine1"] # manager = svc["/org/freedesktop/machine1"] # p manager.ListImages class ProxyService < NodeTree # @return [BusName,nil] The service name. # Will be nil for a {PeerConnection} attr_reader :name # @return [Connection] The connection we're using. attr_reader :connection # @param connection [Connection] The connection we're using. def initialize(name, connection) @name = BusName.new(name) @connection = connection super() end # Determine whether the service name already exists. def exists? bus = connection # TODO: raise a better error if this is a peer connection 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_. # @param path [ObjectPath] # @return [ProxyObject] def [](path) object(path, api: ApiOptions::A1) end # Retrieves an object at the given _path_ # whose methods always return an array. # @param path [ObjectPath] # @param api [ApiOptions] # @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( @connection, @name, path, api: api ) end node.object end private # Perform a recursive retrospection on the given current _node_ # on the given _path_. def rec_introspect(node, path) xml = connection.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, @connection, @name, path).build end end # A hack for pretending that a {PeerConnection} has a single unnamed {ProxyService} # so that we can get {ProxyObject}s from it. class ProxyPeerService < ProxyService # @param connection [Connection] The peer connection we're using. def initialize(connection) # this way we disallow ProxyService taking a nil name by accident super(":0.0", connection) @name = nil end end end ruby-dbus-0.25.0/lib/dbus/proxy_object_factory.rb0000644000004100000410000000333015000117217022021 0ustar www-datawww-data# frozen_string_literal: true # 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 _pobj_ based on the # introspection XML data _xml_ and sets them up recursively. # @param pobj [ProxyObject] # @param xml [String] def self.introspect_into(pobj, xml) # intfs [Array], subnodes [Array] intfs, pobj.subnodes = IntrospectXMLParser.new(xml).parse intfs.each do |i| poi = ProxyObjectInterface.new(pobj, i.name) i.methods.each_value { |m| poi.define(m) } i.signals.each_value { |s| poi.define(s) } i.properties.each_value { |p| poi.define(p) } pobj[i.name] = poi end pobj.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 end ruby-dbus-0.25.0/lib/dbus/object_server.rb0000644000004100000410000001053315000117217020422 0ustar www-datawww-data# frozen_string_literal: true # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # Copyright (C) 2023 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_relative "node_tree" module DBus # The part of a {Connection} that can export {DBus::Object}s to provide # services to clients. # # Note that an ObjectServer does not have a name. Typically a {Connection} # has one well known name, but can have none or more. # # Formerly this class was intermixed with {ProxyService} as Service. # # @example Usage # bus = DBus.session_bus # obj = DBus::Object.new("/path") # a subclass more likely # bus.object_server.export(obj) # bus.request_name("org.example.Test") class ObjectServer < NodeTree # @return [Connection] The connection we're using. attr_reader :connection def initialize(connection) @connection = connection super() end # Retrieves an object at the given _path_ # @param path [ObjectPath] # @return [DBus::Object,nil] def object(path) node = get_node(path, create: false) node&.object end alias [] object # Export an object # @param obj [DBus::Object] # @raise RuntimeError if there's already an exported object at the same path def export(obj) node = get_node(obj.path, create: true) raise "At #{obj.path} there is already an object #{node.object.inspect}" if node.object node.object = obj obj.object_server = self object_manager_for(obj)&.object_added(obj) end # Undo exporting an object *obj_or_path*. # Raises ArgumentError if it is not a DBus::Object. # Returns the object, or false if _obj_ was not exported. # @param obj_or_path [DBus::Object,ObjectPath,String] an object or a valid object path def unexport(obj_or_path) path = self.class.path_of(obj_or_path) parent_path, _separator, node_name = path.rpartition("/") parent_node = get_node(parent_path, create: false) return false unless parent_node node = if node_name == "" # path == "/" parent_node else parent_node[node_name] end obj = node&.object raise ArgumentError, "Cannot unexport, no object at #{path}" unless obj object_manager_for(obj)&.object_removed(obj) obj.object_server = nil node.object = nil # node can be deleted if # - it has no children # - it is not root if node.empty? && !node.equal?(parent_node) parent_node.delete(node_name) end obj end # Find the (closest) parent of *object* # implementing the ObjectManager interface, or nil # @return [DBus::Object,nil] def object_manager_for(object) path = object.path node_chain = get_node_chain(path) om_node = node_chain.reverse_each.find do |node| node.object&.is_a? DBus::ObjectManager end om_node&.object end # All objects (not paths) under this path (except itself). # @param path [ObjectPath] # @return [Array] # @raise ArgumentError if the *path* does not exist def descendants_for(path) node = get_node(path, create: false) raise ArgumentError, "Object path #{path} doesn't exist" if node.nil? node.descendant_objects end # @param obj_or_path [DBus::Object,ObjectPath,String] an object or a valid object path # @return [ObjectPath] # @api private def self.path_of(obj_or_path) case obj_or_path when ObjectPath obj_or_path when String ObjectPath.new(obj_or_path) when DBus::Object obj_or_path.path else raise ArgumentError, "Expecting a DBus::Object argument or DBus::ObjectPath or String which parses as one" end end ######### private ######### # @param path [ObjectPath] a path that must exist # @return [Array] nodes from the root to the leaf def get_node_chain(path) n = @root result = [n] path.sub(%r{^/}, "").split("/").each do |elem| n = n[elem] result.push(n) end result end end end ruby-dbus-0.25.0/lib/dbus/logger.rb0000644000004100000410000000163315000117217017046 0ustar www-datawww-data# frozen_string_literal: true # 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 if !defined?(@logger) || @logger.nil? debug = $DEBUG || ENV["RUBY_DBUS_DEBUG"] @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.25.0/lib/dbus/introspect.rb0000644000004100000410000002205315000117217017760 0ustar www-datawww-data# frozen_string_literal: true # 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_]*$/.freeze # Regular expressions that should match all interface names. INTERFACE_ELEMENT_RE = /^[A-Za-z][A-Za-z0-9_]*$/.freeze # 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 # @return [String] The name of the interface. attr_reader :name # @return [Hash{Symbol => DBus::Method}] The methods that are part of the interface. attr_reader :methods # @return [Hash{Symbol => Signal}] The signals that are part of the interface. attr_reader :signals # @return [Hash{Symbol => Property}] attr_reader :properties # @return [EmitsChangedSignal] attr_reader :emits_changed_signal # Creates a new interface with a given _name_. def initialize(name) validate_name(name) @name = name @methods = {} @signals = {} @properties = {} @emits_changed_signal = EmitsChangedSignal::DEFAULT_ECS end # Helper for {Object.emits_changed_signal=}. # @api private def emits_changed_signal=(ecs) raise TypeError unless ecs.is_a? EmitsChangedSignal # equal?: object identity unless @emits_changed_signal.equal?(EmitsChangedSignal::DEFAULT_ECS) || @emits_changed_signal.value == ecs.value raise "emits_change_signal was assigned more than once" end @emits_changed_signal = ecs 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 # Add _ifc_el_ as a known {Method}, {Signal} or {Property} # @param ifc_el [InterfaceElement] def define(ifc_el) name = ifc_el.name.to_sym category = case ifc_el when Method @methods when Signal @signals when Property @properties end category[name] = ifc_el end alias declare define alias << define # Defines a method with name _id_ and a given _prototype_ in the # interface. # Better name: declare_method def define_method(id, prototype) m = Method.new(id) m.from_prototype(prototype) define(m) end alias declare_method define_method # Return introspection XML string representation of the property. # @return [String] def to_xml xml = " \n" xml += emits_changed_signal.to_xml methods.each_value { |m| xml += m.to_xml } signals.each_value { |m| xml += m.to_xml } properties.each_value { |m| xml += m.to_xml } xml += " \n" xml end end # = A formal parameter has a name and a type class FormalParameter # @return [#to_s] attr_reader :name # @return [SingleCompleteType] 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 # @return [Symbol] The name of the interface element attr_reader :name # @return [Array] The parameters of the interface element 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 # = D-Bus interface method class # # This is a class representing methods that are part of an interface. class Method < InterfaceElement # @return [Array] The list of return values for the method 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_. # @param name [#to_s] # @param signature [SingleCompleteType] def add_return(name, signature) @rets << FormalParameter.new(name, signature) end # Add parameter types by parsing the given _prototype_. # @param prototype [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. # @return [String] 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 # = 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 # An (exported) property # https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties class Property # @return [Symbol] The name of the property, for example FooBar. attr_reader :name # @return [Type] attr_reader :type # @return [Symbol] :read :write or :readwrite attr_reader :access # @return [Symbol,nil] What to call at Ruby side. # (Always without the trailing `=`) # It is `nil` IFF representing a client-side proxy. attr_reader :ruby_name def initialize(name, type, access, ruby_name:) @name = name.to_sym type = DBus.type(type) unless type.is_a?(Type) @type = type @access = access @ruby_name = ruby_name end # @return [Boolean] def readable? access == :read || access == :readwrite end # @return [Boolean] def writable? access == :write || access == :readwrite end # Return introspection XML string representation of the property. def to_xml " \n" end # @param xml_node [AbstractXML::Node] # @return [Property] def self.from_xml(xml_node) name = xml_node["name"].to_sym type = xml_node["type"] access = xml_node["access"].to_sym new(name, type, access, ruby_name: nil) end end end ruby-dbus-0.25.0/lib/dbus/proxy_object_interface.rb0000644000004100000410000001250315000117217022314 0ustar www-datawww-data# frozen_string_literal: true # 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 # @return [Hash{String => DBus::Method}] attr_reader :methods # @return [Hash{String => Signal}] attr_reader :signals # @return [Hash{Symbol => Property}] attr_reader :properties # @return [ProxyObject] The proxy object to which this interface belongs. attr_reader :object # @return [String] 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 = {} @properties = {} 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 _method_. # @param method [Method] def define_method_from_descriptor(method) method.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 method.name do |*args, &reply_handler| if method.params.size != args.size raise ArgumentError, "wrong number of arguments (#{args.size} for #{method.params.size})" end msg = Message.new(Message::METHOD_CALL) msg.path = @object.path msg.interface = @name msg.destination = @object.destination msg.member = method.name msg.sender = @object.bus.unique_name method.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 method.rets.size == 1 ? ret.first : ret end end end @methods[method.name] = method end # Defines a signal from the descriptor _sig_. # @param sig [Signal] def define_signal_from_descriptor(sig) @signals[sig.name] = sig end # @param prop [Property] def define_property_from_descriptor(prop) @properties[prop.name] = prop end # Defines a signal or method based on the descriptor _ifc_el_. # @param ifc_el [DBus::Method,Signal,Property] def define(ifc_el) case ifc_el when Method define_method_from_descriptor(ifc_el) when Signal define_signal_from_descriptor(ifc_el) when Property define_property_from_descriptor(ifc_el) 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" # 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 property_name [String] # @param value [Object] def []=(property_name, value) property = properties[property_name.to_sym] if !property raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"), "Property '#{name}.#{property_name}' (on object '#{object.path}') not found" end case value # accommodate former need to explicitly make a variant with the right type when Data::Variant variant = value else type = property.type typed_value = Data.make_typed(type, value) variant = Data::Variant.new(typed_value, member_type: type) end object[PROPERTY_INTERFACE].Set(name, property_name, variant) 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 end ruby-dbus-0.25.0/lib/dbus/platform.rb0000644000004100000410000000114615000117217017412 0ustar www-datawww-data# frozen_string_literal: true # This file is part of the ruby-dbus project # Copyright (C) 2023 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 "rbconfig" module DBus # Platform detection module Platform module_function def freebsd? RbConfig::CONFIG["target_os"] =~ /freebsd/ end def macos? RbConfig::CONFIG["target_os"] =~ /darwin/ end end end ruby-dbus-0.25.0/lib/dbus/object_path.rb0000644000004100000410000000160615000117217020051 0ustar www-datawww-data# frozen_string_literal: true # This file is part of the ruby-dbus project # Copyright (C) 2019 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 # A {::String} that validates at initialization time. # See also {DBus::Data::ObjectPath} # @see https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path class ObjectPath < String # @raise Error if not a valid object path def initialize(str) unless self.class.valid?(str) raise DBus::Error, "Invalid object path #{str.inspect}" end super end def self.valid?(str) str == "/" || str =~ %r{\A(/[A-Za-z0-9_]+)+\z} end end end ruby-dbus-0.25.0/lib/dbus/raw_message.rb0000644000004100000410000000504115000117217020061 0ustar www-datawww-data# frozen_string_literal: true # This file is part of the ruby-dbus project # Copyright (C) 2022 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 # A message while it is being parsed: a binary string, # with a position cursor (*pos*), and an *endianness* tag. class RawMessage # @return [String] # attr_reader :bytes # @return [Integer] position in the byte buffer attr_reader :pos # @return [:little,:big] attr_reader :endianness # @param bytes [String] # @param endianness [:little,:big,nil] # if not given, read the 1st byte of *bytes* def initialize(bytes, endianness = nil) @bytes = bytes @pos = 0 @endianness = endianness || self.class.endianness(@bytes[0]) end # Get the endiannes switch as a Symbol, # which will make using it slightly more efficient # @param tag_char [String] # @return [:little,:big] def self.endianness(tag_char) case tag_char when LIL_END :little when BIG_END :big else raise InvalidPacketException, "Incorrect endianness #{tag_char.inspect}" end end # @return [void] # @raise IncompleteBufferException if there are not enough bytes remaining def want!(size) raise IncompleteBufferException if @pos + size > @bytes.bytesize end # @return [String] # @raise IncompleteBufferException if there are not enough bytes remaining # TODO: stress test this with encodings. always binary? def read(size) want!(size) ret = @bytes.slice(@pos, size) @pos += size ret end # @return [String] # @api private def remaining_bytes # This returns "" if pos is just past the end of the string, # and nil if it is further. @bytes[@pos..-1] end # Align the *pos* index on a multiple of *alignment* # @param alignment [Integer] must be 1, 2, 4 or 8 # @return [void] def align(alignment) case alignment when 1 nil when 2, 4, 8 bits = alignment - 1 pad_size = ((@pos + bits) & ~bits) - @pos pad = read(pad_size) unless pad.bytes.all?(&:zero?) raise InvalidPacketException, "Alignment bytes are not NUL" end else raise ArgumentError, "Unsupported alignment #{alignment}" end end end end ruby-dbus-0.25.0/lib/dbus/error.rb0000644000004100000410000000302215000117217016712 0ustar www-datawww-data# frozen_string_literal: true # 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 # @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 ruby-dbus-0.25.0/lib/dbus/xml.rb0000644000004100000410000001134415000117217016367 0ustar www-datawww-data# frozen_string_literal: true # 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. # Our gemspec says rexml is needed and nokogiri is optional # but in fact either will do begin require "nokogiri" rescue LoadError begin require "rexml/document" rescue LoadError raise LoadError, "cannot load nokogiri OR rexml/document" end 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_. # @param xml [String] def initialize(xml) @xml = xml end class AbstractXML # @!method initialize(xml) # @abstract # @!method each(xpath) # @abstract # yields nodes which match xpath of type AbstractXML::Node 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 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) super() @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) super() @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 e.each("property") do |pe| p = Property.from_xml(pe) i << p 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 *elem* and initialises # method/signal *methsig*. # @param elem [AbstractXML::Node] def parse_methsig(elem, methsig) elem.each("arg") do |ae| name = ae["name"] dir = ae["direction"] sig = ae["type"] case methsig when DBus::Signal # Direction can only be "out", ignore it methsig.add_fparam(name, sig) when DBus::Method case dir # This is a method, so dir defaults to "in" when "in", nil methsig.add_fparam(name, sig) when "out" methsig.add_return(name, sig) end else raise NotImplementedError, dir end end end end end ruby-dbus-0.25.0/lib/dbus/marshall.rb0000644000004100000410000003035215000117217017372 0ustar www-datawww-data# frozen_string_literal: true # dbus.rb - Module containing the low-level D-Bus implementation # # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. require "socket" require_relative "../dbus/type" # = 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 #{::Object}s (in **plain** mode) or to {Data::Base} (in **exact** mode) # # Spelling note: this codebase always uses a double L # in the "marshall" word and its inflections. class PacketUnmarshaller # Create a new unmarshaller for the given data *buffer*. # @param buffer [String] # @param endianness [:little,:big] def initialize(buffer, endianness) # TODO: this dup can be avoided if we can prove # that an IncompleteBufferException leaves the original *buffer* intact buffer = buffer.dup @raw_msg = RawMessage.new(buffer, endianness) end # Unmarshall the buffer for a given _signature_ and length _len_. # Return an array of unmarshalled objects. # @param signature [Signature] # @param len [Integer,nil] if given, and there is not enough data # in the buffer, raise {IncompleteBufferException} # @param mode [:plain,:exact] # @return [Array<::Object,DBus::Data::Base>] # Objects in `:plain` mode, {DBus::Data::Base} in `:exact` mode # The array size corresponds to the number of types in *signature*. # @raise IncompleteBufferException # @raise InvalidPacketException def unmarshall(signature, len = nil, mode: :plain) @raw_msg.want!(len) if len sigtree = Type::Parser.new(signature).parse ret = [] sigtree.each do |elem| ret << do_parse(elem, mode: mode) end ret end # after the headers, the body starts 8-aligned def align_body @raw_msg.align(8) end # @return [Integer] def consumed_size @raw_msg.pos end private # @param data_class [Class] a subclass of Data::Base (specific?) # @return [::Integer,::Float] def aligned_read_value(data_class) @raw_msg.align(data_class.alignment) bytes = @raw_msg.read(data_class.alignment) bytes.unpack1(data_class.format[@raw_msg.endianness]) end # Based on the _signature_ type, retrieve a packet from the buffer # and return it. # @param signature [Type] # @param mode [:plain,:exact] # @return [Data::Base] def do_parse(signature, mode: :plain) # FIXME: better naming for packet vs value packet = nil data_class = Data::BY_TYPE_CODE[signature.sigtype] if data_class.nil? raise NotImplementedError, "sigtype: #{signature.sigtype} (#{signature.sigtype.chr})" end if data_class.fixed? value = aligned_read_value(data_class) packet = data_class.from_raw(value, mode: mode) elsif data_class.basic? size = aligned_read_value(data_class.size_class) # @raw_msg.align(data_class.alignment) # ^ is not necessary because we've just read a suitably-aligned *size* value = @raw_msg.read(size) nul = @raw_msg.read(1) if nul != "\u0000" raise InvalidPacketException, "#{data_class} is not NUL-terminated" end packet = data_class.from_raw(value, mode: mode) else @raw_msg.align(data_class.alignment) case signature.sigtype when Type::STRUCT, Type::DICT_ENTRY values = signature.members.map do |child_sig| do_parse(child_sig, mode: mode) end packet = data_class.from_items(values, mode: mode, type: signature) when Type::VARIANT data_sig = do_parse(Data::Signature.type, mode: :exact) # -> Data::Signature types = Type::Parser.new(data_sig.value).parse # -> Array unless types.size == 1 raise InvalidPacketException, "VARIANT must contain 1 value, #{types.size} found" end type = types.first value = do_parse(type, mode: mode) packet = data_class.from_items(value, mode: mode, member_type: type) when Type::ARRAY array_bytes = aligned_read_value(Data::UInt32) if array_bytes > 67_108_864 raise InvalidPacketException, "ARRAY body longer than 64MiB" end # needed here because of empty arrays @raw_msg.align(signature.child.alignment) items = [] end_pos = @raw_msg.pos + array_bytes while @raw_msg.pos < end_pos item = do_parse(signature.child, mode: mode) items << item end is_hash = signature.child.sigtype == Type::DICT_ENTRY packet = data_class.from_items(items, mode: mode, type: signature, hash: is_hash) end end packet end end # 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 # @return [String] attr_reader :packet # @return [:little,:big] attr_reader :endianness # Create a new marshaller, setting the current packet to the # empty packet. def initialize(offset = 0, endianness: HOST_ENDIANNESS) @endianness = endianness @packet = "" @offset = offset # for correct alignment of nested marshallers end # Round _num_ up to the specified power of two, _alignment_ def num_align(num, alignment) case alignment when 1, 2, 4, 8 bits = alignment - 1 (num + bits) & ~bits else raise ArgumentError, "Unsupported alignment #{alignment}" end end # Align the buffer with NULL (\0) bytes on a byte length of _alignment_. def align(alignment) pad_count = num_align(@offset + @packet.bytesize, alignment) - @offset @packet = @packet.ljust(pad_count, 0.chr) 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 sz_data = Data::UInt32.new(sz) @packet[sizeidx...sizeidx + 4] = sz_data.marshall(endianness) 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 # # @param type [SingleCompleteType] (or Integer or {Type}) # @param val [::Object] def append(type, val) raise TypeException, "Cannot send nil" if val.nil? type = type.chr if type.is_a?(Integer) type = Type::Parser.new(type).parse[0] if type.is_a?(String) # type is [Type] now data_class = Data::BY_TYPE_CODE[type.sigtype] if data_class.nil? raise NotImplementedError, "sigtype: #{type.sigtype} (#{type.sigtype.chr})" end if data_class.fixed? align(data_class.alignment) data = data_class.new(val) @packet += data.marshall(endianness) elsif data_class.basic? val = val.value if val.is_a?(Data::Basic) align(data_class.size_class.alignment) size_data = data_class.size_class.new(val.bytesize) @packet += size_data.marshall(endianness) # Z* makes a binary string, as opposed to interpolation @packet += [val].pack("Z*") else case type.sigtype when Type::VARIANT append_variant(val) when Type::ARRAY val = val.exact_value if val.is_a?(Data::Array) append_array(type.child, val) when Type::STRUCT, Type::DICT_ENTRY val = val.exact_value if val.is_a?(Data::Struct) || val.is_a?(Data::DictEntry) unless val.is_a?(Array) || val.is_a?(Struct) type_name = Type::TYPE_MAPPING[type.sigtype].first raise TypeException, "#{type_name} expects an Array or Struct, seen #{val.class}" end if type.sigtype == Type::DICT_ENTRY && val.size != 2 raise TypeException, "DICT_ENTRY expects a pair" end if type.members.size != val.size type_name = Type::TYPE_MAPPING[type.sigtype].first raise TypeException, "#{type_name} 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 end def append_variant(val) vartype = nil if val.is_a?(DBus::Data::Variant) vartype = val.member_type vardata = val.exact_value elsif val.is_a?(DBus::Data::Container) vartype = val.type vardata = val.exact_value elsif val.is_a?(DBus::Data::Base) vartype = val.type vardata = val.value elsif val.is_a?(Array) && val.size == 2 case val[0] when Type vartype, vardata = val # Ambiguous but easy to use, because Type # cannot construct "as" "a{sv}" easily when 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(Data::Signature.type, vartype.to_s) align(vartype.alignment) sub = PacketMarshaller.new(@offset + @packet.bytesize, endianness: endianness) sub.append(vartype, vardata) @packet += sub.packet end # @param child_type [Type] def append_array(child_type, val) if val.is_a?(Hash) raise TypeException, "Expected an Array but got a Hash" if child_type.sigtype != Type::DICT_ENTRY # Damn ruby rocks here val = val.to_a end # If string is received and ay is expected, explode the string if val.is_a?(String) && child_type.sigtype == Type::BYTE val = val.bytes end if !val.is_a?(Enumerable) raise TypeException, "Expected an Enumerable of #{child_type.inspect} but got a #{val.class}" end array(child_type) do val.each do |elem| append(child_type, elem) end end end # 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]) } key_type = if value.empty? "s" else t, = make_variant(value.first.first) t end ["a{#{key_type}v}", h] elsif value.respond_to? :to_str ["s", value.to_str] elsif value.respond_to? :to_int i = value.to_int if Data::Int32.range.cover?(i) ["i", i] elsif Data::Int64.range.cover?(i) ["x", i] else ["t", i] end end end end end ruby-dbus-0.25.0/lib/dbus/emits_changed_signal.rb0000644000004100000410000000506615000117217021722 0ustar www-datawww-data# frozen_string_literal: true # This file is part of the ruby-dbus project # Copyright (C) 2022 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 # Describes the behavior of PropertiesChanged signal, for a single property # or for an entire interface. # # The possible values are: # # - *true*: the signal is emitted with the value included. # - *:invalidates*: the signal is emitted but the value is not included # in the signal. # - *:const*: the property never changes value during the lifetime # of the object it belongs to, and hence the signal # is never emitted for it (but clients can cache the value) # - *false*: the signal won't be emitted (clients should re-Get the property value) # # The default is: # - for an interface: *true* # - for a property: what the parent interface specifies # # @see DBus::Object.emits_changed_signal # @see DBus::Object.dbus_attr_accessor # @see https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format # # Immutable once constructed. class EmitsChangedSignal # @return [true,false,:const,:invalidates] attr_reader :value # @param value [true,false,:const,:invalidates,nil] # See class-level description above, {EmitsChangedSignal}. # @param interface [Interface,nil] # If the (property-level) *value* is unspecified (nil), this is the # containing {Interface} to get the value from. def initialize(value, interface: nil) if value.nil? raise ArgumentError, "Both arguments are nil" if interface.nil? @value = interface.emits_changed_signal.value else expecting = [true, false, :const, :invalidates] unless expecting.include?(value) raise ArgumentError, "Expecting one of #{expecting.inspect}. Seen #{value.inspect}" end @value = value end freeze end # Return introspection XML string representation # @return [String] def to_xml return "" if @value == true " \n" end def to_s @value.to_s end def ==(other) if other.is_a?(self.class) other.value == @value else other == value end end alias eql? == DEFAULT_ECS = EmitsChangedSignal.new(true) end end ruby-dbus-0.25.0/lib/dbus/object.rb0000644000004100000410000005211715000117217017040 0ustar www-datawww-data# frozen_string_literal: true # 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 "core_ext/class/attribute" module DBus PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties" # 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 # @return [ObjectPath] The path of the object. attr_reader :path # The interfaces that the object supports. Hash: String => Interface my_class_attribute :intfs self.intfs = {} @@cur_intf = nil # Interface @@intfs_mutex = Mutex.new # Create a new object with a given _path_. # Use ObjectServer#export to export it. # @param path [ObjectPath] The path of the object. def initialize(path) @path = path # TODO: what parts of our API are supposed to work before we're exported? self.object_server = nil end # @return [ObjectServer] the server the object is exported by def object_server # tests may mock the old ivar @object_server || @service end # @param server [ObjectServer] the server the object is exported by # @note only the server itself should call this in its #export/#unexport def object_server=(server) # until v0.22.1 there was attr_writer :service # so subclasses only could use @service @object_server = @service = server end # Dispatch a message _msg_ to call exported methods # @param msg [Message] only METHOD_CALLS do something # @api private def dispatch(msg) case msg.message_type when Message::METHOD_CALL reply = nil begin iface = intfs[msg.interface] if !iface raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"), "Interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist" end member_sym = msg.member.to_sym meth = iface.methods[member_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) rsigs = meth.rets.map(&:type) rsigs.zip(retdata).each do |rsig, rdata| reply.add_param(rsig, rdata) end rescue StandardError => e dbus_msg_exc = msg.annotate_exception(e) reply = ErrorMessage.from_exception(dbus_msg_exc).reply_to(msg) end # TODO: this method chain is too long, # we should probably just return reply [Message] like we get a [Message] object_server.connection.message_queue.push(reply) end end # Select (and create) the interface that the following defined methods # belong to. # @param name [String] interface name like "org.example.ManagerManager" # @see https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-interface def self.dbus_interface(name) @@intfs_mutex.synchronize do @@cur_intf = intfs[name] if !@@cur_intf @@cur_intf = Interface.new(name) # validates the name # As this is a mutable class_attr, we cannot use # self.intfs[name] = @@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(name => @@cur_intf) end begin yield ensure @@cur_intf = nil end end end # Forgetting to declare the interface for a method/signal/property # is a ScriptError. class UndefinedInterface < ScriptError def initialize(sym) super "No interface specified for #{sym}. Enclose it in dbus_interface." end end # Declare the behavior of PropertiesChanged signal, # common for all properties in this interface # (individual properties may override it) # @example # self.emits_changed_signal = :invalidates # @param [true,false,:const,:invalidates] value def self.emits_changed_signal=(value) raise UndefinedInterface, :emits_changed_signal if @@cur_intf.nil? @@cur_intf.emits_changed_signal = EmitsChangedSignal.new(value) end # A read-write property accessing an instance variable. # A combination of `attr_accessor` and {.dbus_accessor}. # # PropertiesChanged signal will be emitted whenever `foo_bar=` is used # but not when @foo_bar is written directly. # # @param ruby_name [Symbol] :foo_bar is exposed as FooBar; # use dbus_name to override # @param type [Type,SingleCompleteType] # a signature like "s" or "a(uus)" or Type::STRING # @param dbus_name [String] if not given it is made # by CamelCasing the ruby_name. foo_bar becomes FooBar # to convert the Ruby convention to the DBus convention. # @param emits_changed_signal [true,false,:const,:invalidates] # see {EmitsChangedSignal}; if unspecified, ask the interface. # @return [void] def self.dbus_attr_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) attr_accessor(ruby_name) dbus_accessor(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end # A read-only property accessing a read-write instance variable. # A combination of `attr_accessor` and {.dbus_reader}. # # @param (see .dbus_attr_accessor) # @return (see .dbus_attr_accessor) def self.dbus_reader_attr_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) attr_accessor(ruby_name) dbus_reader(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end # A read-only property accessing an instance variable. # A combination of `attr_reader` and {.dbus_reader}. # # You may be instead looking for a variant which is read-write from the Ruby side: # {.dbus_reader_attr_accessor}. # # Whenever the property value gets changed from "inside" the object, # you should emit the `PropertiesChanged` signal by calling # {#dbus_properties_changed}. # # dbus_properties_changed(interface_name, {dbus_name.to_s => value}, []) # # or, omitting the value in the signal, # # dbus_properties_changed(interface_name, {}, [dbus_name.to_s]) # # @param (see .dbus_attr_accessor) # @return (see .dbus_attr_accessor) def self.dbus_attr_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) attr_reader(ruby_name) dbus_reader(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end # A write-only property accessing an instance variable. # A combination of `attr_writer` and {.dbus_writer}. # # @param (see .dbus_attr_accessor) # @return (see .dbus_attr_accessor) def self.dbus_attr_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) attr_writer(ruby_name) dbus_writer(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end # A read-write property using a pair of reader/writer methods # (which must already exist). # (To directly access an instance variable, use {.dbus_attr_accessor} instead) # # Uses {.dbus_watcher} to set up the PropertiesChanged signal. # # @param (see .dbus_attr_accessor) # @return (see .dbus_attr_accessor) def self.dbus_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) raise UndefinedInterface, ruby_name if @@cur_intf.nil? dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name) property = Property.new(dbus_name, type, :readwrite, ruby_name: ruby_name) @@cur_intf.define(property) dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end # A read-only property accessing a reader method (which must already exist). # (To directly access an instance variable, use {.dbus_attr_reader} instead) # # At the D-Bus side the property is read only but it makes perfect sense to # implement it with a read-write attr_accessor. In that case this method # uses {.dbus_watcher} to set up the PropertiesChanged signal. # # attr_accessor :foo_bar # dbus_reader :foo_bar, "s" # # The above two declarations have a shorthand: # # dbus_reader_attr_accessor :foo_bar, "s" # # If the property value should change by other means than its attr_writer, # you should emit the `PropertiesChanged` signal by calling # {#dbus_properties_changed}. # # dbus_properties_changed(interface_name, {dbus_name.to_s => value}, []) # # or, omitting the value in the signal, # # dbus_properties_changed(interface_name, {}, [dbus_name.to_s]) # # @param (see .dbus_attr_accessor) # @return (see .dbus_attr_accessor) def self.dbus_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) raise UndefinedInterface, ruby_name if @@cur_intf.nil? dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name) property = Property.new(dbus_name, type, :read, ruby_name: ruby_name) @@cur_intf.define(property) ruby_name_eq = :"#{ruby_name}=" return unless method_defined?(ruby_name_eq) dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end # A write-only property accessing a writer method (which must already exist). # (To directly access an instance variable, use {.dbus_attr_writer} instead) # # Uses {.dbus_watcher} to set up the PropertiesChanged signal. # # @param (see .dbus_attr_accessor) # @return (see .dbus_attr_accessor) def self.dbus_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) raise UndefinedInterface, ruby_name if @@cur_intf.nil? dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name) property = Property.new(dbus_name, type, :write, ruby_name: ruby_name) @@cur_intf.define(property) dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end # Enables automatic sending of the PropertiesChanged signal. # For *ruby_name* `foo_bar`, wrap `foo_bar=` so that it sends # the signal for FooBar. # The original version remains as `_original_foo_bar=`. # # @param ruby_name [Symbol] :foo_bar and :foo_bar= both mean the same thing # @param dbus_name [String] if not given it is made # by CamelCasing the ruby_name. foo_bar becomes FooBar # to convert the Ruby convention to the DBus convention. # @param emits_changed_signal [true,false,:const,:invalidates] # see {EmitsChangedSignal}; if unspecified, ask the interface. # @return [void] def self.dbus_watcher(ruby_name, dbus_name: nil, emits_changed_signal: nil) raise UndefinedInterface, ruby_name if @@cur_intf.nil? interface_name = @@cur_intf.name ruby_name = ruby_name.to_s.sub(/=$/, "").to_sym ruby_name_eq = :"#{ruby_name}=" original_ruby_name_eq = "_original_#{ruby_name_eq}" dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name) emits_changed_signal = EmitsChangedSignal.new(emits_changed_signal, interface: @@cur_intf) # the argument order is alias_method(new_name, existing_name) alias_method original_ruby_name_eq, ruby_name_eq define_method ruby_name_eq do |value| result = public_send(original_ruby_name_eq, value) case emits_changed_signal.value when true # signature: "interface:s, changed_props:a{sv}, invalidated_props:as" dbus_properties_changed(interface_name, { dbus_name.to_s => value }, []) when :invalidates dbus_properties_changed(interface_name, {}, [dbus_name.to_s]) when :const # Oh my, seeing a value change of a supposedly constant property. # Maybe should have raised at declaration time, don't make a fuss now. when false # Do nothing end result end end # Defines an exportable method on the object with the given name _sym_, # _prototype_ and the code in a block. # @param prototype [Prototype] def self.dbus_method(sym, prototype = "", &block) raise UndefinedInterface, sym if @@cur_intf.nil? @@cur_intf.define(Method.new(sym.to_s).from_prototype(prototype)) ruby_name = Object.make_method_name(@@cur_intf.name, sym.to_s) # ::Module#define_method(name) { body } define_method(ruby_name, &block) end # Emits a signal from the object with the given _interface_, signal # _sig_ and arguments _args_. # @param intf [Interface] # @param sig [Signal] # @param args arguments for the signal def emit(intf, sig, *args) raise "Cannot emit signal #{intf.name}.#{sig.name} before #{path} is exported" if object_server.nil? object_server.connection.emit(nil, self, intf, sig, *args) end # Defines a signal for the object with a given name _sym_ and _prototype_. def self.dbus_signal(sym, prototype = "") raise UndefinedInterface, sym if @@cur_intf.nil? cur_intf = @@cur_intf signal = Signal.new(sym.to_s).from_prototype(prototype) cur_intf.define(signal) # ::Module#define_method(name) { body } 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 # TODO: borrow a proven implementation # @param str [String] # @return [String] # @api private def self.camelize(str) str.split(/_/).map(&:capitalize).join("") end # Make a D-Bus conventional name, CamelCased. # @param ruby_name [String,Symbol] eg :do_something # @param dbus_name [String,Symbol,nil] use this if given # @return [Symbol] eg DoSomething def self.make_dbus_name(ruby_name, dbus_name: nil) dbus_name ||= camelize(ruby_name.to_s) dbus_name.to_sym end # Use this instead of calling PropertiesChanged directly. This one # considers not only the PC signature (which says that all property values # are variants) but also the specific property type. # @param interface_name [String] interface name like "org.example.ManagerManager" # @param changed_props [Hash{String => ::Object}] # changed properties (D-Bus names) and their values. # @param invalidated_props [Array] # names of properties whose changed value is not specified def dbus_properties_changed(interface_name, changed_props, invalidated_props) typed_changed_props = changed_props.map do |dbus_name, value| property = dbus_lookup_property(interface_name, dbus_name) type = property.type typed_value = Data.make_typed(type, value) variant = Data::Variant.new(typed_value, member_type: type) [dbus_name, variant] end.to_h PropertiesChanged(interface_name, typed_changed_props, invalidated_props) end # @param interface_name [String] # @param property_name [String] # @return [Property] # @raise [DBus::Error] # @api private def dbus_lookup_property(interface_name, property_name) # what should happen for unknown properties # plasma: InvalidArgs (propname), UnknownInterface (interface) # systemd: UnknownProperty interface = intfs[interface_name] if !interface raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"), "Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found: no such interface" end property = interface.properties[property_name.to_sym] if !property raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"), "Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found" end property end # Generates information about interfaces and properties of the object # # Returns a hash containing interfaces names as keys. Each value is the # same hash that would be returned by the # org.freedesktop.DBus.Properties.GetAll() method for that combination of # object path and interface. If an interface has no properties, the empty # hash is returned. # # @return [Hash{String => Hash{String => Data::Base}}] interface -> property -> value def interfaces_and_properties get_all_method = self.class.make_method_name("org.freedesktop.DBus.Properties", :GetAll) intfs.keys.each_with_object({}) do |interface, hash| hash[interface] = public_send(get_all_method, interface).first end end #################################################################### # use the above defined methods to declare the property-handling # interfaces and methods dbus_interface PROPERTY_INTERFACE do dbus_method :Get, "in interface_name:s, in property_name:s, out value:v" do |interface_name, property_name| property = dbus_lookup_property(interface_name, property_name) if property.readable? begin ruby_name = property.ruby_name value = public_send(ruby_name) # may raise, DBus.error or https://ruby-doc.com/core-3.1.0/TypeError.html typed_value = Data.make_typed(property.type, value) [typed_value] rescue StandardError => e msg = "When getting '#{interface_name}.#{property_name}': " + e.message raise e.exception(msg) end else raise DBus.error("org.freedesktop.DBus.Error.PropertyWriteOnly"), "Property '#{interface_name}.#{property_name}' (on object '#{@path}') is not readable" end end dbus_method :Set, "in interface_name:s, in property_name:s, in val:v" do |interface_name, property_name, value| property = dbus_lookup_property(interface_name, property_name) if property.writable? begin ruby_name_eq = "#{property.ruby_name}=" # TODO: declare dbus_method :Set to take :exact argument # and type check it here before passing its :plain value # to the implementation public_send(ruby_name_eq, value) rescue StandardError => e msg = "When setting '#{interface_name}.#{property_name}': " + e.message raise e.exception(msg) end else raise DBus.error("org.freedesktop.DBus.Error.PropertyReadOnly"), "Property '#{interface_name}.#{property_name}' (on object '#{@path}') is not writable" end end dbus_method :GetAll, "in interface_name:s, out value:a{sv}" do |interface_name| interface = intfs[interface_name] if !interface raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"), "Properties '#{interface_name}.*' (on object '#{@path}') not found: no such interface" end p_hash = {} interface.properties.each do |p_name, property| next unless property.readable? ruby_name = property.ruby_name begin # D-Bus spec says: # > If GetAll is called with a valid interface name for which some # > properties are not accessible to the caller (for example, due # > to per-property access control implemented in the service), # > those properties should be silently omitted from the result # > array. # so we will silently omit properties that fail to read. # Get'ting them individually will send DBus.Error value = public_send(ruby_name) # may raise, DBus.error or https://ruby-doc.com/core-3.1.0/TypeError.html typed_value = Data.make_typed(property.type, value) p_hash[p_name.to_s] = typed_value rescue StandardError DBus.logger.debug "Property '#{interface_name}.#{p_name}' (on object '#{@path}') " \ "has raised during GetAll, omitting it" end end [p_hash] end dbus_signal :PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as" end dbus_interface "org.freedesktop.DBus.Introspectable" do dbus_method :Introspect, "out xml_data:s" do # The body is not used, Connection#process handles it instead # which is more efficient and handles paths without objects. end end end end ruby-dbus-0.25.0/lib/dbus/data.rb0000644000004100000410000004714415000117217016507 0ustar www-datawww-data# frozen_string_literal: true # This file is part of the ruby-dbus project # Copyright (C) 2022 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 # FIXME: in general, when an API gives me, a user, a choice, # remember to make it easy for the case of: # "I don't CARE, I don't WANT to care, WHY should I care?" # Exact/explicit representation of D-Bus data types: # # - {Boolean} # - {Byte}, {Int16}, {Int32}, {Int64}, {UInt16}, {UInt32}, {UInt64} # - {Double} # - {String}, {ObjectPath}, {Signature} # - {Array}, {DictEntry}, {Struct} # - {UnixFD} # - {Variant} # # The common base type is {Base}. # # There are other intermediate classes in the inheritance hierarchy, using # the names the specification uses, but they are an implementation detail: # # - A value is either {Basic} or a {Container}. # - Basic values are either {Fixed}-size or {StringLike}. module Data # Given a plain Ruby *value* and wanting a D-Bus *type*, # construct an appropriate {Data::Base} instance. # # @param type [SingleCompleteType,Type] # @param value [::Object,Data::Base] a plain value; exact values also allowed # @return [Data::Base] # @raise TypeError def make_typed(type, value) type = DBus.type(type) unless type.is_a?(Type) data_class = Data::BY_TYPE_CODE[type.sigtype] # not nil because DBus.type validates data_class.from_typed(value, type: type) end module_function :make_typed # The base class for explicitly typed values. # # A value is either {Basic} or a {Container}. # {Basic} values are either {Fixed}-size or {StringLike}. class Base # @!method self.basic? # @return [Boolean] # @!method self.fixed? # @return [Boolean] # @return [::Object] a valid value, plain-Ruby typed. # @see Data::Container#exact_value attr_reader :value # @!method self.type_code # @return [String] a single-character string, for example "a" for arrays # @!method type # @abstract # Note that for Variants type=="v", # for the specific see {Variant#member_type} # @return [Type] the exact type of this value # @!method self.from_typed(value, type:) # @param value [::Object] # @param type [Type] # @return [Base] # @api private # Use {Data.make_typed} instead. # Construct an instance of the specific subclass, with a type further # specified in the *type* argument. # Child classes must validate *value*. def initialize(value) @value = value end def ==(other) @value == if other.is_a?(Base) other.value else other end end # Hash key equality # See https://ruby-doc.org/core-3.0.0/Object.html#method-i-eql-3F # Stricter than #== (RSpec: eq), 1==1.0 but 1.eql(1.0)->false def eql?(other) return false unless other.class == self.class other.value.eql?(@value) # TODO: this should work, now check derived classes, exact_value end # @param type [Type] def self.assert_type_matches_class(type) raise ArgumentError, "Expecting #{type_code.inspect} for class #{self}, got #{type.sigtype.inspect}" \ unless type.sigtype == type_code end end # A value that is not a {Container}. class Basic < Base def self.basic? true end # @return [Type] def self.type # memoize @type ||= Type.new(type_code).freeze end def type # The basic types can do this, unlike the containers self.class.type end # @param value [::Object] # @param type [Type] # @return [Basic] def self.from_typed(value, type:) assert_type_matches_class(type) new(value) end end # A value that has a fixed size (unlike {StringLike}). class Fixed < Basic def self.fixed? true end # most Fixed types are valid # whatever bits from the wire are used to initialize them # @param mode [:plain,:exact] def self.from_raw(value, mode:) return value if mode == :plain new(value) end # @param endianness [:little,:big] def marshall(endianness) [value].pack(self.class.format[endianness]) end end # Format strings for String#unpack, both little- and big-endian. Format = ::Struct.new(:little, :big) # Represents integers class Int < Fixed # @!method self.range # @return [Range] the full range of allowed values # @param value [::Integer,DBus::Data::Int] # @raise RangeError def initialize(value) value = value.value if value.is_a?(self.class) r = self.class.range raise RangeError, "#{value.inspect} is not a member of #{r}" unless r.member?(value) super(value) end end # Byte. # # TODO: a specialized ByteArray for `ay` may be useful, # to save memory and for natural handling class Byte < Int def self.type_code "y" end def self.alignment 1 end FORMAT = Format.new("C", "C") def self.format FORMAT end def self.range (0..255) end end # Boolean: encoded as a {UInt32} but only 0 and 1 are valid. class Boolean < Fixed def self.type_code "b" end def self.alignment 4 end FORMAT = Format.new("L<", "L>") def self.format FORMAT end def self.validate_raw!(value) return if [0, 1].member?(value) raise InvalidPacketException, "BOOLEAN must be 0 or 1, found #{value}" end def self.from_raw(value, mode:) validate_raw!(value) value = value == 1 return value if mode == :plain new(value) end # Accept any *value*, store its Ruby truth value # (excepting another instance of this class, where use its {#value}). # # So new(0).value is true. # @param value [::Object,DBus::Data::Boolean] def initialize(value) value = value.value if value.is_a?(self.class) super(value ? true : false) end # @param endianness [:little,:big] def marshall(endianness) int = value ? 1 : 0 [int].pack(UInt32.format[endianness]) end end # Signed 16 bit integer. class Int16 < Int def self.type_code "n" end def self.alignment 2 end FORMAT = Format.new("s<", "s>") def self.format FORMAT end def self.range (-32_768..32_767) end end # Unsigned 16 bit integer. class UInt16 < Int def self.type_code "q" end def self.alignment 2 end FORMAT = Format.new("S<", "S>") def self.format FORMAT end def self.range (0..65_535) end end # Signed 32 bit integer. class Int32 < Int def self.type_code "i" end def self.alignment 4 end FORMAT = Format.new("l<", "l>") def self.format FORMAT end def self.range (-2_147_483_648..2_147_483_647) end end # Unsigned 32 bit integer. class UInt32 < Int def self.type_code "u" end def self.alignment 4 end FORMAT = Format.new("L<", "L>") def self.format FORMAT end def self.range (0..4_294_967_295) end end # Unix file descriptor, not implemented yet. class UnixFD < UInt32 def self.type_code "h" end end # Signed 64 bit integer. class Int64 < Int def self.type_code "x" end def self.alignment 8 end FORMAT = Format.new("q<", "q>") def self.format FORMAT end def self.range (-9_223_372_036_854_775_808..9_223_372_036_854_775_807) end end # Unsigned 64 bit integer. class UInt64 < Int def self.type_code "t" end def self.alignment 8 end FORMAT = Format.new("Q<", "Q>") def self.format FORMAT end def self.range (0..18_446_744_073_709_551_615) end end # Double-precision floating point number. class Double < Fixed def self.type_code "d" end def self.alignment 8 end FORMAT = Format.new("E", "G") def self.format FORMAT end # @param value [#to_f,DBus::Data::Double] # @raise TypeError,ArgumentError def initialize(value) value = value.value if value.is_a?(self.class) value = Kernel.Float(value) super(value) end end # {DBus::Data::String}, {DBus::Data::ObjectPath}, or {DBus::Data::Signature}. class StringLike < Basic def self.fixed? false end def initialize(value) if value.is_a?(self.class) value = value.value else self.class.validate_raw!(value) end super(value) end end # UTF-8 encoded string. class String < StringLike def self.type_code "s" end def self.alignment 4 end def self.size_class UInt32 end def self.validate_raw!(value) value.each_codepoint do |cp| raise InvalidPacketException, "Invalid string, contains NUL" if cp.zero? end rescue ArgumentError raise InvalidPacketException, "Invalid string, not in UTF-8" end def self.from_raw(value, mode:) value.force_encoding(Encoding::UTF_8) if mode == :plain validate_raw!(value) return value end new(value) end end # See also {DBus::ObjectPath} class ObjectPath < StringLike def self.type_code "o" end def self.alignment 4 end def self.size_class UInt32 end # @raise InvalidPacketException def self.validate_raw!(value) DBus::ObjectPath.new(value) rescue DBus::Error => e raise InvalidPacketException, e.message end def self.from_raw(value, mode:) if mode == :plain validate_raw!(value) return value end new(value) end end # Signature string, zero or more single complete types. # See also {DBus::Type} class Signature < StringLike def self.type_code "g" end def self.alignment 1 end def self.size_class Byte end # @return [::Array] def self.validate_raw!(value) DBus.types(value) rescue Type::SignatureException => e raise InvalidPacketException, "Invalid signature: #{e.message}" end def self.from_raw(value, mode:) if mode == :plain _types = validate_raw!(value) return value end new(value) end end # Contains one or more other values. class Container < Base def self.basic? false end def self.fixed? false end # For containers, the type varies among instances # @see Base#type attr_reader :type # @return something that is, or contains, {Data::Base}. # Er, this docs kinda sucks. def exact_value @value end def value @value.map(&:value) end # Hash key equality # See https://ruby-doc.org/core-3.0.0/Object.html#method-i-eql-3F # Stricter than #== (RSpec: eq), 1==1.0 but 1.eql(1.0)->false def eql?(other) return false unless other.class == self.class other.exact_value.eql?(exact_value) end # def ==(other) # eql?(other) || super # end end # An Array, or a Dictionary (Hash). class Array < Container def self.type_code "a" end def self.alignment 4 end # TODO: check that Hash keys are basic types # @param mode [:plain,:exact] # @param type [Type] # @param hash [Boolean] are we unmarshalling an ARRAY of DICT_ENTRY # @return [Data::Array] def self.from_items(value, mode:, type:, hash: false) value = Hash[value] if hash return value if mode == :plain new(value, type: type) end # @param value [::Object] # @param type [Type] # @return [Data::Array] def self.from_typed(value, type:) new(value, type: type) # initialize(::Array) end def value v = super if type.child.sigtype == Type::DICT_ENTRY # BTW this makes a copy so mutating it is pointless v.to_h else v end end # FIXME: should Data::Array be mutable? # if it is, is its type mutable too? # TODO: specify type or guess type? # Data is the exact type, so its constructor should be exact # and guesswork should be clearly labeled # @param value [Data::Array,Enumerable] # @param type [SingleCompleteType,Type] def initialize(value, type:) type = Type::Factory.make_type(type) self.class.assert_type_matches_class(type) @type = type typed_value = case value when Data::Array unless value.type == type raise ArgumentError, "Specified type is #{type.inspect} but value type is #{value.type.inspect}" end value.exact_value else # TODO: Dict?? value.map do |i| Data.make_typed(type.child, i) end end super(typed_value) end end # A fixed size, heterogenerous tuple. # # (The item count is fixed, not the byte size.) class Struct < Container def self.type_code "r" end def self.alignment 8 end # @param value [::Array] def self.from_items(value, mode:, type:) value.freeze return value if mode == :plain new(value, type: type) end # @param value [::Object] (#size, #each) # @param type [Type] # @return [Struct] def self.from_typed(value, type:) new(value, type: type) end # @param value [Data::Struct,Enumerable] # @param type [SingleCompleteType,Type] def initialize(value, type:) type = Type::Factory.make_type(type) self.class.assert_type_matches_class(type) @type = type typed_value = case value when self.class unless value.type == type raise ArgumentError, "Specified type is #{type.inspect} but value type is #{value.type.inspect}" end value.exact_value else member_types = type.members unless value.size == member_types.size raise ArgumentError, "Specified type has #{member_types.size} members " \ "but value has #{value.size} members" end member_types.zip(value).map do |item_type, item| Data.make_typed(item_type, item) end end super(typed_value) end def ==(other) case other when ::Struct @value.size == other.size && @value.zip(other.to_a).all? { |i, other_i| i == other_i } else super end end end # Dictionary/Hash entry. # TODO: shouldn't instantiate? class DictEntry < Struct def self.type_code "e" end # @param value [::Array] def self.from_items(value, mode:, type:) # rubocop:disable Lint/UnusedMethodArgument value.freeze # DictEntry ignores the :exact mode value end # @param value [::Object] (#size, #each) # @param type [Type] # @return [DictEntry] def self.from_typed(value, type:) new(value, type: type) end end # A generic type. # # Implementation note: @value is a {Data::Base}. class Variant < Container def self.type_code "v" end def self.alignment 1 end def value @value.value end # @param member_type [Type] def self.from_items(value, mode:, member_type:) return value if mode == :plain new(value, member_type: member_type) end # @param value [::Object] # @param type [Type] # @return [Variant] def self.from_typed(value, type:) assert_type_matches_class(type) # nil: decide on type of value new(value, member_type: nil) end # @return [Type] def self.type # memoize @type ||= Type.new(type_code).freeze end # Note that for Variants type.to_s=="v", # for the specific see {Variant#member_type} # @return [Type] the exact type of this value def type self.class.type end # @return [Type] attr_reader :member_type # Determine the type of *value* # @param value [::Object] # @return [Type] # @api private # See also {PacketMarshaller.make_variant} def self.guess_type(value) sct, = PacketMarshaller.make_variant(value) DBus.type(sct) end # @param member_type [SingleCompleteType,Type,nil] def initialize(value, member_type:) member_type = Type::Factory.make_type(member_type) if member_type # TODO: validate that the given *member_type* matches *value* case value when Data::Variant # Copy the contained value instead of boxing it more # TODO: except perhaps for round-tripping in exact mode? @member_type = value.member_type value = value.exact_value when Data::Base @member_type = member_type || value.type raise ArgumentError, "Variant type #{@member_type} does not match value type #{value.type}" \ unless @member_type == value.type else @member_type = member_type || self.class.guess_type(value) value = Data.make_typed(@member_type, value) end super(value) end # Internal helpers to keep the {DBus.variant} method working. # Formerly it returned just a pair of [DBus.type(string_type), value] # so let's provide [0], [1], .first, .last def [](index) case index when 0 member_type when 1 value else raise ArgumentError, "DBus.variant can only be indexed with 0 or 1, seen #{index.inspect}" end end # @see #[] def first self[0] end # @see #[] def last self[1] end end consts = constants.map { |c_sym| const_get(c_sym) } classes = consts.find_all { |c| c.respond_to?(:type_code) } by_type_code = classes.map { |cl| [cl.type_code, cl] }.to_h # { "b" => Data::Boolean, "s" => Data::String, ...} BY_TYPE_CODE = by_type_code end end ruby-dbus-0.25.0/lib/dbus/matchrule.rb0000644000004100000410000000573015000117217017555 0ustar www-datawww-data# frozen_string_literal: true # 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 # @return [String] 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 _typ_. # Possible message types are: signal, method_call, method_return, and error. def type=(typ) if !["signal", "method_call", "method_return", "error"].member?(typ) raise MatchRuleException, typ end @type = typ 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) return false if @type && @type != msg.message_type_s 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 end ruby-dbus-0.25.0/lib/dbus/message_queue.rb0000644000004100000410000001253515000117217020422 0ustar www-datawww-data# frozen_string_literal: true # 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 # Encapsulates a socket so that we can {#push} and {#pop} {Message}s. class MessageQueue # The socket that is used to connect with the bus. attr_reader :socket # The buffer size for messages. MSG_BUF_SIZE = 4096 def initialize(address) DBus.logger.debug "MessageQueue: #{address}" @address = address @buffer = "" # Reduce allocations by using a single buffer for our socket @read_buffer = String.new(capacity: MSG_BUF_SIZE) @is_tcp = false @mutex = Mutex.new connect end # @param blocking [Boolean] # true: wait to return a {Message}; # false: may return `nil` # @return [Message,nil] one message or nil if unavailable # @raise EOFError # @todo failure modes def pop(blocking: true) # FIXME: this is not enough, the R/W test deadlocks on shared connections @mutex.synchronize do buffer_from_socket_nonblock message = message_from_buffer_nonblock if blocking # 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 end def push(message) @mutex.synchronize do @socket.write(message.marshall) end end alias << push private # Connect to the bus and initialize the connection. def connect addresses = @address.split ";" # connect to first one that succeeds 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 # 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) host = params["host"] port = params["port"] if host && port begin # initialize the tcp socket @socket = TCPSocket.new(host, 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: #{host}:#{port}, will now exit." exit(1) # a little harsh end else # Danger, Will Robinson: the specified "path" is not usable puts "Error: supplied params: #{@params}, 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 = Authentication::Client.new(@socket) client.authenticate end public # FIXME: fix Main loop instead # Get and remove one message from the buffer. # @return [Message,nil] the message or nil if unavailable 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 remain nil end ret end # Fill (append) the buffer from data that might be available on the # socket. # @return [void] # @raise EOFError def buffer_from_socket_nonblock @buffer += @socket.read_nonblock(MSG_BUF_SIZE, @read_buffer) 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.25.0/lib/dbus/bus.rb0000644000004100000410000002573215000117217016366 0ustar www-datawww-data# frozen_string_literal: true # dbus.rb - Module containing the low-level D-Bus implementation # # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. require "socket" require "singleton" require_relative "connection" # = D-Bus main module # # Module containing all the D-Bus modules and classes. module DBus # A regular Bus {Connection}. # As opposed to a peer connection to a single counterparty with no daemon in between. class BusConnection < Connection # The unique name (by specification) of the message. attr_reader :unique_name # Connect, authenticate, and send Hello. # @param addresses [String] # @see https://dbus.freedesktop.org/doc/dbus-specification.html#addresses def initialize(addresses) super @unique_name = nil @proxy = nil send_hello 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? xml_filename = File.expand_path("org.freedesktop.DBus.xml", __dir__) xml = File.read(xml_filename) path = "/org/freedesktop/DBus" dest = "org.freedesktop.DBus" pof = DBus::ProxyObjectFactory.new( xml, self, dest, path, api: ApiOptions::A0 ) @proxy = pof.build["org.freedesktop.DBus"] end @proxy end # Request a well-known name so that clients can find us. # @note Parameters other than *name* are advanced, you probably don't need them. # # With no boolean flags, running a second instance of a program that calls `request_name` # will result in the second one failing, which this library translates to an exception. # If you want the second instance to take over, you need both # `allow_replacement: true` and `replace_existing: true.` # # @param name [BusName] the requested name # @param replace_existing [Boolean] # Replace an existing owner of the name, if that owner set *allow_replacement*. # @param allow_replacement [Boolean] # Other connections that specify *replace_existing* will be able to take # the name from us. We will get {#on_name_lost NameLost}. If we specified *queue* # we may get the name again, with {#on_name_acquired NameAcquired}. # @param queue [Boolean] # Affects the behavior when the bus denies the name (sooner or later). # - If `false` (default), it is recommended to let the `NameRequestError` fall through and end your program. # - If `true`, you should `rescue` the `NameRequestError` and set up # {#on_name_acquired NameAcquired} and {#on_name_lost NameLost} handlers. # Meanwhile, the bus will put us in a queue waiting for *name* (this is the "sooner" case). # Also, if we had `allow_replacement: true`, another connection can cause us # to lose the name. We will be moved back to the queue, waiting for when the other owners give up # (the "later" case). # @param flags [Integer,nil] # If specified, overrides the boolean parameters. # Use a bitwise sum `|` of: # - NAME_FLAG_ALLOW_REPLACEMENT # - NAME_FLAG_REPLACE_EXISTING # - NAME_FLAG_DO_NOT_QUEUE # Note that `0` implies `queue: true`. # # @return [REQUEST_NAME_REPLY_PRIMARY_OWNER,REQUEST_NAME_REPLY_ALREADY_OWNER] on success # @raise [NameRequestError] with #error_code REQUEST_NAME_REPLY_EXISTS or REQUEST_NAME_REPLY_IN_QUEUE, on failure # @raise DBus::Error another way to fail is being prohibited to own the name # which is the default on the system bus # # @see https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-request-name # # @example Simple usage # bus = DBus.session_bus # bus.object_server.export(DBus::Object.new("/org/example/Test")) # bus.request_name("org.example.Test") # # main loop # # @example Second instance taking over # bus = DBus.session_bus # bus.object_server.export(DBus::Object.new("/org/example/Test")) # bus.on_name_lost { exit } # bus.request_name("org.example.Test", allow_replacement: true, replace_existing: true) # # main loop # # @example Second instance waiting for its turn # bus = DBus.session_bus # bus.object_server.export(DBus::Object.new("/org/example/Test")) # bus.on_name_acquired { @owner = true } # begin # bus.request_name("org.example.Test", queue: true) # rescue DBus::Connection::NameRequestError => e # @owner = false # end # # main loop def request_name(name, allow_replacement: false, replace_existing: false, queue: false, flags: nil) if flags.nil? flags = (allow_replacement ? NAME_FLAG_ALLOW_REPLACEMENT : 0) | (replace_existing ? NAME_FLAG_REPLACE_EXISTING : 0) | (queue ? 0 : NAME_FLAG_DO_NOT_QUEUE) end name = BusName.new(name) r = proxy.RequestName(name, flags).first handle_return_of_request_name(r, name) end # The caller has released his claim on the given name. # Either the caller was the primary owner of the name, and the name is now unused # or taken by somebody waiting in the queue for the name, # or the caller was waiting in the queue for the name and has now been removed from the queue. RELEASE_NAME_REPLY_RELEASED = 1 # The given name does not exist on this bus. RELEASE_NAME_REPLY_NON_EXISTENT = 2 # The caller was not the primary owner of this name, and was also not waiting in the queue to own this name. RELEASE_NAME_REPLY_NOT_OWNER = 3 # @param name [BusName] the name to release def release_name(name) name = BusName.new(name) proxy.ReleaseName(name).first end def on_name_acquired(&handler) proxy.on_signal("NameAcquired", &handler) end def on_name_lost(&handler) proxy.on_signal("NameLost", &handler) end # Asks bus to send us messages matching mr, and execute slot when # received # @param match_rule [MatchRule,#to_s] # @return [void] def add_match(match_rule, &slot) mrs = match_rule.to_s rule_existed = super(mrs, &slot) # don't ask for the same match if we override it return if rule_existed DBus.logger.debug "Asked for a new match" proxy.AddMatch(mrs) end # @param match_rule [MatchRule,#to_s] # @return [void] def remove_match(match_rule) mrs = match_rule.to_s rule_existed = super(mrs) # 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 # Makes a {ProxyService} with the given *name*. # Note that this succeeds even if the name does not exist and cannot be # activated. It will only fail when calling a method. # @return [ProxyService] def service(name) # The service might not exist at this time so we cannot really check # anything ProxyService.new(name, self) end alias [] service ########################################################################### 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 end end # = 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 < BusConnection # Get the the default session bus. def initialize super(self.class.session_bus_address) end def self.session_bus_address ENV["DBUS_SESSION_BUS_ADDRESS"] || address_from_file || ("launchd:env=DBUS_LAUNCHD_SESSION_BUS_SOCKET" if Platform.macos?) || (raise NotImplementedError, "Cannot find session bus; sorry, haven't figured out autolaunch yet") 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 # Default socket name for the system bus. SYSTEM_BUS_ADDRESS = "unix:path=/var/run/dbus/system_bus_socket" # = 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 < BusConnection # Get the default system bus. def initialize super(self.class.system_bus_address) end def self.system_bus_address ENV["DBUS_SYSTEM_BUS_ADDRESS"] || SYSTEM_BUS_ADDRESS 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: # https://gitlab.com/pangdudu/ruby-dbus/-/blob/master/README.rdoc # TODO: keep the name but update the docs # @deprecated just use BusConnection class RemoteBus < BusConnection end # See ASystemBus class SystemBus < ASystemBus include Singleton end # Shortcut for the {SystemBus} instance # @return [BusConnection] def self.system_bus SystemBus.instance end # Shortcut for the {SessionBus} instance # @return [BusConnection] def self.session_bus SessionBus.instance end end ruby-dbus-0.25.0/lib/dbus/proxy_object.rb0000644000004100000410000001356315000117217020303 0ustar www-datawww-data# frozen_string_literal: true # 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 # Represents 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 corresponding # 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. # @return [Boolean] attr_accessor :introspected # The (remote) destination of the object. attr_reader :destination # The path to the object. # @return [ObjectPath] 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 OPEN_QUOTE = RUBY_VERSION >= "3.4" ? "'" : "`" # 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 = ObjectPath.new(path) @introspected = false @interfaces = {} @subnodes = [] @api = api end # Returns the interfaces of the object. # @return [Array] names of the interfaces 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 ifc = @interfaces[intfname] raise DBus::Error, "no such interface #{OPEN_QUOTE}#{intfname}' on object #{OPEN_QUOTE}#{@path}'" unless ifc ifc 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) unless @default_iface && has_iface?(@default_iface) raise NoMethodError, "undefined signal #{OPEN_QUOTE}#{name}' for DBus interface " \ "#{OPEN_QUOTE}#{@default_iface}' on object #{OPEN_QUOTE}#{@path}'" end @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 #{OPEN_QUOTE}#{name}' for DBus interface " \ "#{OPEN_QUOTE}#{@default_iface}' on object #{OPEN_QUOTE}#{@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 #{OPEN_QUOTE}#{name}'/ # BTW e.exception("...") would preserve the class. raise NoMethodError, "undefined method #{OPEN_QUOTE}#{name}' for DBus interface " \ "#{OPEN_QUOTE}#{@default_iface}' on object #{OPEN_QUOTE}#{@path}'" end end def respond_to_missing?(name, _include_private = false) (@default_iface && has_iface?(@default_iface) && @interfaces[@default_iface].methods.key?(name)) or super end end end ruby-dbus-0.25.0/lib/dbus/core_ext/0000755000004100000410000000000015000117217017047 5ustar www-datawww-dataruby-dbus-0.25.0/lib/dbus/core_ext/module/0000755000004100000410000000000015000117217020334 5ustar www-datawww-dataruby-dbus-0.25.0/lib/dbus/core_ext/module/redefine_method.rb0000644000004100000410000000324515000117217024006 0ustar www-datawww-data# frozen_string_literal: true # copied from activesupport/core_ext from Rails, MIT license # https://github.com/rails/rails/tree/a713fdae4eb4f7ccd34932edc61561a96b8d9f35/activesupport/lib/active_support/core_ext/module class Module if RUBY_VERSION >= "2.3" # Marks the named method as intended to be redefined, if it exists. # Suppresses the Ruby method redefinition warning. Prefer # #redefine_method where possible. def silence_redefinition_of_method(method) if method_defined?(method) || private_method_defined?(method) # This suppresses the "method redefined" warning; the self-alias # looks odd, but means we don't need to generate a unique name alias_method method, method end end else def silence_redefinition_of_method(method) if method_defined?(method) || private_method_defined?(method) alias_method :__rails_redefine, method remove_method :__rails_redefine end end end # Replaces the existing method definition, if there is one, with the passed # block as its body. def redefine_method(method, &block) visibility = method_visibility(method) silence_redefinition_of_method(method) define_method(method, &block) send(visibility, method) end # Replaces the existing singleton method definition, if there is one, with # the passed block as its body. def redefine_singleton_method(method, &block) singleton_class.redefine_method(method, &block) end def method_visibility(method) # :nodoc: case when private_method_defined?(method) :private when protected_method_defined?(method) :protected else :public end end end ruby-dbus-0.25.0/lib/dbus/core_ext/class/0000755000004100000410000000000015000117217020154 5ustar www-datawww-dataruby-dbus-0.25.0/lib/dbus/core_ext/class/attribute.rb0000644000004100000410000000667215000117217022517 0ustar www-datawww-data# frozen_string_literal: true # copied from activesupport/core_ext from Rails, MIT license # https://github.com/rails/rails/tree/9794e85351243cac6d4e78adaba634b8e4ecad0a/activesupport/lib/active_support/core_ext require_relative "../module/redefine_method" 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. # # ==== Examples # # class Base # my_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 place. Instead 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 def my_class_attribute(*attrs) instance_reader = true instance_writer = true attrs.each do |name| singleton_class.silence_redefinition_of_method(name) define_singleton_method(name) { nil } ivar = "@#{name}".to_sym singleton_class.silence_redefinition_of_method("#{name}=") define_singleton_method("#{name}=") do |val| singleton_class.class_eval do redefine_method(name) { val } end if singleton_class? class_eval do redefine_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 redefine_method(name) do if instance_variable_defined?(ivar) instance_variable_get ivar else self.class.public_send name end end end if instance_writer redefine_method("#{name}=") do |val| instance_variable_set ivar, val end end end end end ruby-dbus-0.25.0/lib/dbus/node_tree.rb0000644000004100000410000000570315000117217017535 0ustar www-datawww-data# frozen_string_literal: true # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # Copyright (C) 2023 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 # Has a tree of {Node}s, refering to {Object}s or to {ProxyObject}s. class NodeTree # @return [Node] attr_reader :root def initialize @root = Node.new("/") end # Get the object node corresponding to the given *path*. # @param path [ObjectPath] # @param create [Boolean] if true, the the {Node}s in the path are created # if they do not already exist. # @return [Node,nil] 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 n end end # = Object path node class # # Class representing a node on an object path. class Node < Hash # @return [DBus::Object,DBus::ProxyObject,nil] # The D-Bus object contained by the node. attr_accessor :object # The name of the node. # @return [String] the last component of its object path, or "/" attr_reader :name # Create a new node with a given _name_. def initialize(name) super() @name = name @object = nil end # Return an XML string representation of the node. # It is shallow, not recursing into subnodes # @param node_opath [String] def to_xml(node_opath) xml = ' ' xml += "\n" each_key do |k| xml += " \n" end @object&.intfs&.each_value do |v| xml += v.to_xml 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 contents_sub_inspect = keys .map { |k| "#{k} => #{self[k].sub_inspect}" } .join(",") "#{s}{#{contents_sub_inspect}}" end # All objects (not paths) under this path (except itself). # @return [Array] def descendant_objects children_objects = values.map(&:object).compact descendants = values.map(&:descendant_objects) flat_descendants = descendants.reduce([], &:+) children_objects + flat_descendants end end end ruby-dbus-0.25.0/lib/dbus/auth.rb0000644000004100000410000002662615000117217016541 0ustar www-datawww-data# frozen_string_literal: true # 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 authentication fails somehow. class AuthenticationFailed < StandardError end # The Authentication Protocol. # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol # # @api private module Authentication # Base class of authentication mechanisms class Mechanism # @!method call(challenge) # @abstract # Replies to server *challenge*, or sends an initial response if the challenge is `nil`. # @param challenge [String,nil] # @return [Array(Symbol,String)] pair [action, response], where # - [:MechContinue, response] caller should send "DATA response" and go to :WaitingForData # - [:MechOk, response] caller should send "DATA response" and go to :WaitingForOk # - [:MechError, message] caller should send "ERROR message" and go to :WaitingForData # Uppercase mechanism name, as sent to the server # @return [String] def name self.class.to_s.upcase.sub(/.*::/, "") end end # Anonymous authentication class. # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-anonymous class Anonymous < Mechanism def call(_challenge) [:MechOk, "Ruby DBus"] end end # Class for 'external' type authentication. # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-external class External < Mechanism # Performs the authentication. def call(_challenge) [:MechOk, Process.uid.to_s] end end # A variant of EXTERNAL that doesn't say our UID. # Seen busctl do this and it worked across a container boundary. class ExternalWithoutUid < External def name "EXTERNAL" end def call(_challenge) [:MechContinue, nil] end end # Implements the AUTH DBUS_COOKIE_SHA1 mechanism. # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-sha class DBusCookieSHA1 < Mechanism # returns the modules name def name "DBUS_COOKIE_SHA1" end # First we are called with nil and we reply with our username. # Then we prove that we can read that user's cookie file. def call(challenge) if challenge.nil? require "etc" # number of retries we have for auth @retries = 1 return [:MechContinue, Etc.getlogin] end require "digest/sha1" # name of cookie file, id of cookie in file, servers random challenge context, id, s_challenge = challenge.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.start_with?(id) # 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) # Return response response = [:MechOk, "#{c_challenge} #{sha}"] return response end end return if @retries <= 0 # a little rescue magic puts "ERROR: Could not auth, will now exit." puts "ERROR: Unable to locate cookie, retry in 1 second." @retries -= 1 sleep 1 call(challenge) end end # Declare client state transitions, for ease of code reading. # It is just a pair. NextState = Struct.new(:state, :command_words) # Authenticates the connection before messages can be exchanged. class Client # @return [Boolean] have we negotiated Unix file descriptor passing # NOTE: not implemented yet in upper layers attr_reader :unix_fd # @return [String] attr_reader :address_uuid # Create a new authentication client. # @param mechs [Array,nil] custom list of auth Mechanism objects or classes def initialize(socket, mechs = nil) @unix_fd = false @address_uuid = nil @socket = socket @state = nil @auth_list = mechs || [ External, DBusCookieSHA1, ExternalWithoutUid, Anonymous ] end # Start the authentication process. # @return [void] # @raise [AuthenticationFailed] def authenticate DBus.logger.debug "Authenticating" send_nul_byte use_next_mechanism @state, command = next_state_via_mechanism.to_a send(command) loop do DBus.logger.debug "auth STATE: #{@state}" words = next_msg @state, command = next_state(words).to_a break if [:TerminatedOk, :TerminatedError].include? @state send(command) end raise AuthenticationFailed, command.first if @state == :TerminatedError send("BEGIN") end ########## private ########## # The authentication protocol requires a nul byte # that may carry credentials. # @return [void] def send_nul_byte if Platform.freebsd? @socket.sendmsg(0.chr, 0, nil, [:SOCKET, :SCM_CREDS, ""]) else @socket.write(0.chr) end end # encode plain to hex # @param plain [String,nil] # @return [String,nil] def hex_encode(plain) return nil if plain.nil? plain.unpack1("H*") end # decode hex to plain # @param encoded [String,nil] # @return [String,nil] def hex_decode(encoded) return nil if encoded.nil? [encoded].pack("H*") end # Send a string to the socket; good place for test mocks. def write_line(str) DBus.logger.debug "auth_write: #{str.inspect}" @socket.write(str) end # Send *words* to the server as a single CRLF terminated string. # @param words [Array,String] def send(words) joined = Array(words).compact.join(" ") write_line("#{joined}\r\n") end # Try authentication using the next mechanism. # @raise [AuthenticationFailed] if there are no more left # @return [void] def use_next_mechanism raise AuthenticationFailed, "Authentication mechanisms exhausted" if @auth_list.empty? @mechanism = @auth_list.shift @mechanism = @mechanism.new if @mechanism.is_a? Class rescue AuthenticationFailed # TODO: make this caller's responsibility @socket.close raise end # Read data (a buffer) from the bus until CR LF is encountered. # Return the buffer without the CR LF characters. # @return [Array] received words def next_msg read_line.chomp.split(" ") end # Read a line from the socket; good place for test mocks. # @return [String] CRLF (\r\n) terminated def read_line # TODO: probably can simply call @socket.readline data = "" crlf = "\r\n" left = 1024 # 1024 byte, no idea if it's ever getting bigger while left.positive? 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 DBus.logger.debug "auth_read: #{data.inspect}" data 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 # @param hex_challenge [String,nil] (nil when the server said "DATA\r\n") # @param use_data [Boolean] say DATA instead of AUTH # @return [NextState] def next_state_via_mechanism(hex_challenge = nil, use_data: false) challenge = hex_decode(hex_challenge) action, response = @mechanism.call(challenge) DBus.logger.debug "auth mechanism action: #{action.inspect}" command = use_data ? ["DATA"] : ["AUTH", @mechanism.name] case action when :MechError NextState.new(:WaitingForData, ["ERROR", response]) when :MechContinue NextState.new(:WaitingForData, command + [hex_encode(response)]) when :MechOk NextState.new(:WaitingForOk, command + [hex_encode(response)]) else raise AuthenticationFailed, "internal error, unknown action #{action.inspect} " \ "from our mechanism #{@mechanism.inspect}" end end # Try to reach the next state based on the current state. # @param received_words [Array] # @return [NextState] def next_state(received_words) msg = received_words case @state when :WaitingForData case msg[0] when "DATA" next_state_via_mechanism(msg[1], use_data: true) when "REJECTED" use_next_mechanism next_state_via_mechanism when "ERROR" NextState.new(:WaitingForReject, ["CANCEL"]) when "OK" @address_uuid = msg[1] # NextState.new(:TerminatedOk, []) NextState.new(:WaitingForAgreeUnixFD, ["NEGOTIATE_UNIX_FD"]) else NextState.new(:WaitingForData, ["ERROR"]) end when :WaitingForOk case msg[0] when "OK" @address_uuid = msg[1] # NextState.new(:TerminatedOk, []) NextState.new(:WaitingForAgreeUnixFD, ["NEGOTIATE_UNIX_FD"]) when "REJECTED" use_next_mechanism next_state_via_mechanism when "DATA", "ERROR" NextState.new(:WaitingForReject, ["CANCEL"]) else # we don't understand server's response but still wait for a successful auth completion NextState.new(:WaitingForOk, ["ERROR"]) end when :WaitingForReject case msg[0] when "REJECTED" use_next_mechanism next_state_via_mechanism else # TODO: spec says to close socket, clarify NextState.new(:TerminatedError, ["Unknown server reply #{msg[0].inspect} when expecting REJECTED"]) end when :WaitingForAgreeUnixFD case msg[0] when "AGREE_UNIX_FD" @unix_fd = true NextState.new(:TerminatedOk, []) when "ERROR" @unix_fd = false NextState.new(:TerminatedOk, []) else # TODO: spec says to close socket, clarify NextState.new(:TerminatedError, ["Unknown server reply #{msg[0].inspect} to NEGOTIATE_UNIX_FD"]) end else raise "Internal error: unhandled state #{@state.inspect}" end end end end end ruby-dbus-0.25.0/lib/dbus/bus_name.rb0000644000004100000410000000203615000117217017356 0ustar www-datawww-data# frozen_string_literal: true # This file is part of the ruby-dbus project # Copyright (C) 2019 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: a name for a connection, like ":1.3" or "org.example.ManagerManager". # Implemented as a {::String} that validates at initialization time. # @see https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-bus class BusName < String # @raise Error if not a valid bus name def initialize(name) unless self.class.valid?(name) raise DBus::Error, "Invalid bus name #{name.inspect}" end super end def self.valid?(name) name.size <= 255 && (name =~ /\A:[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)+\z/ || name =~ /\A[A-Za-z_-][A-Za-z0-9_-]*(\.[A-Za-z_-][A-Za-z0-9_-]*)+\z/) end end end ruby-dbus-0.25.0/lib/dbus/main.rb0000644000004100000410000000412215000117217016507 0ustar www-datawww-data# frozen_string_literal: true # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # Copyright (C) 2023 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 # = 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 => e DBus.logger.debug "Got #{e.inspect} from #{socket.inspect}" @buses.delete socket # this bus died next end while (m = b.message_queue.message_from_buffer_nonblock) b.process(m) end end end DBus.logger.debug "Main loop quit" if @quitting DBus.logger.debug "Main loop quit, no connections left" if @buses.empty? end end end ruby-dbus-0.25.0/lib/dbus/api_options.rb0000644000004100000410000000202215000117217020104 0ustar www-datawww-data# frozen_string_literal: true # 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 # @return [Boolean] # - true: a proxy (client-side) method will return an array # even for the most common case where the method is declared # to have only one 'out parameter' # - false: a proxy (client-side) method will return # - one value for the only 'out parameter' # - an array with more 'out parameters' 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.25.0/lib/dbus/org.freedesktop.DBus.xml0000644000004100000410000000602215000117217021716 0ustar www-datawww-data ruby-dbus-0.25.0/lib/dbus/message.rb0000644000004100000410000002126415000117217017215 0ustar www-datawww-data# frozen_string_literal: true # 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 "raw_message" # = D-Bus main module # # Module containing all the D-Bus modules and classes. module DBus # = D-Bus message class # # Class that holds any type of message that travels over the bus. class Message # The serial number of the message. @@serial = 1 # Mutex that protects updates on the serial number. @@serial_mutex = Mutex.new # Type of a message (by specification). MESSAGE_SIGNATURE = "yyyyuua(yv)" # FIXME: following message type constants should be under Message::Type IMO # well, yeah sure # # Invalid message type. INVALID = 0 # Method call message type. METHOD_CALL = 1 # Method call return value message type. METHOD_RETURN = 2 # Error message type. ERROR = 3 # Signal message type. SIGNAL = 4 # Names used by signal match rules TYPE_NAMES = ["invalid", "method_call", "method_return", "error", "signal"].freeze # 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 # @return [Integer] (u32) # The serial number of the message this message is a reply for. attr_accessor :reply_serial # The protocol. attr_reader :protocol # @return [Integer] (u32) # 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 # @return [String] name of message type, as used in match rules: # "method_call", "method_return", "signal", "error" def message_type_s TYPE_NAMES[message_type] || "unknown_type_#{message_type}" end # Create a regular reply to a message _msg_. def self.method_return(msg) MethodReturnMessage.new.reply_to(msg) end # Create an error reply to a message _msg_. def self.error(msg, error_name, description = nil) ErrorMessage.new(error_name, description).reply_to(msg) end # Mark this message as a reply to a another message _msg_, taking # the serial number of _msg_ as reply serial and the sender of _msg_ as # destination. def reply_to(msg) @reply_serial = msg.serial @destination = msg.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?(Integer) @signature += type.to_s @params << [type, val] end # "l" or "B" ENDIANNESS_CHAR = ENV.fetch("RUBY_DBUS_ENDIANNESS", HOST_END) ENDIANNESS = RawMessage.endianness(ENDIANNESS_CHAR) # 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 RESERVED_PATH = "/org/freedesktop/DBus/Local" # Marshall the message with its current set parameters and return # it in a packet form. # @return [String] def marshall if @path == RESERVED_PATH # the bus would disconnect us, better explain why raise "Cannot send a message with the reserved path #{RESERVED_PATH}: #{inspect}" end params_marshaller = PacketMarshaller.new(endianness: ENDIANNESS) @params.each do |type, value| params_marshaller.append(type, value) end @body_length = params_marshaller.packet.bytesize marshaller = PacketMarshaller.new(endianness: ENDIANNESS) marshaller.append(Type::BYTE, ENDIANNESS_CHAR.ord) 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) marshaller.packet + params_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) pu = PacketUnmarshaller.new(buf, RawMessage.endianness(buf[0])) 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_body if @body_length.positive? && @signature @params = pu.unmarshall(@signature, @body_length) end [self, pu.consumed_size] end # Make a new exception from ex, mark it as being caused by this message # @api private def annotate_exception(exc) new_exc = exc.exception("#{exc}; caused by #{self}") new_exc.set_backtrace(exc.backtrace) new_exc end end 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(exc) name = if exc.is_a? DBus::Error exc.name else "org.freedesktop.DBus.Error.Failed" # exc.class.to_s # RuntimeError is not a valid name, has no dot end description = exc.message msg = new(name, description) msg.add_param(DBus.type("as"), exc.backtrace) msg end end end ruby-dbus-0.25.0/lib/dbus/object_manager.rb0000644000004100000410000000437515000117217020535 0ustar www-datawww-data# frozen_string_literal: true # This file is part of the ruby-dbus project # Copyright (C) 2022 José Iván López González # Copyright (C) 2022 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 # A mixin for {DBus::Object} implementing # {https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager # org.freedesktop.DBus.ObjectManager}. # # {ObjectServer#export} and {ObjectServer#unexport} will look for an ObjectManager # parent in the path hierarchy. If found, it will emit InterfacesAdded # or InterfacesRemoved, as appropriate. module ObjectManager OBJECT_MANAGER_INTERFACE = "org.freedesktop.DBus.ObjectManager" # Implements `the GetManagedObjects` method. # @return [Hash{ObjectPath => Hash{String => Hash{String => Data::Base}}}] # object -> interface -> property -> value def managed_objects descendant_objects = object_server.descendants_for(path) descendant_objects.each_with_object({}) do |obj, hash| hash[obj.path] = obj.interfaces_and_properties end end # {ObjectServer#export} will call this for you to emit the `InterfacesAdded` signal. # @param object [DBus::Object] # @return [void] def object_added(object) InterfacesAdded(object.path, object.interfaces_and_properties) end # {ObjectServer#unexport} will call this for you to emit the `InterfacesRemoved` signal. # @param object [DBus::Object] # @return [void] def object_removed(object) InterfacesRemoved(object.path, object.intfs.keys) end # Module#included, a hook for `include ObjectManager`, declares its dbus_interface. def self.included(base) base.class_eval do dbus_interface OBJECT_MANAGER_INTERFACE do dbus_method :GetManagedObjects, "out res:a{oa{sa{sv}}}" do [managed_objects] end dbus_signal :InterfacesAdded, "object:o, interfaces_and_properties:a{sa{sv}}" dbus_signal :InterfacesRemoved, "object:o, interfaces:as" end end end end end ruby-dbus-0.25.0/lib/dbus/connection.rb0000644000004100000410000003065015000117217017727 0ustar www-datawww-data# frozen_string_literal: true # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # Copyright (C) 2023 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 main connection class # # Main class that maintains a connection to a bus and can handle incoming # and outgoing messages. class Connection # pop and push messages here # @return [MessageQueue] 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) # @return [Hash{Integer => Proc}] # key: message serial # value: block to be run when the reply to that message is received @method_call_replies = {} # @return [Hash{Integer => Message}] # for debugging only: messages for which a reply was not received yet; # key == value.serial @method_call_msgs = {} @signal_matchrules = {} end def object_server @object_server ||= ObjectServer.new(self) 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(blocking: false)) # 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 # NAME_FLAG_* and REQUEST_NAME_* belong to BusConnection # but users will have referenced them in Connection so they need to stay here # 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 # @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) 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 # @return [Integer] one of # REQUEST_NAME_REPLY_IN_QUEUE # REQUEST_NAME_REPLY_EXISTS attr_reader :error_code def initialize(error_code) @error_code = error_code super() end end # In case RequestName did not succeed, raise an exception but first ask the bus who owns the name instead of us # @param ret [Integer] what RequestName returned # @param name Name that was requested # @return [REQUEST_NAME_REPLY_PRIMARY_OWNER,REQUEST_NAME_REPLY_ALREADY_OWNER] on success # @raise [NameRequestError] with #error_code REQUEST_NAME_REPLY_EXISTS or REQUEST_NAME_REPLY_IN_QUEUE, on failure # @api private def handle_return_of_request_name(ret, name) if [REQUEST_NAME_REPLY_EXISTS, REQUEST_NAME_REPLY_IN_QUEUE].include?(ret) other = proxy.GetNameOwner(name).first other_creds = proxy.GetConnectionCredentials(other).first message = "Could not request #{name}, already owned by #{other}, #{other_creds.inspect}" raise NameRequestError.new(ret), message end ret end # Attempt to request a service _name_. # @raise NameRequestError which cannot really be rescued as it will be raised when dispatching a later call. # @return [ObjectServer] # @deprecated Use {BusConnection#request_name}. 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) handle_return_of_request_name(r, name) end object_server 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 _msg_ on to the bus. This is done synchronously, thus # the call will block until a reply message arrives. # @param msg [Message] # @param retc [Proc] the reply handler # @yieldparam rmsg [MethodReturnMessage] the reply # @yieldreturn [Array] the reply (out) parameters def send_sync(msg, &retc) # :yields: reply/return message return if msg.nil? # check if somethings wrong @message_queue.push(msg) @method_call_msgs[msg.serial] = msg @method_call_replies[msg.serial] = retc retm = wait_for_message return if retm.nil? # check if somethings wrong process(retm) while @method_call_replies.key? msg.serial retm = wait_for_message process(retm) end rescue EOFError new_err = DBus::Error.new("Connection dropped after we sent #{msg.inspect}") raise new_err end # @api private # Specify a code block that has to be executed when a reply for # message _msg_ is received. # @param msg [Message] def on_return(msg, &retc) # Have a better exception here if msg.message_type != Message::METHOD_CALL raise "on_return should only get method_calls" end @method_call_msgs[msg.serial] = msg @method_call_replies[msg.serial] = retc end # Asks bus to send us messages matching mr, and execute slot when # received # @param match_rule [MatchRule,#to_s] # @return [void] actually return whether the rule existed, internal detail def add_match(match_rule, &slot) # check this is a signal. mrs = match_rule.to_s DBus.logger.debug "#{@signal_matchrules.size} rules, adding #{mrs.inspect}" rule_existed = @signal_matchrules.key?(mrs) @signal_matchrules[mrs] = slot rule_existed end # @param match_rule [MatchRule,#to_s] # @return [void] actually return whether the rule existed, internal detail def remove_match(match_rule) mrs = match_rule.to_s @signal_matchrules.delete(mrs).nil? end # @api private # Process a message _msg_ based on its type. # @param msg [Message] def process(msg) return if msg.nil? # check if somethings wrong case msg.message_type when Message::ERROR, Message::METHOD_RETURN raise InvalidPacketException if msg.reply_serial.nil? mcs = @method_call_replies[msg.reply_serial] if !mcs DBus.logger.debug "no return code for mcs: #{mcs.inspect} msg: #{msg.inspect}" else if msg.message_type == Message::ERROR mcs.call(Error.new(msg)) else mcs.call(msg) end @method_call_replies.delete(msg.reply_serial) @method_call_msgs.delete(msg.reply_serial) end when DBus::Message::METHOD_CALL if msg.path == "/org/freedesktop/DBus" DBus.logger.debug "Got method call on /org/freedesktop/DBus" end node = object_server.get_node(msg.path, create: false) # introspect a known path even if there is no object on it if node && msg.interface == "org.freedesktop.DBus.Introspectable" && msg.member == "Introspect" reply = Message.new(Message::METHOD_RETURN).reply_to(msg) reply.sender = @unique_name xml = node.to_xml(msg.path) reply.add_param(Type::STRING, xml) @message_queue.push(reply) # dispatch for an object elsif node&.object node.object.dispatch(msg) else reply = Message.error(msg, "org.freedesktop.DBus.Error.UnknownObject", "Object #{msg.path} doesn't exist") @message_queue.push(reply) end when DBus::Message::SIGNAL # the signal can match multiple different rules # clone to allow new signale handlers to be registered @signal_matchrules.dup.each do |mrs, slot| if DBus::MatchRule.new.from_s(mrs).match(msg) slot.call(msg) end end else # spec(Message Format): Unknown types must be ignored. DBus.logger.debug "Unknown message type: #{msg.message_type}" end rescue Exception => e raise msg.annotate_exception(e) end # @api private # Emit a signal event for the given _service_, object _obj_, interface # _intf_ and signal _sig_ with arguments _args_. # @param _service unused # @param obj [DBus::Object] # @param intf [Interface] # @param sig [Signal] # @param args arguments for the signal 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 i = 0 sig.params.each do |par| m.add_param(par.type, args[i]) i += 1 end @message_queue.push(m) end end # A {Connection} that is talking directly to a peer, with no bus daemon in between. # A prominent example is the PulseAudio connection, # see https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/DBus/ # When starting, it still starts with authentication but omits the Hello message. class PeerConnection < Connection # Get a {ProxyPeerService}, a dummy helper to get {ProxyObject}s for # a {PeerConnection}. # @return [ProxyPeerService] def peer_service ProxyPeerService.new(self) end end end ruby-dbus-0.25.0/lib/dbus/type.rb0000644000004100000410000003067215000117217016555 0ustar www-datawww-data# frozen_string_literal: true # 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 # Like a {Signature} but containing only a single complete type. # # For documentation purposes only. class SingleCompleteType < String; end # Zero or more {SingleCompleteType}s; its own type code is "g". # For example "ssv" for a method taking two Strings and a Variant/ # # For documentation purposes only. class Signature < String; end # Similar to {Signature} but for {DBus::Object.define_method}, # contains names and direction of the parameters. # For example "in query:s, in case_sensitive:b, out results:ao". # # For documentation purposes only. class Prototype < String; end # Represents the D-Bus types. # # Corresponds to {SingleCompleteType}. # Instances are immutable/frozen once fully constructed. # # See also {DBus::Data::Signature} which is "type on the wire". class Type # Mapping from type number to name and alignment. TYPE_MAPPING = { 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 TYPE_MAPPING.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 # Formerly this was a Module and there was a DBus::Type::Type class # but the class got too prominent to keep its double double name. # This is for backward compatibility. Type = self # rubocop:disable Naming/ConstantName # @return [String] the signature type character, eg "s" or "e". attr_reader :sigtype # @return [Array] contained member types. attr_reader :members # Use {DBus.type} instead, because this allows constructing # incomplete or invalid types, for backward compatibility. # # @param abstract [Boolean] allow abstract types "r" and "e" # (Enabled for internal usage by {Parser}.) def initialize(sigtype, abstract: false) if !TYPE_MAPPING.keys.member?(sigtype) case sigtype when ")" raise SignatureException, "STRUCT unexpectedly closed: )" when "}" raise SignatureException, "DICT_ENTRY unexpectedly closed: }" else raise SignatureException, "Unknown type code #{sigtype.inspect}" end end unless abstract case sigtype when STRUCT raise SignatureException, "Abstract STRUCT, use \"(...)\" instead of \"#{STRUCT}\"" when DICT_ENTRY raise SignatureException, "Abstract DICT_ENTRY, use \"{..}\" instead of \"#{DICT_ENTRY}\"" end end @sigtype = sigtype.freeze @members = [] # not frozen yet, Parser#parse_one or Factory will do it freeze end # A Type is equal to # - another Type with the same string representation # - a String ({SingleCompleteType}) describing the type def ==(other) case other when ::String to_s == other else eql?(other) end end # A Type is eql? to # - another Type with the same string representation # # Hash key equality # See https://ruby-doc.org/core-3.0.0/Object.html#method-i-eql-3F def eql?(other) return false unless other.is_a?(Type) @sigtype == other.sigtype && @members == other.members end # Return the required alignment for the type. def alignment TYPE_MAPPING[@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}" when DICT_ENTRY "{#{@members.collect(&:to_s).join}}" else @sigtype.chr end end # Add a new member type _item_. # @param item [Type] def <<(item) raise ArgumentError unless item.is_a?(Type) if ![STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype) raise SignatureException end raise SignatureException if @sigtype == ARRAY && !@members.empty? if @sigtype == DICT_ENTRY case @members.size when 2 raise SignatureException, "DICT_ENTRY must have 2 subtypes, found 3 or more: #{@members.inspect} << #{item.inspect}" when 0 if [STRUCT, ARRAY, DICT_ENTRY, VARIANT].member?(item.sigtype) raise SignatureException, "DICT_ENTRY key must be basic (non-container)" end end end @members << item end # Return the first contained member type. def child @members[0] end def inspect s = TYPE_MAPPING[@sigtype].first if [STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype) s += ": #{@members.inspect}" end s end # = D-Bus type parser class # # Helper class to parse a type signature in the protocol. # @api private class Parser # Create a new parser for the given _signature_. # @param signature [Signature] def initialize(signature) @signature = signature if signature.size > 255 msg = "Potential signature is longer than 255 characters (#{@signature.size}): #{@signature}" raise SignatureException, msg end @idx = 0 end # Returns the next character from the signature. def nextchar c = @signature[@idx] @idx += 1 c end # Parse one character _char_ of the signature. # @param for_array [Boolean] are we parsing an immediate child of an ARRAY # @return [Type] def parse_one(char, for_array: false) res = nil case char when "a" res = Type.new(ARRAY) char = nextchar raise SignatureException, "Empty ARRAY in #{@signature}" if char.nil? child = parse_one(char, for_array: true) res << child when "(" res = Type.new(STRUCT, abstract: true) while (char = nextchar) && char != ")" res << parse_one(char) end raise SignatureException, "STRUCT not closed in #{@signature}" if char.nil? raise SignatureException, "Empty STRUCT in #{@signature}" if res.members.empty? when "{" raise SignatureException, "DICT_ENTRY not an immediate child of an ARRAY" unless for_array res = Type.new(DICT_ENTRY, abstract: true) # key type, value type 2.times do |i| char = nextchar raise SignatureException, "DICT_ENTRY not closed in #{@signature}" if char.nil? raise SignatureException, "DICT_ENTRY must have 2 subtypes, found #{i} in #{@signature}" if char == "}" res << parse_one(char) end # closing "}" char = nextchar raise SignatureException, "DICT_ENTRY not closed in #{@signature}" if char.nil? raise SignatureException, "DICT_ENTRY must have 2 subtypes, found 3 or more in #{@signature}" if char != "}" else res = Type.new(char) end res.members.freeze res end # Parse the entire signature, return a DBus::Type object. # @return [Array] def parse @idx = 0 ret = [] while (c = nextchar) ret << parse_one(c) end ret.freeze end # Parse one {SingleCompleteType} # @return [Type] def parse1 c = nextchar raise SignatureException, "Empty signature, expecting a Single Complete Type" if c.nil? t = parse_one(c) raise SignatureException, "Has more than a Single Complete Type: #{@signature}" unless nextchar.nil? t.freeze end end class Factory # @param type [Type,SingleCompleteType,Class] # @see from_plain_class # @return [Type] (frozen) def self.make_type(type) case type when Type type when String DBus.type(type) when Class from_plain_class(type) else msg = "Expecting DBus::Type, DBus::SingleCompleteType(aka ::String), or Class, got #{type.inspect}" raise ArgumentError, msg end end # Make a {Type} corresponding to some plain classes: # - String # - Float # - DBus::ObjectPath # - DBus::Signature, DBus::SingleCompleteType # @param klass [Class] # @return [Type] (frozen) def self.from_plain_class(klass) @signature_type ||= DBus.type(SIGNATURE) @class_to_type ||= { DBus::ObjectPath => DBus.type(OBJECT_PATH), DBus::Signature => @signature_type, DBus::SingleCompleteType => @signature_type, String => DBus.type(STRING), Float => DBus.type(DOUBLE) } t = @class_to_type[klass] raise ArgumentError, "Cannot convert plain class #{klass} to a D-Bus type" if t.nil? t end end # Syntactic helper for constructing an array Type. # You may be looking for {Data::Array} instead. # @example # t = Type::Array[Type::INT16] class ArrayFactory < Factory # @param member_type [Type,SingleCompleteType] # @return [Type] (frozen) def self.[](member_type) t = Type.new(ARRAY) t << make_type(member_type) t.members.freeze t end end # @example # t = Type::Array[Type::INT16] Array = ArrayFactory # Syntactic helper for constructing a hash Type. # You may be looking for {Data::Array} and {Data::DictEntry} instead. # @example # t = Type::Hash[Type::STRING, Type::VARIANT] class HashFactory < Factory # @param key_type [Type,SingleCompleteType] # @param value_type [Type,SingleCompleteType] # @return [Type] (frozen) def self.[](key_type, value_type) t = Type.new(ARRAY) de = Type.new(DICT_ENTRY, abstract: true) de << make_type(key_type) de << make_type(value_type) de.members.freeze t << de t.members.freeze t end end # @example # t = Type::Hash[Type::INT16] Hash = HashFactory # Syntactic helper for constructing a struct Type. # You may be looking for {Data::Struct} instead. # @example # t = Type::Struct[Type::INT16, Type::STRING] class StructFactory < Factory # @param member_types [::Array] # @return [Type] (frozen) def self.[](*member_types) raise ArgumentError if member_types.empty? t = Type.new(STRUCT, abstract: true) member_types.each do |mt| t << make_type(mt) end t.members.freeze t end end # @example # t = Type::Struct[Type::INT16, Type::STRING] Struct = StructFactory end # shortcuts # Parse a String to a valid {DBus::Type}. # This is prefered to {Type#initialize} which allows # incomplete or invalid types. # @param string_type [SingleCompleteType] # @return [DBus::Type] (frozen) # @raise SignatureException def type(string_type) Type::Parser.new(string_type).parse1 end module_function :type # Parse a String to zero or more {DBus::Type}s. # @param string_type [Signature] # @return [Array] (frozen) # @raise SignatureException def types(string_type) Type::Parser.new(string_type).parse end module_function :types # Make an explicit [Type, value] pair # @param string_type [SingleCompleteType] # @param value [::Object] # @return [Array(DBus::Type::Type,::Object)] # @deprecated Use {Data::Variant#initialize} instead def variant(string_type, value) Data::Variant.new(value, member_type: string_type) end module_function :variant end ruby-dbus-0.25.0/spec/0000755000004100000410000000000015000117217014466 5ustar www-datawww-dataruby-dbus-0.25.0/spec/coverage_helper.rb0000644000004100000410000000220015000117217020137 0ustar www-datawww-data# frozen_string_literal: true coverage = if ENV["COVERAGE"] ENV["COVERAGE"] == "true" else # heuristics: enable for interactive builds (but not in OBS) ENV["DISPLAY"] end if coverage require "simplecov" SimpleCov.root File.expand_path("..", __dir__) # do not cover specs SimpleCov.add_filter "_spec.rb" # do not cover the activesupport helpers SimpleCov.add_filter "/core_ext/" # measure all if/else branches on a line SimpleCov.enable_coverage :branch SimpleCov.start # additionally use the LCOV format for on-line code coverage reporting at CI if ENV["COVERAGE_LCOV"] == "true" require "simplecov-lcov" SimpleCov::Formatter::LcovFormatter.config do |c| c.report_with_single_file = true # this is the default Coveralls GitHub Action location # https://github.com/marketplace/actions/coveralls-github-action c.single_report_path = "coverage/lcov.info" end SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [ SimpleCov::Formatter::HTMLFormatter, SimpleCov::Formatter::LcovFormatter ] end end ruby-dbus-0.25.0/spec/dbus_spec.rb0000755000004100000410000000100415000117217016760 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus do describe ".session_bus", tag_bus: true do it "returns a BusConnection" do expect(DBus.session_bus).to be_a(DBus::BusConnection) end end describe ".system_bus" do # coverage obsession: mock it out, # system bus may not exist during RPM builds it "calls SystemBus.instance" do expect(DBus::SystemBus).to receive(:instance) DBus.system_bus end end end ruby-dbus-0.25.0/spec/variant_spec.rb0000755000004100000410000000374415000117217017504 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true # 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(val) DBus::PacketMarshaller.make_variant(val) 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.25.0/spec/introspect_xml_parser_spec.rb0000755000004100000410000000127215000117217022460 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe "IntrospectXMLParserTest" do it "tests split interfaces" do xml = <<-XML XML 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.25.0/spec/bus_connection_spec.rb0000755000004100000410000000652715000117217021052 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::BusConnection do let(:bus) { DBus::ASessionBus.new } # deprecated method describe "#request_service", tag_bus: true, tag_deprecated: true do context "when the name request succeeds" do # Formerly it returned Service, now ObjectServer takes its role. # Replacement: server = bus.object_server; bus.request_name(name) it "returns something which can export objects" do name = "org.rubygems.ruby_dbus.RequestServiceTest" server = bus.request_service(name) expect(server).to respond_to(:export) bus.proxy.ReleaseName(name) end end context "when the name is taken already", tag_service: true do it "raises NameRequestError... too late" do name = "org.ruby.service" expect do bus.request_service(name) _unrelated_call = bus.proxy.GetId.first end.to raise_error(DBus::Connection::NameRequestError) # The call fails but it means we did not get the name RIGHT AWAY # but we are still queued to get it as soon as the current owner # gives it up. # So even now we have to the bus to remove us from the queue bus.proxy.ReleaseName(name) end end # This only works with our special bus setup context "when we're not allowed to own the name", tag_limited_bus: true do it "raises an error... too late" do name = "org.rubygems.ruby_dbus.NobodyCanOwnThisName" expect do bus.request_service(name) _unrelated_call = bus.proxy.GetId.first end.to raise_error(DBus::Error, /not allowed to own the service/) end end end describe "#request_name", tag_bus: true do context "when the name request succeeds" do it "returns a success code" do name = "org.rubygems.ruby_dbus.RequestNameTest" expect(bus.request_name(name)).to eq DBus::Connection::REQUEST_NAME_REPLY_PRIMARY_OWNER # second time, considered also a success expect(bus.request_name(name)).to eq DBus::Connection::REQUEST_NAME_REPLY_ALREADY_OWNER bus.release_name(name) end end context "when the name is taken already", tag_service: true do it "raises NameRequestError" do name = "org.ruby.service" expect do bus.request_name(name) end.to raise_error(DBus::Connection::NameRequestError) end end context "when the name is taken already but we request queuing", tag_service: true do it "raises NameRequestError but we are queued" do name = "org.ruby.service" owning = nil # TODO: we do not expect the handlers to run bus.on_name_acquired { owning = true } bus.on_name_lost { owning = false } expect do bus.request_name(name, queue: true) end.to raise_error(DBus::Connection::NameRequestError) expect(bus.release_name(name)).to eq DBus::BusConnection::RELEASE_NAME_REPLY_RELEASED end end context "when we're not allowed to own the name", tag_limited_bus: true do it "raises an error" do name = "org.rubygems.ruby_dbus.NobodyCanOwnThisName" expect do bus.request_name(name) end.to raise_error(DBus::Error, /not allowed to own the service/) end end end end ruby-dbus-0.25.0/spec/data/0000755000004100000410000000000015000117217015377 5ustar www-datawww-dataruby-dbus-0.25.0/spec/data/marshall.yaml0000644000004100000410000005136615000117217020101 0ustar www-datawww-data--- # Test data for marshalling and unmarshalling of D-Bus values. # The intent is to be implementation independent. # More importantly, it should work both ways, for marshalling and unmarshalling. # # This file is a list of test cases. # # Each test case is a dictionary: # - sig: the signature of the data # - end: endianness of the byte buffer ("big" or "little") # - buf: the byte buffer. Logically it is an array of bytes but YAML # would write that in base-64, obscuring the contents. # So we write it as nested lists of integers (bytes), or UTF-8 strings. # The nesting only matters for test case readability, the data is # flattened before use. # - val: the unmarshalled value (for valid buffers) # - exc: exception name (for invalid buffers) # - msg: exception message substring (for invalid buffers) # - marshall: true (default) or false, # - unmarshall: true (default) or false, for test cases that only work one way # or are expected to fail - sig: "y" end: little buf: - 0 val: 0 - sig: "y" end: little buf: - 128 val: 128 - sig: "y" end: little buf: - 255 val: 255 - sig: "y" end: big buf: - 0 val: 0 - sig: "y" end: big buf: - 128 val: 128 - sig: "y" end: big buf: - 255 val: 255 - sig: b end: little buf: [1, 0, 0, 0] val: true - sig: b end: little buf: [0, 0, 0, 0] val: false - sig: b end: big buf: [0, 0, 0, 1] val: true - sig: b end: big buf: [0, 0, 0, 0] val: false - sig: b end: little buf: - 0 - 255 - 255 - 0 exc: DBus::InvalidPacketException msg: BOOLEAN must be 0 or 1, found - sig: b end: big buf: - 0 - 255 - 255 - 0 exc: DBus::InvalidPacketException msg: BOOLEAN must be 0 or 1, found - sig: "n" end: little buf: - 0 - 0 val: 0 - sig: "n" end: little buf: - 255 - 127 val: 32767 - sig: "n" end: little buf: - 0 - 128 val: -32768 - sig: "n" end: little buf: - 255 - 255 val: -1 - sig: "n" end: big buf: - 0 - 0 val: 0 - sig: "n" end: big buf: - 127 - 255 val: 32767 - sig: "n" end: big buf: - 128 - 0 val: -32768 - sig: "n" end: big buf: - 255 - 255 val: -1 - sig: q end: little buf: - 0 - 0 val: 0 - sig: q end: little buf: - 255 - 127 val: 32767 - sig: q end: little buf: - 0 - 128 val: 32768 - sig: q end: little buf: - 255 - 255 val: 65535 - sig: q end: big buf: - 0 - 0 val: 0 - sig: q end: big buf: - 127 - 255 val: 32767 - sig: q end: big buf: - 128 - 0 val: 32768 - sig: q end: big buf: - 255 - 255 val: 65535 - sig: i end: little buf: - 0 - 0 - 0 - 0 val: 0 - sig: i end: little buf: - 255 - 255 - 255 - 127 val: 2147483647 - sig: i end: little buf: - 0 - 0 - 0 - 128 val: -2147483648 - sig: i end: little buf: - 255 - 255 - 255 - 255 val: -1 - sig: i end: big buf: - 0 - 0 - 0 - 0 val: 0 - sig: i end: big buf: - 127 - 255 - 255 - 255 val: 2147483647 - sig: i end: big buf: - 128 - 0 - 0 - 0 val: -2147483648 - sig: i end: big buf: - 255 - 255 - 255 - 255 val: -1 - sig: u end: little buf: - 0 - 0 - 0 - 0 val: 0 - sig: u end: little buf: - 255 - 255 - 255 - 127 val: 2147483647 - sig: u end: little buf: - 0 - 0 - 0 - 128 val: 2147483648 - sig: u end: little buf: - 255 - 255 - 255 - 255 val: 4294967295 - sig: u end: big buf: - 0 - 0 - 0 - 0 val: 0 - sig: u end: big buf: - 127 - 255 - 255 - 255 val: 2147483647 - sig: u end: big buf: - 128 - 0 - 0 - 0 val: 2147483648 - sig: u end: big buf: - 255 - 255 - 255 - 255 val: 4294967295 - sig: h end: little buf: - 0 - 0 - 0 - 0 val: 0 - sig: h end: little buf: - 255 - 255 - 255 - 127 val: 2147483647 - sig: h end: little buf: - 0 - 0 - 0 - 128 val: 2147483648 - sig: h end: little buf: - 255 - 255 - 255 - 255 val: 4294967295 - sig: h end: big buf: - 0 - 0 - 0 - 0 val: 0 - sig: h end: big buf: - 127 - 255 - 255 - 255 val: 2147483647 - sig: h end: big buf: - 128 - 0 - 0 - 0 val: 2147483648 - sig: h end: big buf: - 255 - 255 - 255 - 255 val: 4294967295 - sig: x end: little buf: - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 val: 0 - sig: x end: little buf: - 255 - 255 - 255 - 255 - 255 - 255 - 255 - 127 val: 9223372036854775807 - sig: x end: little buf: - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 128 val: -9223372036854775808 - sig: x end: little buf: - 255 - 255 - 255 - 255 - 255 - 255 - 255 - 255 val: -1 - sig: x end: big buf: - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 val: 0 - sig: x end: big buf: - 127 - 255 - 255 - 255 - 255 - 255 - 255 - 255 val: 9223372036854775807 - sig: x end: big buf: - 128 - 0 - 0 - 0 - 0 - 0 - 0 - 0 val: -9223372036854775808 - sig: x end: big buf: - 255 - 255 - 255 - 255 - 255 - 255 - 255 - 255 val: -1 - sig: t end: little buf: - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 val: 0 - sig: t end: little buf: - 255 - 255 - 255 - 255 - 255 - 255 - 255 - 127 val: 9223372036854775807 - sig: t end: little buf: - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 128 val: 9223372036854775808 - sig: t end: little buf: - 255 - 255 - 255 - 255 - 255 - 255 - 255 - 255 val: 18446744073709551615 - sig: t end: big buf: - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 val: 0 - sig: t end: big buf: - 127 - 255 - 255 - 255 - 255 - 255 - 255 - 255 val: 9223372036854775807 - sig: t end: big buf: - 128 - 0 - 0 - 0 - 0 - 0 - 0 - 0 val: 9223372036854775808 - sig: t end: big buf: - 255 - 255 - 255 - 255 - 255 - 255 - 255 - 255 val: 18446744073709551615 - sig: d end: little buf: - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 val: 0.0 - sig: d end: little buf: - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 128 val: -0.0 - sig: d end: little buf: - 0 - 0 - 0 - 0 - 0 - 0 - 0 - "@" val: 2.0 - sig: d end: big buf: - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 val: 0.0 - sig: d end: big buf: - 128 - 0 - 0 - 0 - 0 - 0 - 0 - 0 val: -0.0 - sig: d end: big buf: - "@" - 0 - 0 - 0 - 0 - 0 - 0 - 0 val: 2.0 - sig: s end: little buf: - 0 - 0 - 0 - 0 - 0 val: '' - sig: s end: little buf: - 2 - 0 - 0 - 0 - 197 - 152 - 0 val: Ř - sig: s end: little buf: - 3 - 0 - 0 - 0 - 239 - 191 - 191 - 0 val: "\uFFFF" - sig: s end: big buf: - 0 - 0 - 0 - 0 - 0 val: '' - sig: s end: big buf: - 0 - 0 - 0 - 2 - 197 - 152 - 0 val: Ř - sig: s end: big buf: - 0 - 0 - 0 - 3 - 239 - 191 - 191 - 0 val: "\uFFFF" - sig: s end: big buf: - 0 - 0 - 0 - 4 - 244 - 143 - 191 - 191 - 0 val: "\U0010FFFF" - sig: s end: little buf: - 0 - 0 - 0 - 0 - U exc: DBus::InvalidPacketException msg: not NUL-terminated - sig: s end: little buf: - 1 - 0 - 0 - 0 - "@U" exc: DBus::InvalidPacketException msg: not NUL-terminated - sig: s end: little buf: - 0 - 0 - 0 - 0 exc: DBus::IncompleteBufferException msg: '' - sig: s end: little buf: - 0 - 0 - 0 exc: DBus::IncompleteBufferException msg: '' - sig: s end: little buf: - 0 - 0 exc: DBus::IncompleteBufferException msg: '' - sig: s end: little buf: - 0 exc: DBus::IncompleteBufferException msg: '' # NUL in the middle - sig: s end: little buf: - 3 - 0 - 0 - 0 - a - 0 - b - 0 exc: DBus::InvalidPacketException msg: Invalid string # invalid UTF-8 - sig: s end: little buf: - 4 - 0 - 0 - 0 - 255 - 255 - 255 - 255 - 0 exc: DBus::InvalidPacketException msg: Invalid string # overlong sequence encoding an "A" - sig: s end: little buf: - 2 - 0 - 0 - 0 - 0xC1 - 0x81 - 0 exc: DBus::InvalidPacketException msg: Invalid string # first codepoint outside UTF-8, U+110000 - sig: s end: little buf: - 4 - 0 - 0 - 0 - 0xF4 - 0x90 - 0xC0 - 0xC0 - 0 exc: DBus::InvalidPacketException msg: Invalid string - sig: o end: little buf: - 1 - 0 - 0 - 0 - "/" - 0 val: "/" - sig: o end: little buf: - 32 - 0 - 0 - 0 - "/99Numbers/_And_Underscores/anyw" - 0 val: "/99Numbers/_And_Underscores/anyw" # no size limit like for other names; 512 characters are fine - sig: o end: little buf: - [0, 2, 0, 0] - "/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - 0 val: "/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - sig: o end: big buf: - 0 - 0 - 0 - 1 - "/" - 0 val: "/" - sig: o end: big buf: - 0 - 0 - 0 - " /99Numbers/_And_Underscores/anyw" - 0 val: "/99Numbers/_And_Underscores/anyw" - sig: o end: little buf: - 0 - 0 - 0 - 0 - U exc: DBus::InvalidPacketException msg: not NUL-terminated - sig: o end: little buf: - 1 - 0 - 0 - 0 - "/U" exc: DBus::InvalidPacketException msg: not NUL-terminated - sig: o end: little buf: - 0 - 0 - 0 - 0 exc: DBus::IncompleteBufferException msg: '' - sig: o end: little buf: - 0 - 0 - 0 exc: DBus::IncompleteBufferException msg: '' - sig: o end: little buf: - 0 - 0 exc: DBus::IncompleteBufferException msg: '' - sig: o end: little buf: - 0 exc: DBus::IncompleteBufferException msg: '' - sig: o end: little buf: - 0 - 0 - 0 - 0 - 0 exc: DBus::InvalidPacketException msg: Invalid object path - sig: o end: big buf: - 0 - 0 - 0 - 0 - 0 exc: DBus::InvalidPacketException msg: Invalid object path - sig: o end: big buf: - 0 - 0 - 0 - 5 - "/_//_" - 0 exc: DBus::InvalidPacketException msg: Invalid object path - sig: o end: big buf: - 0 - 0 - 0 - 5 - "/_/_/" - 0 exc: DBus::InvalidPacketException msg: Invalid object path - sig: o end: big buf: - 0 - 0 - 0 - 5 - "/_/_ " - 0 exc: DBus::InvalidPacketException msg: Invalid object path - sig: o end: big buf: - 0 - 0 - 0 - 5 - "/_/_-" - 0 exc: DBus::InvalidPacketException msg: Invalid object path # NUL in the middle - sig: o end: big buf: - 0 - 0 - 0 - 5 - "/_/_" - 0 - 0 exc: DBus::InvalidPacketException msg: Invalid object path # accented a - sig: o end: big buf: - 0 - 0 - 0 - 5 - "/_/" - 195 - 161 - 0 exc: DBus::InvalidPacketException msg: Invalid object path - sig: g end: little buf: - 0 - 0 val: '' - sig: g end: big buf: - 0 - 0 val: '' - sig: g end: little buf: - 1 - b - 0 val: b - sig: g end: big buf: - 1 - b - 0 val: b - sig: g end: big buf: - 0 - U exc: DBus::InvalidPacketException msg: not NUL-terminated - sig: g end: big buf: - 1 - bU exc: DBus::InvalidPacketException msg: not NUL-terminated - sig: g end: little buf: - 0 exc: DBus::IncompleteBufferException msg: '' - sig: g end: big buf: - 1 - "!" - 0 exc: DBus::InvalidPacketException msg: Invalid signature - sig: g end: big buf: - 1 - r - 0 exc: DBus::InvalidPacketException msg: Invalid signature - sig: g end: big buf: - 2 - ae - 0 exc: DBus::InvalidPacketException msg: Invalid signature - sig: g end: big buf: - 1 - a - 0 exc: DBus::InvalidPacketException msg: Invalid signature # dict_entry with other than 2 members - sig: g end: big buf: - 3 - a{} - 0 exc: DBus::InvalidPacketException msg: Invalid signature - sig: g end: big buf: - 4 - a{s} - 0 exc: DBus::InvalidPacketException msg: Invalid signature - sig: g end: big buf: - 6 - a{sss} - 0 exc: DBus::InvalidPacketException msg: Invalid signature # dict_entry with non-basic key - sig: g end: big buf: - 5 - a{vs} - 0 exc: DBus::InvalidPacketException msg: Invalid signature # dict_entry outside array - sig: g end: big buf: - 4 - "{sv}" - 0 exc: DBus::InvalidPacketException msg: Invalid signature # dict_entry not immediately in an array - sig: g end: big buf: - 7 - a({sv}) - 0 exc: DBus::InvalidPacketException msg: Invalid signature # NUL in the middle - sig: g end: big buf: - 3 - a - 0 - "y" - 0 exc: DBus::InvalidPacketException msg: Invalid signature # ARRAYs # marshalling format: # - (alignment of data_bytes) # - UINT32 data_bytes (without any alignment padding) # - (alignment of ITEM_TYPE, even if the array is empty) # - ITEM_TYPE item1 # - (alignment of ITEM_TYPE) # - ITEM_TYPE item2... # Here we repeat the STRINGs test data (without the trailing NUL) # but the outcomes are different - sig: ay end: little buf: - 0 - 0 - 0 - 0 val: [] - sig: ay end: little buf: - 2 - 0 - 0 - 0 - 197 - 152 val: - 197 - 152 - sig: ay end: little buf: - 3 - 0 - 0 - 0 - 239 - 191 - 191 val: - 239 - 191 - 191 - sig: ay end: big buf: - 0 - 0 - 0 - 0 val: [] - sig: ay end: big buf: - 0 - 0 - 0 - 2 - 197 - 152 val: - 197 - 152 - sig: ay end: big buf: - 0 - 0 - 0 - 3 - 239 - 191 - 191 val: - 239 - 191 - 191 - sig: ay end: big buf: - 0 - 0 - 0 - 4 - 244 - 143 - 191 - 191 val: - 244 - 143 - 191 - 191 - sig: ay end: little buf: - 3 - 0 - 0 - 0 - a - 0 - b val: - 97 - 0 - 98 - sig: ay end: little buf: - 4 - 0 - 0 - 0 - 255 - 255 - 255 - 255 val: - 255 - 255 - 255 - 255 - sig: ay end: little buf: - 2 - 0 - 0 - 0 - 193 - 129 val: - 193 - 129 - sig: ay end: little buf: - 4 - 0 - 0 - 0 - 244 - 144 - 192 - 192 val: - 244 - 144 - 192 - 192 # With basic types, by the time we have found the message to be invalid, # it is nevertheless well-formed and we could read the next message. # However, an overlong array (body longer than 64MiB) is a good enough # reason to drop the connection, which is what InvalidPacketException # does, right? Doesn't it? # Well it does, by crashing the entire process. # That should be made more graceful. - sig: ay end: little buf: - 1 - 0 - 0 - 4 exc: DBus::InvalidPacketException msg: ARRAY body longer than 64MiB - sig: ay end: little buf: - 2 - 0 - 0 - 0 - 170 exc: DBus::IncompleteBufferException msg: '' - sig: ay end: little buf: - 0 - 0 - 0 exc: DBus::IncompleteBufferException msg: '' - sig: ay end: little buf: - 0 - 0 exc: DBus::IncompleteBufferException msg: '' - sig: ay end: little buf: - 0 exc: DBus::IncompleteBufferException msg: '' - sig: at end: little buf: # body size - [0, 0, 0, 0] # padding - [0, 0, 0, 0] val: [] - sig: at end: little buf: # body size - [16, 0, 0, 0] # padding - [0, 0, 0, 0] # item - [1, 0, 0, 0, 0, 0, 0, 0] # item - [2, 0, 0, 0, 0, 0, 0, 0] val: - 1 - 2 - sig: at end: little buf: # body size, missing padding - [0, 0, 0, 0] exc: DBus::IncompleteBufferException msg: '' - sig: at end: little buf: # body size - [0, 0, 0, 0] # nonzero padding - [0xDE, 0xAD, 0xBE, 0xEF] exc: DBus::InvalidPacketException msg: '' - sig: at end: little buf: # body size - [8, 0, 0, 0] # padding - [0, 0, 0, 0] # incomplete item - 170 exc: DBus::IncompleteBufferException msg: '' # arrays of nontrivial types let us demonstrate the padding of their elements - sig: a(qq) end: little buf: # body size - [0, 0, 0, 0] # padding - [0, 0, 0, 0] val: [] - sig: a(qq) end: little buf: # body size - [12, 0, 0, 0] # padding - [0, 0, 0, 0] # item - [1, 0, 2, 0] # padding - [0, 0, 0, 0] # item - [3, 0, 4, 0] val: - - 1 - 2 - - 3 - 4 # This illustrates that the specification is wrong in asserting that # the body size is divisible by the item count - sig: a(qq) end: little buf: # body size - [20, 0, 0, 0] # padding - [0, 0, 0, 0] # item - [5, 0, 6, 0] # padding - [0, 0, 0, 0] # item - [7, 0, 8, 0] # padding - [0, 0, 0, 0] # item - [9, 0, 10, 0] val: - - 5 - 6 - - 7 - 8 - - 9 - 10 - sig: a(qq) end: little buf: # body size, missing padding - [0, 0, 0, 0] exc: DBus::IncompleteBufferException msg: '' - sig: a(qq) end: little buf: # body size - [0, 0, 0, 0] # nonzero padding - [0xDE, 0xAD, 0xBE, 0xEF] exc: DBus::InvalidPacketException msg: '' - sig: a{yq} end: little buf: # body size - [0, 0, 0, 0] # padding - [0, 0, 0, 0] val: {} - sig: a{yq} end: little buf: # body size - [12, 0, 0, 0] # dict_entry padding - [0, 0, 0, 0] # key, padding, value - [1, 0, 2, 0] # dict_entry padding - [0, 0, 0, 0] # key, padding, value - [3, 0, 4, 0] val: 1: 2 3: 4 - sig: a{yq} end: big buf: # body size - [0, 0, 0, 12] # dict_entry padding - [0, 0, 0, 0] # key, padding, value - [1, 0, 0, 2] # dict_entry padding - [0, 0, 0, 0] # key, padding, value - [3, 0, 0, 4] val: 1: 2 3: 4 - sig: a{yq} end: little buf: # body size, missing padding - [0, 0, 0, 0] exc: DBus::IncompleteBufferException msg: '' - sig: a{yq} end: little buf: # body size - [0, 0, 0, 0] # nonzero padding - [0xDE, 0xAD, 0xBE, 0xEF] exc: DBus::InvalidPacketException msg: '' - sig: a{oq} end: little buf: # body size - [0, 0, 0, 0] # padding - [0, 0, 0, 0] val: {} - sig: a{oq} end: little buf: # body size - [26, 0, 0, 0] # dict_entry padding - [0, 0, 0, 0] # key, padding, value - [2, 0, 0, 0, "/7", 0] - 0 - [7, 0] # dict_entry padding - [0, 0, 0, 0, 0, 0] # key, padding, value - [2, 0, 0, 0, "/9", 0] - 0 - [9, 0] val: /7: 7 /9: 9 - sig: "(qq)" end: little buf: - 1 - 0 - 2 - 0 val: - 1 - 2 - sig: "(qq)" end: big buf: - 0 - 3 - 0 - 4 val: - 3 - 4 - sig: v end: little buf: # signature - [1, "y", 0] # value - 255 val: 255 marshall: false - sig: v end: little buf: # signature - [1, "u", 0] # padding - 0 # value - [1, 0, 0, 0] val: 1 marshall: false # nested variant - sig: v end: little buf: # signature - [1, "v", 0] # value: # signature - [1, "y", 0] # value - 255 val: 255 marshall: false # the signature has no type - sig: v end: little buf: # signature - [0, 0] exc: DBus::InvalidPacketException msg: 1 value, 0 found # the signature has more than one type - sig: v end: little buf: # signature - [2, "yy", 0] # data - 255 - 255 exc: DBus::InvalidPacketException msg: 1 value, 2 found # a variant nested 69 levels - sig: v end: little buf: - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "v", 0, 1, "v", 0, 1, "v", 0, 1, "v", 0] - [1, "y", 0] - 255 exc: DBus::InvalidPacketException msg: nested too deep unmarshall: false ruby-dbus-0.25.0/spec/binding_spec.rb0000755000004100000410000000472315000117217017450 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true # 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 raise_error(DBus::Error) do |e| expect(e.message).to match(/no such interface/) end # and the parent should not get polluted by the child expect { @base["org.ruby.Test2"] }.to raise_error(DBus::Error) do |e| expect(e.message).to match(/no such interface/) end 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.25.0/spec/emits_changed_signal_spec.rb0000755000004100000410000000320315000117217022155 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::EmitsChangedSignal do describe "#initialize" do it "accepts a simple value" do expect(described_class.new(:const).value).to eq :const end it "avoids nil by asking the interface" do ifc = DBus::Interface.new("org.example.Foo") ifc.emits_changed_signal = described_class.new(:invalidates) expect(described_class.new(nil, interface: ifc).value).to eq :invalidates end it "fails for unknown value" do expect { described_class.new(:huh) }.to raise_error(ArgumentError, /Seen :huh/) end it "fails for 2 nils" do expect { described_class.new(nil, interface: nil) }.to raise_error(ArgumentError, /Both/) end end describe "#==" do it "is true for two different objects with the same value" do const_a = described_class.new(:const) const_b = described_class.new(:const) expect(const_a == const_b).to be true end end describe "#to_xml" do it "uses a string value" do expect(described_class.new(:const).to_xml) .to eq " \n" end end describe "#to_s" do it "uses a string value" do expect(described_class.new(:const).to_s).to eq "const" end end end describe DBus::Interface do describe ".emits_changed_signal=" do it "only allows an EmitsChangedSignal as argument" do ifc = described_class.new("org.ruby.Interface") expect { ifc.emits_changed_signal = :const }.to raise_error(TypeError) end end end ruby-dbus-0.25.0/spec/spec_helper.rb0000644000004100000410000000460715000117217017313 0ustar www-datawww-data# frozen_string_literal: true require_relative "coverage_helper" $LOAD_PATH.unshift File.expand_path("../lib", __dir__) # http://betterspecs.org/#expect RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = :expect end end require "tempfile" require "timeout" TOPDIR = File.expand_path("..", __dir__) # 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/mock-service/spaghetti-monster.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 = <<-TEXT.gsub(/^\s*/, "") [D-BUS Service] Name=#{name} Exec=#{exec} TEXT f.write(s) end block.call # This would kill also other instances, # namely on the bus set up by test_env. ## system "pkill -f #{exec}" end # Make a binary string from readable YAML pieces; see data/marshall.yaml def buffer_from_yaml(parts) strings = parts.flatten.map do |part| if part.is_a? Integer part.chr else part end end strings.join.force_encoding(Encoding::BINARY) end ruby-dbus-0.25.0/spec/object_manager_spec.rb0000755000004100000410000000201715000117217020770 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::ObjectManager do describe "GetManagedObjects" do let(:bus) { DBus::ASessionBus.new } let(:service) { bus["org.ruby.service"] } let(:obj) { service["/org/ruby/MyInstance"] } let(:parent_iface) { obj["org.ruby.TestParent"] } let(:om_iface) { obj["org.freedesktop.DBus.ObjectManager"] } it "returns the interfaces and properties of currently managed objects" do c1_opath = parent_iface.New("child1") c2_opath = parent_iface.New("child2") parent_iface.Delete(c1_opath) expected_gmo = { "/org/ruby/MyInstance/child2" => { "org.freedesktop.DBus.Introspectable" => {}, "org.freedesktop.DBus.Properties" => {}, "org.ruby.TestChild" => { "Name" => "Child2" } } } expect(om_iface.GetManagedObjects).to eq(expected_gmo) parent_iface.Delete(c2_opath) expect(om_iface.GetManagedObjects).to eq({}) end end end ruby-dbus-0.25.0/spec/mock-service/0000755000004100000410000000000015000117217017055 5ustar www-datawww-dataruby-dbus-0.25.0/spec/mock-service/com.redhat.Cockpit.DBusTests.xml0000644000004100000410000001446315000117217025105 0ustar www-datawww-data ruby-dbus-0.25.0/spec/mock-service/org.rubygems.ruby_dbus.DBusTests.service0000644000004100000410000000021715000117217026736 0ustar www-datawww-data[D-BUS Service] Name=org.rubygems.ruby_dbus.DBusTests # ... will replace the directory with the current one Exec=/usr/bin/cockpit-dbustests.rb ruby-dbus-0.25.0/spec/mock-service/org.ruby.service.service0000644000004100000410000000017715000117217023652 0ustar www-datawww-data[D-BUS Service] Name=org.ruby.service # ... will replace the directory with the current one Exec=/usr/bin/spaghetti-monster.rb ruby-dbus-0.25.0/spec/mock-service/spaghetti-monster.rb0000755000004100000410000001667515000117217023101 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true # This file was formerly named spec/service_newapi.rb, after the example # which it mutated from. # Spaghetti monster is a better name, # reflecting on its evolution and current nature :'-) require_relative "../coverage_helper" SimpleCov.command_name "Service Tests (#{Process.pid})" if Object.const_defined? "SimpleCov" # find the library without external help $LOAD_PATH.unshift File.expand_path("../../lib", __dir__) require "dbus" SERVICE_NAME = "org.ruby.service" class TestChild < DBus::Object def initialize(opath) @name = opath.split("/").last.capitalize super end dbus_interface "org.ruby.TestChild" do dbus_reader_attr_accessor :name, "s" end end class Test < DBus::Object Point2D = Struct.new(:x, :y) attr_writer :main_loop include DBus::ObjectManager INTERFACE = "org.ruby.SampleInterface" def initialize(path) super path @read_me = "READ ME" @read_or_write_me = "READ OR WRITE ME" @my_struct = ["three", "strings", "in a struct"].freeze @my_array = [42, 43] @my_dict = { "one" => 1, "two" => "dva", "three" => [3, 3, 3] } @my_variant = @my_array.dup # 201 is a RET instruction for ZX Spectrum which has turned 40 recently @my_byte = 201 @main_loop = nil end # Create an interface aggregating all upcoming dbus_method defines. dbus_interface INTERFACE do dbus_method :quit, "" do @main_loop&.quit end 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 dbus_method :Coordinates, "out coords:(dd)" do coords = [3.0, 4.0].freeze [coords] end dbus_method :Coordinates2, "out coords:(dd)" do coords = Point2D.new(5.0, 12.0) [coords] end # Two OUT arguments dbus_method :EvenOdd, "in numbers:ai, out even:ai, out odd:ai" do |numbers| even, odd = numbers.partition(&:even?) [even, odd] 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_attr_accessor :read_or_write_me, "s" dbus_attr_reader :read_me, "s" def write_me=(value) raise "We don't talk about Bruno" if value =~ /Bruno/ @read_me = value end dbus_writer :write_me, "s" dbus_attr_writer :password, "s" # a property that raises when client tries to read it def explosive raise "Something failed" end dbus_reader :explosive, "s" dbus_attr_accessor :my_struct, "(sss)" dbus_attr_accessor :my_array, "aq" dbus_attr_accessor :my_dict, "a{sv}" dbus_attr_accessor :my_variant, "v" dbus_attr_accessor :my_byte, "y" # to test dbus_properties_changed dbus_method :SetTwoProperties, "in read_me:s, in byte:y" do |read_me, byte| @read_me = read_me @my_byte = byte dbus_properties_changed(INTERFACE, { "ReadMe" => read_me, "MyByte" => byte }, []) end end # closing and reopening the same interface dbus_interface INTERFACE do dbus_method :multibyte_string, "out string:s" do "あいうえお" end dbus_method :i16_plus, "in a:n, in b:n, out result:n" do |a, b| a + b 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.TestParent" do dbus_method :New, "in name:s, out opath:o" do |name| child = TestChild.new("#{path}/#{name}") object_server.export(child) [child.path] end dbus_method :Delete, "in opath:o" do |opath| raise ArgumentError unless opath.start_with?(path) object_server.unexport(opath) 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 # 'Shadowed' from the Ruby side, meaning ProxyObject#interfaces # will return the list of interfaces rather than calling this method. # Calling it with busctl will work just fine. 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 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.object_server 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 bus.request_name(SERVICE_NAME) # 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 DBus.logger.info "Service #{SERVICE_NAME} listening, with ruby-#{RUBY_VERSION}" main = DBus::Main.new main << bus myobj.main_loop = main begin main.run rescue SystemCallError, SignalException => e DBus.logger.info "Service #{SERVICE_NAME} got #{e.inspect}, exiting" # the test driver will kill the bus, that's OK end ruby-dbus-0.25.0/spec/mock-service/cockpit-dbustests.rb0000755000004100000410000000163715000117217023066 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true require_relative "../coverage_helper" SimpleCov.command_name "Cockpit Tests (#{Process.pid})" if Object.const_defined? "SimpleCov" # find the library without external help $LOAD_PATH.unshift File.expand_path("../../lib", __dir__) require "dbus" SERVICE_NAME = "org.rubygems.ruby_dbus.DBusTests" ROOT_OPATH = "/otree/frobber" class DBusTests < DBus::Object FROBBER_INTERFACE = "com.redhat.Cockpit.DBusTests.Frobber" dbus_interface FROBBER_INTERFACE do dbus_method :HelloWorld, "in greeting:s, out response:s" do |greeting| # TODO: return the same thing as the original implementation # and try substituting it? [format("Word! You said `%s'. I'm Skeleton, btw!", greeting)] end end end bus = DBus::SessionBus.instance bus.object_server.export(DBusTests.new(ROOT_OPATH)) bus.request_name(SERVICE_NAME) DBus::Main.new.tap { |m| m << bus }.run ruby-dbus-0.25.0/spec/proxy_object_interface_spec.rb0000755000004100000410000000206115000117217022556 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::ProxyObjectInterface do # TODO: tag tests that need a service, eg "needs-service" # TODO: maybe remove this and rely on a packaged tool 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"] } # This is white box testing, knowing the implementation # A better way would be structuring it according to the D-Bus Spec # Or testing the service side doing the right thing? (What if our bugs cancel out) describe "#define_method_from_descriptor" do it "can call a method with multiple OUT arguments" do obj = svc["/org/ruby/MyInstance"] ifc = obj["org.ruby.SampleInterface"] even, odd = ifc.EvenOdd([3, 1, 4, 1, 5, 9, 2, 6]) expect(even).to eq [4, 2, 6] expect(odd).to eq [3, 1, 1, 5, 9] end end end end ruby-dbus-0.25.0/spec/byte_array_spec.rb0000755000004100000410000000200515000117217020166 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true 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.25.0/spec/proxy_object_spec.rb0000755000004100000410000000272015000117217020540 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true 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 describe "#[]" do it "raises when the interface is not found" do obj = svc["/org/ruby/MyInstance"] expect { obj["org.ruby.NoSuchInterface"] }.to raise_error(DBus::Error) do |e| expect(e.message).to match(/no such interface/) end end end end end ruby-dbus-0.25.0/spec/packet_marshaller_spec.rb0000755000004100000410000000236515000117217021517 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" require "ostruct" require "yaml" data_dir = File.expand_path("data", __dir__) marshall_yaml_s = File.read("#{data_dir}/marshall.yaml") marshall_yaml = YAML.safe_load(marshall_yaml_s) describe DBus::PacketMarshaller do context "marshall.yaml" do marshall_yaml.each do |test| t = OpenStruct.new(test) next if t.marshall == false # skip test cases for invalid unmarshalling next if t.val.nil? # while the marshaller can use only native endianness, skip the other endianness = t.end.to_sym signature = t.sig expected = buffer_from_yaml(t.buf) it "writes a '#{signature}' with value #{t.val.inspect} (#{endianness})" do subject = described_class.new(endianness: endianness) subject.append(signature, t.val) expect(subject.packet).to eq(expected) end it "writes a '#{signature}' with typed value #{t.val.inspect} (#{endianness})" do subject = described_class.new(endianness: endianness) typed_val = DBus::Data.make_typed(signature, t.val) subject.append(signature, typed_val) expect(subject.packet).to eq(expected) end end end end ruby-dbus-0.25.0/spec/client_robustness_spec.rb0000755000004100000410000000146115000117217021577 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true # Test that a client survives various error cases require_relative "spec_helper" require "dbus" describe "ClientRobustnessTest" do before(:each) do @bus = DBus::ASessionBus.new @svc = @bus.service("org.ruby.service") end context "when the bus name is invalid" do it "tells the user the bus name is invalid" do # user mistake, should be "org.ruby.service" expect { @bus.service(".org.ruby.service") }.to raise_error(DBus::Error, /Invalid bus name/) end end context "when the object path is invalid" do it "tells the user the path is invalid" do # user mistake, should be "/org/ruby/MyInstance" expect { @svc.object("org.ruby.MyInstance") }.to raise_error(DBus::Error, /Invalid object path/) end end end ruby-dbus-0.25.0/spec/data_spec.rb0000755000004100000410000005467615000117217016763 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" # The from_raw methods are tested in packet_unmarshaller_spec.rb RSpec.shared_examples "#== and #eql? work for basic types" do |*args| plain_a = args.fetch(0, 22) plain_b = args.fetch(1, 222) context "with #{plain_a.inspect} and #{plain_b.inspect}" do describe "#eql?" do it "returns true for same class and value" do a = described_class.new(plain_a) b = described_class.new(plain_a) expect(a).to eql(b) end it "returns false for same class, different value" do a = described_class.new(plain_a) b = described_class.new(plain_b) expect(a).to_not eql(b) end it "returns false for same value but plain class" do a = described_class.new(plain_a) b = plain_a expect(a).to_not eql(b) end end describe "#==" do it "returns true for same class and value" do a = described_class.new(plain_a) b = described_class.new(plain_a) expect(a).to eq(b) end it "returns false for same class, different value" do a = described_class.new(plain_a) b = described_class.new(plain_b) expect(a).to_not eq(b) end it "returns true for same value but plain class" do a = described_class.new(plain_a) b = plain_a expect(a).to eq(b) end end end end RSpec.shared_examples "#== and #eql? work for container types (1 value)" do |plain_a, a_kwargs| a1 = described_class.new(plain_a, **a_kwargs) a2 = described_class.new(plain_a, **a_kwargs) context "with #{plain_a.inspect}, #{a_kwargs.inspect}" do describe "#eql?" do it "returns true for same class and value" do expect(a1).to eql(a2) end it "returns false for same value but plain class" do expect(a1).to_not eql(plain_a) end end describe "#==" do it "returns true for same class and value" do expect(a1).to eq(a2) end it "returns true for same value but plain class" do expect(a1).to eq(plain_a) end end end end RSpec.shared_examples "#== and #eql? work for container types (inequal)" do |plain_a, a_kwargs, plain_b, b_kwargs| # RSpec note: if the shared_examples is used via include_examples more than # once in a single context, `let` would take value from just one of them. # So use plain assignment. a = described_class.new(plain_a, **a_kwargs) b = described_class.new(plain_b, **b_kwargs) include_examples "#== and #eql? work for container types (1 value)", plain_a, a_kwargs context "with #{plain_a.inspect}, #{a_kwargs.inspect} and #{plain_b.inspect}, #{b_kwargs.inspect}" do describe "#eql?" do it "returns false for same class, different value" do expect(a).to_not eql(b) end end describe "#==" do it "returns false for same class, different value" do expect(a).to_not eq(b) end end end end RSpec.shared_examples "#== and #eql? work for container types (equal)" do |plain_a, a_kwargs, plain_b, b_kwargs| a = described_class.new(plain_a, **a_kwargs) b = described_class.new(plain_b, **b_kwargs) include_examples "#== and #eql? work for container types (1 value)", plain_a, a_kwargs context "with #{plain_a.inspect}, #{a_kwargs.inspect} and #{plain_b.inspect}, #{b_kwargs.inspect}" do describe "#eql?" do it "returns true for same class, differently expressed value" do expect(a).to eql(b) end end describe "#==" do it "returns true for same class, differently expressed value" do expect(a).to eq(b) end end describe "#==" do it "returns true for plain, differently expressed value" do expect(a).to eq(plain_b) expect(b).to eq(plain_a) end end end end RSpec.shared_examples "constructor accepts numeric range" do |min, max| describe "#initialize" do it "accepts the min value #{min}" do expect(described_class.new(min).value).to eql(min) end it "accepts the max value #{max}" do expect(described_class.new(max).value).to eql(max) end it "raises on too small a value #{min - 1}" do expect { described_class.new(min - 1) }.to raise_error(RangeError) end it "raises on too big a value #{max + 1}" do expect { described_class.new(max + 1) }.to raise_error(RangeError) end it "raises on nil" do expect { described_class.new(nil) }.to raise_error(RangeError) end end end RSpec.shared_examples "constructor accepts plain or typed values" do |plain_list| describe "#initialize" do Array(plain_list).each do |plain| it "accepts the plain value #{plain.inspect}" do expect(described_class.new(plain).value).to eql(plain) expect(described_class.new(plain)).to eq(plain) end it "accepts the typed value #{plain.inspect}" do typed = described_class.new(plain) expect(described_class.new(typed).value).to eql(plain) expect(described_class.new(typed)).to eq(plain) end end end end # FIXME: decide eq and eql here RSpec.shared_examples "constructor (kwargs) accepts values" do |list| describe "#initialize" do list.each do |value, kwargs_hash| it "accepts the plain value #{value.inspect}, #{kwargs_hash.inspect}" do expect(described_class.new(value, **kwargs_hash)).to eq(value) end it "accepts the typed value #{value.inspect}, #{kwargs_hash.inspect}" do typed = described_class.new(value, **kwargs_hash) expect(described_class.new(typed, **kwargs_hash)).to eq(value) end end end end RSpec.shared_examples "constructor rejects values from this list" do |bad_list| describe "#initialize" do bad_list.each do |(value, exc_class, msg_substr)| it "rejects #{value.inspect} with #{exc_class}: #{msg_substr}" do msg_re = Regexp.try_convert(msg_substr) || Regexp.new(Regexp.quote(msg_substr)) expect { described_class.new(value) }.to raise_error(exc_class, msg_re) end end end end RSpec.shared_examples "constructor (kwargs) rejects values" do |bad_list| describe "#initialize" do bad_list.each do |(value, kwargs_hash, exc_class, msg_substr)| it "rejects #{value.inspect}, #{kwargs_hash.inspect} with #{exc_class}: #{msg_substr}" do msg_re = Regexp.try_convert(msg_substr) || Regexp.new(Regexp.quote(msg_substr)) expect { described_class.new(value, **kwargs_hash) }.to raise_error(exc_class, msg_re) end end end end # TODO: Look at conversions? to_str, to_int? describe DBus::Data do T = DBus::Type unless const_defined? "T" # test initialization, from user code, or from packet (from_raw) # remember to unpack if initializing from Data::Base # #value should recurse inside so that the user doesnt have to # Kick InvalidPacketException out of here? describe DBus::Data::Byte do include_examples "#== and #eql? work for basic types" include_examples "constructor accepts numeric range", 0, (2**8) - 1 include_examples "constructor accepts plain or typed values", 42 end describe DBus::Data::Int16 do include_examples "#== and #eql? work for basic types" include_examples "constructor accepts numeric range", -2**15, (2**15) - 1 include_examples "constructor accepts plain or typed values", 42 end describe DBus::Data::UInt16 do include_examples "#== and #eql? work for basic types" include_examples "constructor accepts numeric range", 0, (2**16) - 1 include_examples "constructor accepts plain or typed values", 42 end describe DBus::Data::Int32 do include_examples "#== and #eql? work for basic types" include_examples "constructor accepts numeric range", -2**31, (2**31) - 1 include_examples "constructor accepts plain or typed values", 42 end describe DBus::Data::UInt32 do include_examples "#== and #eql? work for basic types" include_examples "constructor accepts numeric range", 0, (2**32) - 1 include_examples "constructor accepts plain or typed values", 42 end describe DBus::Data::Int64 do include_examples "#== and #eql? work for basic types" include_examples "constructor accepts numeric range", -2**63, (2**63) - 1 include_examples "constructor accepts plain or typed values", 42 end describe DBus::Data::UInt64 do include_examples "#== and #eql? work for basic types" include_examples "constructor accepts numeric range", 0, (2**64) - 1 include_examples "constructor accepts plain or typed values", 42 end describe DBus::Data::Boolean do describe "#initialize" do it "accepts false and true" do expect(described_class.new(false).value).to eq(false) expect(described_class.new(true).value).to eq(true) end it "accepts truth value of other objects" do expect(described_class.new(nil).value).to eq(false) expect(described_class.new(0).value).to eq(true) # ! expect(described_class.new(1).value).to eq(true) expect(described_class.new(Time.now).value).to eq(true) end end include_examples "#== and #eql? work for basic types", false, true include_examples "constructor accepts plain or typed values", false end describe DBus::Data::Double do include_examples "#== and #eql? work for basic types" include_examples "constructor accepts plain or typed values", Math::PI describe "#initialize" do it "raises on values that can't be made a Float" do expect { described_class.new(nil) }.to raise_error(TypeError) expect { described_class.new("one") }.to raise_error(ArgumentError) expect { described_class.new(/itsaregexp/) }.to raise_error(TypeError) end end end describe "basic, string-like types" do describe DBus::Data::String do # TODO: what about strings with good codepoints but encoded in # let's say Encoding::ISO8859_2? good = [ "", "Ř", # a Noncharacter, but well-formed Unicode # https://www.unicode.org/versions/corrigendum9.html "\uffff", # maximal UTF-8 codepoint U+10FFFF "\u{10ffff}" ] bad = [ # NUL in the middle # FIXME: InvalidPacketException is wrong here, it should be ArgumentError ["a\x00b", DBus::InvalidPacketException, "contains NUL"], # invalid UTF-8 ["\xFF\xFF\xFF\xFF", DBus::InvalidPacketException, "not in UTF-8"], # overlong sequence encoding an "A" ["\xC1\x81", DBus::InvalidPacketException, "not in UTF-8"], # first codepoint outside UTF-8, U+110000 ["\xF4\x90\xC0\xC0", DBus::InvalidPacketException, "not in UTF-8"] ] include_examples "#== and #eql? work for basic types", "foo", "bar" include_examples "constructor accepts plain or typed values", good include_examples "constructor rejects values from this list", bad describe ".alignment" do # this overly specific test avoids a redundant alignment call # in the production code it "returns the correct value" do expect(described_class.alignment).to eq 4 end end end describe DBus::Data::ObjectPath do good = [ "/" # TODO: others ] bad = [ ["", DBus::InvalidPacketException, "Invalid object path"] # TODO: others ] include_examples "#== and #eql? work for basic types", "/foo", "/bar" include_examples "constructor accepts plain or typed values", good include_examples "constructor rejects values from this list", bad describe ".alignment" do # this overly specific test avoids a redundant alignment call # in the production code it "returns the correct value" do expect(described_class.alignment).to eq 4 end end end describe DBus::Data::Signature do good = [ "", "i", "ii" # TODO: others ] bad = [ ["!", DBus::InvalidPacketException, "Unknown type code"] # TODO: others ] include_examples "#== and #eql? work for basic types", "aah", "aaaaah" include_examples "constructor accepts plain or typed values", good include_examples "constructor rejects values from this list", bad describe ".alignment" do # this overly specific test avoids a redundant alignment call # in the production code it "returns the correct value" do expect(described_class.alignment).to eq 1 end end end end describe "containers" do describe DBus::Data::Array do aq = DBus::Data::Array.new([1, 2, 3], type: "aq") good = [ [[1, 2, 3], { type: "aq" }], [[1, 2, 3], { type: T::Array[T::UINT16] }], [[1, 2, 3], { type: T::Array["q"] }], [[DBus::Data::UInt16.new(1), DBus::Data::UInt16.new(2), DBus::Data::UInt16.new(3)], { type: T::Array["q"] }] # TODO: others ] bad = [ # undesirable type guessing [[1, 2, 3], { type: nil }, ArgumentError, /Expecting DBus::Type.*got nil/], [[1, 2, 3], { type: "!" }, DBus::Type::SignatureException, "Unknown type code"], [aq, { type: "q" }, ArgumentError, "Expecting \"a\""], [aq, { type: "ao" }, ArgumentError, "Specified type is ARRAY: [OBJECT_PATH] but value type is ARRAY: [UINT16]"] # TODO: how to handle these? # [{1 => 2, 3 => 4}, { type: "aq" }, ArgumentError, "?"], # [/i am not an array/, { type: "aq" }, ArgumentError, "?"], ] include_examples "#== and #eql? work for container types (inequal)", [1, 2, 3], { type: "aq" }, [3, 2, 1], { type: "aq" } include_examples "#== and #eql? work for container types (inequal)", [[1, 2, 3]], { type: "aaq" }, [[3, 2, 1]], { type: "aaq" } include_examples "constructor (kwargs) accepts values", good include_examples "constructor (kwargs) rejects values", bad describe ".from_typed" do it "creates new instance from given object and type" do type = T::Array[String] expect(described_class.from_typed(["test", "lest"], type: type)).to be_a(described_class) end end end describe DBus::Data::Struct do three_words = ::Struct.new(:a, :b, :c) qqq = T::Struct[T::UINT16, T::UINT16, T::UINT16] integers = [1, 2, 3] uints = [DBus::Data::UInt16.new(1), DBus::Data::UInt16.new(2), DBus::Data::UInt16.new(3)] # TODO: all the reasonable initialization params # need to be normalized into one/few internal representation. # So check what is the result # # Internally, it must be Data::Base # Perhaps distinguish #value => Data::Base # and #plain_value => plain Ruby # # but then, can they mutate? # # TODO: also check data ownership: reasonable to own the data? # can make it explicit? good = [ # from plain array; various *type* styles [integers, { type: DBus.type("(qqq)") }], [integers, { type: T::Struct["q", "q", "q"] }], [integers, { type: T::Struct[T::UINT16, T::UINT16, T::UINT16] }], [integers, { type: T::Struct[*DBus.types("qqq")] }], # plain array of data [uints, { type: qqq }], # ::Struct [three_words.new(*integers), { type: qqq }], [three_words.new(*uints), { type: qqq }] # TODO: others ] # check these only when canonicalizing @value, because that will # type-check the value deeply _bad_but_valid = [ # STRUCT specific: member count mismatch [[1, 2], { type: qqq }, ArgumentError, "???"], [[1, 2, 3, 4], { type: qqq }, ArgumentError, "???"] # TODO: others ] include_examples "#== and #eql? work for container types (inequal)", [1, 2, 3], { type: qqq }, [3, 2, 1], { type: qqq } include_examples "#== and #eql? work for container types (equal)", three_words.new(*integers), { type: qqq }, [1, 2, 3], { type: qqq } include_examples "constructor (kwargs) accepts values", good # include_examples "constructor (kwargs) rejects values", bad describe ".from_typed" do it "creates new instance from given object and type" do type = T::Struct[T::STRING, T::STRING] expect(described_class.from_typed(["test", "lest"].freeze, type: type)) .to be_a(described_class) end end describe "#initialize" do it "converts type to Type" do value = [1, 2, 3] type = "(uuu)" result = described_class.new(value, type: type) expect(result.type).to be_a DBus::Type end it "checks that type matches class" do value = [1, 2, 3] type = T::Array[T::INT32] expect { described_class.new(value, type: type) } .to raise_error(ArgumentError, /Expecting "r"/) end it "checks type of a Data::Struct value" do value1 = [1, 2, 3] type1 = "(uuu)" result1 = described_class.new(value1, type: type1) value2 = result1 type2 = "(xxx)" expect { described_class.new(value2, type: type2) } .to raise_error(ArgumentError, /value type is STRUCT.*UINT32/) end it "checks that size of type and value match" do value = [1, 2, 3, 4] type = "(uuu)" expect { described_class.new(value, type: type) } .to raise_error(ArgumentError, /type has 3 members.*value has 4 members/) end it "converts value to ::Array of Data::Base" do value = three_words.new(*integers) type = T::Struct[T::INT32, T::INT32, T::INT32] result = described_class.new(value, type: type) expect(result.exact_value).to be_an(::Array) expect(result.exact_value[0]).to be_a(DBus::Data::Base) end end end describe DBus::Data::DictEntry do describe ".from_typed" do it "creates new instance from given object and type" do type = T::Hash[String, T::INT16].child expect(described_class.from_typed(["test", 12], type: type)) .to be_a(described_class) end end describe "#initialize" do it "checks that type matches class" do value = [1, 2] type = T::Array[T::INT32] expect { described_class.new(value, type: type) } .to raise_error(ArgumentError, /Expecting "e"/) end it "checks type of a Data::DictEntry value" do value1 = [1, 2] type1 = T::Hash[T::UINT32, T::UINT32].child result1 = described_class.new(value1, type: type1) value2 = result1 type2 = T::Hash[T::UINT64, T::UINT64].child expect { described_class.new(value2, type: type2) } .to raise_error(ArgumentError, /value type is DICT_ENTRY.*UINT32/) end it "checks that size of type and value match" do value = [1, 2, 3] type = T::Hash[T::UINT32, T::UINT32].child expect { described_class.new(value, type: type) } .to raise_error(ArgumentError, /type has 2 members.*value has 3 members/) end it "converts value to ::Array of Data::Base" do two_words = ::Struct.new(:k, :v) value = two_words.new(1, 2) type = T::Hash[T::UINT32, T::UINT32].child result = described_class.new(value, type: type) expect(result.exact_value).to be_an(::Array) expect(result.exact_value[0]).to be_a(DBus::Data::Base) end it "takes a plain value" do input = ["test", 23] type = T::Hash[String, T::INT16].child value = described_class.new(input, type: type) expect(value).to be_a(described_class) expect(value.type.to_s).to eq "{sn}" expect(value.value).to eql input end end end describe DBus::Data::Variant do describe ".from_typed" do it "creates new instance from given object and type" do type = DBus.type(T::VARIANT) value = described_class.from_typed("test", type: type) expect(value).to be_a(described_class) expect(value.type.to_s).to eq "v" expect(value.member_type.to_s).to eq "s" end end describe "#initialize" do it "takes a plain value" do input = 42 type = DBus.type(T::INT16) value = described_class.new(input, member_type: type) expect(value).to be_a(described_class) expect(value.type.to_s).to eq "v" expect(value.member_type.to_s).to eq "n" expect(value.value).to eq 42 end # FIXME: verify that @value has the correct class it "takes an exact value" do input = DBus::Data::Int16.new(42) type = DBus.type(T::INT16) value = described_class.new(input, member_type: type) expect(value).to be_a(described_class) expect(value.type.to_s).to eq "v" expect(value.member_type.to_s).to eq "n" expect(value.value).to eq 42 end it "checks the type of the exact value" do input = DBus::Data::UInt16.new(42) type = DBus.type(T::INT16) expect { described_class.new(input, member_type: type) } .to raise_error(ArgumentError, /Variant type n does not match value type q/) end end include_examples "#== and #eql? work for container types (1 value)", "/foo", { member_type: DBus.type(T::STRING) } describe "DBus.variant compatibility" do let(:v) { DBus.variant("o", "/foo") } describe "#[]" do it "returns the type for 0" do expect(v[0]).to eq DBus.type(DBus::Type::OBJECT_PATH) end it "returns the value for 1" do expect(v[1]).to eq DBus::ObjectPath.new("/foo") end it "returns an error for other indices" do expect { v[2] }.to raise_error(ArgumentError, /DBus.variant can only be indexed with 0 or 1/) end end describe "#first" do it "returns the type" do expect(v.first).to eq DBus.type(DBus::Type::OBJECT_PATH) end end describe "#last" do it "returns the value" do expect(v.last).to eq DBus::ObjectPath.new("/foo") end end end end end end ruby-dbus-0.25.0/spec/session_bus_spec_manual.rb0000755000004100000410000000062415000117217021723 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true 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.25.0/spec/bus_and_xml_backend_spec.rb0000755000004100000410000000251715000117217021777 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true # Test the bus class require_relative "spec_helper" require "rubygems" # If we have nokogiri, rexml is normally omitted # but here we include it for test coverage require "rexml" 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.25.0/spec/node_spec.rb0000755000004100000410000000346415000117217016764 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::Node do describe "#inspect" do # the behavior needs improvement it "shows the node, poorly" do parent = described_class.new("parent") parent.object = DBus::Object.new("/parent") 3.times do |i| child_name = "child#{i}" child = described_class.new(child_name) parent[child_name] = child end expect(parent.inspect).to match(/ {},child1 => {},child2 => {}}>/) end end describe "#descendant_objects" do let(:manager_path) { "/org/example/FooManager" } let(:child_paths) do [ # NOTE: "/org/example/FooManager/good" # is a path under a managed object but there is no object there "/org/example/FooManager/good/1", "/org/example/FooManager/good/2", "/org/example/FooManager/good/3", "/org/example/FooManager/bad/1", "/org/example/FooManager/bad/2" ] end let(:non_child_paths) do [ "/org/example/BarManager/good/1", "/org/example/BarManager/good/2" ] end context "on the bus" do let(:bus) { DBus::ASessionBus.new } let(:service) { bus.object_server } before do service.export(DBus::Object.new(manager_path)) non_child_paths.each do |p| service.export(DBus::Object.new(p)) end end it "returns just the descendants of the specified objects" do child_exported_objects = child_paths.map { |p| DBus::Object.new(p) } child_exported_objects.each { |obj| service.export(obj) } node = service.get_node(manager_path, create: false) expect(node.descendant_objects).to eq child_exported_objects end end end end ruby-dbus-0.25.0/spec/type_spec.rb0000755000004100000410000001411715000117217017015 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus do describe ".type" do good = [ "i", "ai", "a(ii)", "aai" ] context "valid single types" do good.each do |s| it "#{s.inspect} is parsed" do expect(DBus.type(s).to_s).to eq(s) end end end bad = [ ["\x00", "Unknown type code"], ["!", "Unknown type code"], # ARRAY related ["a", "Empty ARRAY"], ["aa", "Empty ARRAY"], # STRUCT related ["r", "Abstract STRUCT"], ["()", "Empty STRUCT"], ["(ii", "STRUCT not closed"], ["a{i)", "STRUCT unexpectedly closed"], # TODO: deep nesting arrays, structs, combined # DICT_ENTRY related ["e", "Abstract DICT_ENTRY"], ["a{}", "DICT_ENTRY must have 2 subtypes, found 0"], ["a{s}", "DICT_ENTRY must have 2 subtypes, found 1"], ["a{sss}", "DICT_ENTRY must have 2 subtypes, found 3"], ["a{vs}", "DICT_ENTRY key must be basic (non-container)"], ["{sv}", "DICT_ENTRY not an immediate child of an ARRAY"], ["a({sv})", "DICT_ENTRY not an immediate child of an ARRAY"], ["a{s", "DICT_ENTRY not closed"], ["a{sv", "DICT_ENTRY not closed"], ["}", "DICT_ENTRY unexpectedly closed"], # Too long ["(#{"y" * 254})", "longer than 255"], # not Single Complete Types ["", "expecting a Single Complete Type"], ["ii", "more than a Single Complete Type"] ] context "invalid single types" do bad.each.each do |s, msg| it "#{s.inspect} raises an exception mentioning: #{msg}" do rx = Regexp.new(Regexp.quote(msg)) expect { DBus.type(s) }.to raise_error(DBus::Type::SignatureException, rx) end end end end describe ".types" do good = [ "", "ii" ] context "valid signatures" do good.each do |s| it "#{s.inspect} is parsed" do expect(DBus.types(s).map(&:to_s).join).to eq(s) end end end end describe DBus::Type do let(:as1) { DBus.type("as") } let(:as2) { DBus.type("as") } let(:aas) { DBus.type("aas") } describe "#==" do it "is true for same types" do expect(as1).to eq(as2) end it "is true for a type and its string representation" do expect(as1).to eq("as") end it "is false for different types" do expect(as1).to_not eq(aas) end it "is false for a type and a different string" do expect(as1).to_not eq("aas") end end describe "#eql?" do it "is true for same types" do expect(as1).to eql(as2) end it "is false for a type and its string representation" do expect(as1).to_not eql("as") end it "is false for different types" do expect(as1).to_not eql(aas) end it "is false for a type and a different string" do expect(as1).to_not eql("aas") end end describe "#<<" do it "raises if the argument is not a Type" do t = DBus::Type.new(DBus::Type::ARRAY) expect { t << "s" }.to raise_error(ArgumentError) end # TODO: the following raise checks do not occur in practice, as there are # parallel checks in the parses. The code could be simplified? it "raises if adding too much to an array" do t = DBus::Type.new(DBus::Type::ARRAY) b = DBus::Type.new(DBus::Type::BOOLEAN) t << b expect { t << b }.to raise_error(DBus::Type::SignatureException) end it "raises if adding too much to a dict_entry" do t = DBus::Type.new(DBus::Type::DICT_ENTRY, abstract: true) b = DBus::Type.new(DBus::Type::BOOLEAN) t << b t << b expect { t << b }.to raise_error(DBus::Type::SignatureException) end it "raises if adding to a non-container" do t = DBus::Type.new(DBus::Type::STRING) b = DBus::Type.new(DBus::Type::BOOLEAN) expect { t << b }.to raise_error(DBus::Type::SignatureException) t = DBus::Type.new(DBus::Type::VARIANT) expect { t << b }.to raise_error(DBus::Type::SignatureException) end end describe DBus::Type::Array do describe ".[]" do it "takes Type argument" do t = DBus::Type::Array[DBus::Type.new("s")] expect(t.to_s).to eq "as" end it "takes 's':String argument" do t = DBus::Type::Array["s"] expect(t.to_s).to eq "as" end it "takes String:Class argument" do t = DBus::Type::Array[String] expect(t.to_s).to eq "as" end it "rejects Integer:Class argument" do expect { DBus::Type::Array[Integer] }.to raise_error(ArgumentError) end it "rejects /./:Regexp argument" do expect { DBus::Type::Array[/./] }.to raise_error(ArgumentError) end end end describe DBus::Type::Hash do describe ".[]" do it "takes Type arguments" do t = DBus::Type::Hash[DBus::Type.new("s"), DBus::Type.new("v")] expect(t.to_s).to eq "a{sv}" end it "takes 's':String arguments" do t = DBus::Type::Hash["s", "v"] expect(t.to_s).to eq "a{sv}" end it "takes String:Class argument" do t = DBus::Type::Hash[String, DBus::Type::VARIANT] expect(t.to_s).to eq "a{sv}" end end end describe DBus::Type::Struct do describe ".[]" do it "takes Type arguments" do t = DBus::Type::Struct[DBus::Type.new("s"), DBus::Type.new("v")] expect(t.to_s).to eq "(sv)" end it "takes 's':String arguments" do t = DBus::Type::Struct["s", "v"] expect(t.to_s).to eq "(sv)" end it "takes String:Class argument" do t = DBus::Type::Struct[String, DBus::Type::VARIANT] expect(t.to_s).to eq "(sv)" end it "raises on no arguments" do expect { DBus::Type::Struct[] }.to raise_error(ArgumentError) end end end end end ruby-dbus-0.25.0/spec/err_msg_spec.rb0000755000004100000410000000263215000117217017471 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true # 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.25.0/spec/async_spec.rb0000755000004100000410000000206415000117217017147 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true # 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.25.0/spec/auth_spec.rb0000755000004100000410000001713715000117217017002 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::Authentication::Client do let(:socket) { instance_double("Socket") } let(:subject) { described_class.new(socket) } before(:each) do allow(Process).to receive(:uid).and_return(999) allow(subject).to receive(:send_nul_byte) end describe "#next_state" do it "raises when I forget to handle a state" do subject.instance_variable_set(:@state, :Denmark) expect { subject.__send__(:next_state, []) }.to raise_error(RuntimeError, /unhandled state :Denmark/) end end def expect_protocol(pairs) pairs.each do |we_say, server_says| expect(subject).to receive(:write_line).with(we_say) next if server_says.nil? expect(subject).to receive(:read_line).and_return(server_says) end end context "with ANONYMOUS" do let(:subject) { described_class.new(socket, [DBus::Authentication::Anonymous]) } it "authentication passes" do expect_protocol [ ["AUTH ANONYMOUS 527562792044427573\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"], ["NEGOTIATE_UNIX_FD\r\n", "ERROR not for anonymous\r\n"], ["BEGIN\r\n"] ] expect { subject.authenticate }.to_not raise_error end end context "with EXTERNAL" do let(:subject) { described_class.new(socket, [DBus::Authentication::External]) } it "authentication passes, and address_uuid is set" do expect_protocol [ ["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"], ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"], ["BEGIN\r\n"] ] expect { subject.authenticate }.to_not raise_error expect(subject.address_uuid).to eq "ffffffffffffffffffffffffffffffff" end context "when the server says superfluous things before an OK" do it "authentication passes" do expect_protocol [ ["AUTH EXTERNAL 393939\r\n", "WOULD_YOU_LIKE_SOME_TEA\r\n"], ["ERROR\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"], ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"], ["BEGIN\r\n"] ] expect { subject.authenticate }.to_not raise_error end end context "when the server messes up NEGOTIATE_UNIX_FD" do it "authentication fails orderly" do expect_protocol [ ["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"], ["NEGOTIATE_UNIX_FD\r\n", "I_DONT_NEGOTIATE_WITH_TENORISTS\r\n"] ] allow(socket).to receive(:close) # want to get rid of this # TODO: quote the server error message? expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /Unknown server reply/) end end context "when the server replies with ERROR" do it "authentication fails orderly" do expect_protocol [ ["AUTH EXTERNAL 393939\r\n", "ERROR something failed\r\n"], ["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"] ] allow(socket).to receive(:close) # want to get rid of this # TODO: quote the server error message? expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/) end end end context "with EXTERNAL without uid" do let(:subject) do described_class.new(socket, [DBus::Authentication::External, DBus::Authentication::ExternalWithoutUid]) end it "authentication passes" do expect_protocol [ ["AUTH EXTERNAL 393939\r\n", "REJECTED EXTERNAL\r\n"], # this succeeds when we connect to a privileged container, # where outside-non-root becomes inside-root ["AUTH EXTERNAL\r\n", "DATA\r\n"], ["DATA\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"], ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"], ["BEGIN\r\n"] ] expect { subject.authenticate }.to_not raise_error end end context "with a rejected mechanism and then EXTERNAL" do let(:rejected_mechanism) do double("Mechanism", name: "WIMP", call: [:MechContinue, "I expect to be rejected"]) end let(:subject) { described_class.new(socket, [rejected_mechanism, DBus::Authentication::External]) } it "authentication eventually passes" do expect_protocol [ [/^AUTH WIMP .*\r\n/, "REJECTED EXTERNAL\r\n"], ["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"], ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"], ["BEGIN\r\n"] ] expect { subject.authenticate }.to_not raise_error end end context "with a DATA-using mechanism" do let(:mechanism) do double("Mechanism", name: "CHALLENGE_ME", call: [:MechContinue, "1"]) end # try it twice to test calling #use_next_mechanism let(:subject) { described_class.new(socket, [mechanism, mechanism]) } it "authentication fails orderly when the server says ERROR" do expect_protocol [ ["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"], ["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"], ["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"], ["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"] ] allow(socket).to receive(:close) # want to get rid of this # TODO: quote the server error message? expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/) end it "authentication fails orderly when the server says ERROR and then changes its mind" do expect_protocol [ ["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"], ["CANCEL\r\n", "I_CHANGED_MY_MIND please come back\r\n"] ] allow(socket).to receive(:close) # want to get rid of this # TODO: quote the server error message? expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /Unknown.*MIND.*REJECTED/) end it "authentication passes when the server says superfluous things before DATA" do expect_protocol [ ["AUTH CHALLENGE_ME 31\r\n", "WOULD_YOU_LIKE_SOME_TEA\r\n"], ["ERROR\r\n", "DATA\r\n"], ["DATA 31\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"], ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"], ["BEGIN\r\n"] ] expect { subject.authenticate }.to_not raise_error end it "authentication passes when the server decides not to need the DATA" do expect_protocol [ ["AUTH CHALLENGE_ME 31\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"], ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"], ["BEGIN\r\n"] ] expect { subject.authenticate }.to_not raise_error end end context "with a mechanism returning :MechError" do let(:fallible_mechanism) do double(name: "FALLIBLE", call: [:MechError, "not my best day"]) end let(:subject) { described_class.new(socket, [fallible_mechanism]) } it "authentication fails orderly" do expect_protocol [ ["ERROR not my best day\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"] ] allow(socket).to receive(:close) # want to get rid of thise expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/) end end context "with a badly implemented mechanism" do let(:buggy_mechanism) do double(name: "buggy", call: [:smurf, nil]) end let(:subject) { described_class.new(socket, [buggy_mechanism]) } it "authentication fails before protoxol is exchanged" do expect(subject).to_not receive(:write_line) expect(subject).to_not receive(:read_line) expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /smurf/) end end end ruby-dbus-0.25.0/spec/object_spec.rb0000755000004100000410000001271615000117217017305 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" class ObjectTest < DBus::Object T = DBus::Type unless const_defined? "T" dbus_interface "org.ruby.ServerTest" do dbus_attr_writer :write_me, T::Struct[String, String] attr_accessor :read_only_for_dbus dbus_reader :read_only_for_dbus, T::STRING, emits_changed_signal: :invalidates end end describe DBus::Object do describe ".dbus_attr_writer" do describe "the declared assignment method" do # Slightly advanced RSpec: # https://rspec.info/documentation/3.9/rspec-expectations/RSpec/Matchers.html#satisfy-instance_method let(:a_struct_in_a_variant) do satisfying { |x| x.is_a?(DBus::Data::Variant) && x.member_type.to_s == "(ss)" } # ^ This formatting keeps the matcher on a single line # which enables RSpec to cite it if it fails, instead of saying "block". end it "emits PropertyChanged with correctly typed argument" do obj = ObjectTest.new("/test") expect(obj).to receive(:PropertiesChanged).with( "org.ruby.ServerTest", { "WriteMe" => a_struct_in_a_variant }, [] ) # bug: call PC with simply the assigned value, # which will need type guessing obj.write_me = ["two", "strings"] end end end describe ".dbus_accessor" do it "can only be used within a dbus_interface" do expect do ObjectTest.instance_exec do dbus_accessor :foo, DBus::Type::STRING end end.to raise_error(DBus::Object::UndefinedInterface) end end describe ".dbus_reader" do it "can only be used within a dbus_interface" do expect do ObjectTest.instance_exec do dbus_reader :foo, DBus::Type::STRING end end.to raise_error(DBus::Object::UndefinedInterface) end it "fails when the signature is invalid" do expect do ObjectTest.instance_exec do dbus_interface "org.ruby.ServerTest" do dbus_reader :foo2, "!" end end end.to raise_error(DBus::Type::SignatureException) end end describe ".dbus_reader, when paired with attr_accessor" do describe "the declared assignment method" do it "emits PropertyChanged" do obj = ObjectTest.new("/test") expect(obj).to receive(:PropertiesChanged).with( "org.ruby.ServerTest", {}, ["ReadOnlyForDbus"] ) obj.read_only_for_dbus = "myvalue" end end end describe ".dbus_writer" do it "can only be used within a dbus_interface" do expect do ObjectTest.instance_exec do dbus_writer :foo, DBus::Type::STRING end end.to raise_error(DBus::Object::UndefinedInterface) end end describe ".dbus_watcher" do it "can only be used within a dbus_interface" do expect do ObjectTest.instance_exec do dbus_watcher :foo end end.to raise_error(DBus::Object::UndefinedInterface) end end describe ".dbus_method" do it "can only be used within a dbus_interface" do expect do ObjectTest.instance_exec do dbus_method :foo do end end end.to raise_error(DBus::Object::UndefinedInterface) end end describe ".dbus_signal" do it "can only be used within a dbus_interface" do expect do ObjectTest.instance_exec do dbus_signal :signal_without_interface end end.to raise_error(DBus::Object::UndefinedInterface) end it "cannot be named with a bang" do expect do ObjectTest.instance_exec do dbus_interface "org.ruby.ServerTest" do # a valid Ruby symbol but an invalid DBus name; Ticket#38 dbus_signal :signal_with_a_bang! end end end.to raise_error(DBus::InvalidMethodName) end end describe ".emits_changed_signal" do it "raises UndefinedInterface when so" do expect { ObjectTest.emits_changed_signal = false } .to raise_error DBus::Object::UndefinedInterface end it "assigns to the current interface" do ObjectTest.instance_exec do dbus_interface "org.ruby.Interface" do self.emits_changed_signal = false end end ecs = ObjectTest.intfs["org.ruby.Interface"].emits_changed_signal expect(ecs).to eq false end it "only can be assigned once" do expect do Class.new(DBus::Object) do dbus_interface "org.ruby.Interface" do self.emits_changed_signal = false self.emits_changed_signal = :invalidates end end end.to raise_error(RuntimeError, /assigned more than once/) end end # coverage obsession describe "#dispatch" do it "survives being called with a non-METHOD_CALL, doing nothing" do obj = ObjectTest.new("/test") msg = DBus::MethodReturnMessage.new expect { obj.dispatch(msg) }.to_not raise_error end end describe "#emit" do context "before the object has been exported" do it "raises an explanatory error" do obj = ObjectTest.new("/test") intf = DBus::Interface.new("org.example.Test") signal = DBus::Signal.new("Ring") expect { obj.emit(intf, signal) } .to raise_error( RuntimeError, %r{Cannot emit signal org.example.Test.Ring before /test is exported} ) end end end end ruby-dbus-0.25.0/spec/bus_spec.rb0000755000004100000410000000104515000117217016621 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true # 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.25.0/spec/object_path_spec.rb0000755000004100000410000000170015000117217020310 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::ObjectPath do describe ".valid?" do it "recognizes valid paths" do expect(described_class.valid?("/")).to be_truthy expect(described_class.valid?("/99Numbers/_And_Underscores/anywhere")).to be_truthy long_name = "/A23456789" * 42 # no 255 character limit for object paths expect(described_class.valid?(long_name)).to be_truthy end it "recognizes invalid paths" do expect(described_class.valid?("")).to be_falsey expect(described_class.valid?("/Empty//Component")).to be_falsey expect(described_class.valid?("/EmptyLastComponent/")).to be_falsey expect(described_class.valid?("/Invalid Character")).to be_falsey expect(described_class.valid?("/Invalid-Character")).to be_falsey expect(described_class.valid?("/InválídCháráctér")).to be_falsey end end end ruby-dbus-0.25.0/spec/raw_message_spec.rb0000755000004100000410000000156015000117217020327 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" # Pedantic full coverage test. # The happy paths are covered via calling classes describe DBus::RawMessage do describe ".endianness" do it "returns :little for 'l'" do expect(described_class.endianness("l")).to eq :little end it "returns :big for 'B'" do expect(described_class.endianness("B")).to eq :big end it "raises for other strings" do expect { described_class.endianness("m") } .to raise_error(DBus::InvalidPacketException, /Incorrect endianness/) end end describe "#align" do it "raises for values other than 1 2 4 8" do subject = described_class.new("l") expect { subject.align(3) }.to raise_error(ArgumentError) expect { subject.align(16) }.to raise_error(ArgumentError) end end end ruby-dbus-0.25.0/spec/object_server_spec.rb0000755000004100000410000000761715000117217020677 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::ObjectServer do let(:bus) { DBus::ASessionBus.new } let(:server) { bus.object_server } describe "#descendants_for" do it "raises for not existing path" do expect { server.descendants_for("/notthere") }.to raise_error(ArgumentError, /notthere doesn't exist/) end end # tag_bus MEANS that the test needs a session bus # tag_service MEANS that it needs a session bus with our testing services describe "#export, #object, #[]", tag_bus: true do it "object and [] return the object if it was exported" do path = "/org/ruby/Foo" obj = DBus::Object.new(path) server.export(obj) expect(server.object(path)).to be_equal(obj) expect(server[path]).to be_equal(obj) end it "object and [] return nil if the path was not found or has no object" do path = "/org/ruby/Bar" obj = DBus::Object.new(path) server.export(obj) path2 = "/org/ruby/nosuch" expect(server.object(path2)).to be_nil expect(server[path2]).to be_nil path3 = "/org" expect(server.object(path3)).to be_nil expect(server[path3]).to be_nil end end describe "#export", tag_bus: true do context "when exporting at a path where an object exists already" do let(:path) { "/org/ruby/Same" } let(:obj1) do o = DBus::Object.new(path) o.define_singleton_method(:which) { 1 } o end let(:obj2) do o = DBus::Object.new(path) o.define_singleton_method(:which) { 2 } o end it "raises an error" do server.export(obj1) expect { server.export(obj2) }.to raise_error(RuntimeError, /there is already an object/) end end end describe "#unexport", tag_bus: true do before(:each) do bus = DBus::ASessionBus.new @svc = bus.object_server end it "returns the unexported leaf object" do obj = DBus::Object.new "/org/ruby/Foo" @svc.export obj expect(@svc.unexport(obj)).to be_equal(obj) end it "returns the unexported leaf object, if specified by its path" do obj = DBus::Object.new "/org/ruby/Foo" @svc.export obj expect(@svc.unexport(obj.path)).to be_equal(obj) obj = DBus::Object.new "/org/ruby/Foo" @svc.export obj expect(@svc.unexport(DBus::ObjectPath.new(obj.path))).to be_equal(obj) end it "returns false if the object was never exported" do obj = DBus::Object.new "/org/ruby/Foo" expect(@svc.unexport(obj)).to be false end it "raises false if the path has no node" do obj = DBus::Object.new "/org/ruby/Foo" @svc.export obj expect { @svc.unexport("/org/ruby/NotFoo") }.to raise_error(ArgumentError) @svc.unexport obj end it "raises false if the path has no object" do obj = DBus::Object.new "/org/ruby/Foo" @svc.export obj expect { @svc.unexport("/org/ruby") }.to raise_error(ArgumentError) @svc.unexport obj end it "raises when argument is not usable" do expect { @svc.unexport(:foo) }.to raise_error(ArgumentError) end context "/child_of_root" do it "returns the unexported object" do obj = DBus::Object.new "/child_of_root" @svc.export obj expect(@svc.unexport(obj)).to be_equal(obj) end end context "/ (root)" do it "returns the unexported object" do obj = DBus::Object.new "/" @svc.export obj expect(@svc.unexport(obj)).to be_equal(obj) end end context "not a leaf object" do it "maintains objects on child paths" do obj = DBus::Object.new "/org/ruby" @svc.export obj obj2 = DBus::Object.new "/org/ruby/Foo" @svc.export obj2 @svc.unexport(obj) expect(@svc.object("/org/ruby/Foo")).to be_a DBus::Object end end end end ruby-dbus-0.25.0/spec/signal_spec.rb0000755000004100000410000000566115000117217017315 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true # Test the signal handlers require_relative "spec_helper" require "dbus" def new_quitter(main_loop) 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" main_loop.quit end end 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", slow: true 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 = new_quitter(@loop) @loop.run quitter.join expect(counter).to eq(1) end it "tests on signal overload", slow: true 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 = new_quitter(@loop) @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 "is possible to add signal handlers from within handlers", slow: true do ended = false @intf.on_signal "LongTaskStart" do @intf.on_signal "LongTaskEnd" do ended = true end end @obj.LongTaskBegin 3 quitter = new_quitter(@loop) @loop.run quitter.join expect(ended).to eq(true) 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 describe DBus::ProxyObject do describe "#on_signal" do it "raises a descriptive error when the default_iface is wrong" do open_quote = RUBY_VERSION >= "3.4" ? "'" : "`" @obj.default_iface = "org.ruby.NoSuchInterface" expect { @obj.on_signal("Foo") {} } .to raise_error(NoMethodError, /undefined signal.*interface #{open_quote}org.ruby.NoSuchInterface'/) end end end end ruby-dbus-0.25.0/spec/session_bus_spec.rb0000755000004100000410000000745315000117217020375 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::ASystemBus do describe "#initialize" do it "will use DBUS_SYSTEM_BUS_ADDRESS or the well known address" do expect(ENV) .to receive(:[]) .with("DBUS_SYSTEM_BUS_ADDRESS") .and_return(nil) expect(DBus::MessageQueue) .to receive(:new) .with("unix:path=/var/run/dbus/system_bus_socket") expect_any_instance_of(described_class).to receive(:send_hello) described_class.new end end end 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 it "uses launchd on macOS when ENV and file fail" do ENV["DBUS_SESSION_BUS_ADDRESS"] = nil expect(described_class).to receive(:address_from_file).and_return(nil) expect(DBus::Platform).to receive(:macos?).and_return(true) expect(described_class.session_bus_address).to start_with "launchd:" end it "raises a readable exception when all addresses fail" do ENV["DBUS_SESSION_BUS_ADDRESS"] = nil expect(described_class).to receive(:address_from_file).and_return(nil) expect(DBus::Platform).to receive(:macos?).and_return(false) expect { described_class.session_bus_address }.to raise_error(NotImplementedError, /Cannot find session bus/) 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", __dir__) 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) { <<-TEXT.gsub(/^\s*/, "") } DBUS_SESSION_BUS_ADDRESS='#{dbus_session_bus_address}' DBUS_SESSION_BUS_PID=12345 DBUS_SESSION_BUS_WINDOWID=12345678 TEXT 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) { <<-TEXT.gsub(/^\s*/, "") } DBUS_SESSION_BUS_ADDRESS="#{dbus_session_bus_address}" DBUS_SESSION_BUS_PID=12345 DBUS_SESSION_BUS_WINDOWID=12345678 TEXT 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) { <<-TEXT.gsub(/^\s*/, "") } DBUS_SESSION_BUS_ADDRESS=#{dbus_session_bus_address} DBUS_SESSION_BUS_PID=12345 DBUS_SESSION_BUS_WINDOWID=12345678 TEXT expect(DBus::ASessionBus.address_from_file).to eq(dbus_session_bus_address) end end end end ruby-dbus-0.25.0/spec/packet_unmarshaller_spec.rb0000755000004100000410000001577215000117217022070 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" require "ostruct" require "yaml" data_dir = File.expand_path("data", __dir__) marshall_yaml_s = File.read("#{data_dir}/marshall.yaml") marshall_yaml = YAML.safe_load(marshall_yaml_s) # Helper to access PacketUnmarshaller internals. # Add it to its public API? # @param p_u [PacketUnmarshaller] # @return [String] the binary string with unconsumed data def remaining_buffer(p_u) raw_msg = p_u.instance_variable_get(:@raw_msg) raw_msg.remaining_bytes end RSpec.shared_examples "parses good data" do |cases| describe "parses all the instances of good test data" do cases.each_with_index do |(buffer, endianness, expected), i| it "parses plain data ##{i}" do buffer = String.new(buffer, encoding: Encoding::BINARY) subject = described_class.new(buffer, endianness) results = subject.unmarshall(signature, mode: :plain) # unmarshall works on multiple signatures but we use one expect(results).to be_an(Array) expect(results.size).to eq(1) result = results.first expect(result).to eq(expected) expect(remaining_buffer(subject)).to be_empty end it "parses exact data ##{i}" do buffer = String.new(buffer, encoding: Encoding::BINARY) subject = described_class.new(buffer, endianness) results = subject.unmarshall(signature, mode: :exact) # unmarshall works on multiple signatures but we use one expect(results).to be_an(Array) expect(results.size).to eq(1) result = results.first expect(result).to be_a(DBus::Data::Base) expect(result.value).to eq(expected) expect(remaining_buffer(subject)).to be_empty end end end end # this is necessary because we do an early switch on the signature RSpec.shared_examples "reports empty data" do it "reports empty data" do [:big, :little].each do |endianness| subject = described_class.new("", endianness) expect { subject.unmarshall(signature) }.to raise_error(DBus::IncompleteBufferException) end end end describe DBus::PacketUnmarshaller do context "marshall.yaml" do marshall_yaml.each do |test| t = OpenStruct.new(test) signature = t.sig buffer = buffer_from_yaml(t.buf) endianness = t.end.to_sym # successful parse if !t.val.nil? expected = t.val it "parses a '#{signature}' to get #{t.val.inspect} (plain)" do subject = described_class.new(buffer, endianness) results = subject.unmarshall(signature, mode: :plain) # unmarshall works on multiple signatures but we use one expect(results).to be_an(Array) expect(results.size).to eq(1) result = results.first expect(result).to eq(expected) expect(remaining_buffer(subject)).to be_empty end it "parses a '#{t.sig}' to get #{t.val.inspect} (exact)" do subject = described_class.new(buffer, endianness) results = subject.unmarshall(signature, mode: :exact) # unmarshall works on multiple signatures but we use one expect(results).to be_an(Array) expect(results.size).to eq(1) result = results.first expect(result).to be_a(DBus::Data::Base) expect(result.value).to eq(expected) expect(remaining_buffer(subject)).to be_empty end elsif t.exc next if t.unmarshall == false exc_class = DBus.const_get(t.exc) msg_re = Regexp.new(Regexp.escape(t.msg)) # TODO: InvalidPacketException is never rescued. # The other end is sending invalid data. Can we do better than crashing? # When we can test with peer connections, try it out. it "parses a '#{signature}' to report a #{t.exc}" do subject = described_class.new(buffer, endianness) expect { subject.unmarshall(signature, mode: :plain) }.to raise_error(exc_class, msg_re) subject = described_class.new(buffer, endianness) expect { subject.unmarshall(signature, mode: :exact) }.to raise_error(exc_class, msg_re) end end end end context "BYTEs" do let(:signature) { "y" } include_examples "reports empty data" end context "BOOLEANs" do let(:signature) { "b" } include_examples "reports empty data" end context "INT16s" do let(:signature) { "n" } include_examples "reports empty data" end context "UINT16s" do let(:signature) { "q" } include_examples "reports empty data" end context "INT32s" do let(:signature) { "i" } include_examples "reports empty data" end context "UINT32s" do let(:signature) { "u" } include_examples "reports empty data" end context "UNIX_FDs" do let(:signature) { "h" } include_examples "reports empty data" end context "INT64s" do let(:signature) { "x" } include_examples "reports empty data" end context "UINT64s" do let(:signature) { "t" } include_examples "reports empty data" end context "DOUBLEs" do let(:signature) { "d" } # See https://en.wikipedia.org/wiki/Double-precision_floating-point_format # for binary representations # TODO: figure out IEEE754 comparisons good = [ # But == cant distinguish -0.0 ["\x00\x00\x00\x00\x00\x00\x00\x80", :little, -0.0], # But NaN == NaN is false! # ["\xff\xff\xff\xff\xff\xff\xff\xff", :little, Float::NAN], ["\x80\x00\x00\x00\x00\x00\x00\x00", :big, -0.0] # ["\xff\xff\xff\xff\xff\xff\xff\xff", :big, Float::NAN] ] include_examples "parses good data", good include_examples "reports empty data" end context "STRINGs" do let(:signature) { "s" } include_examples "reports empty data" end context "OBJECT_PATHs" do let(:signature) { "o" } include_examples "reports empty data" end context "SIGNATUREs" do let(:signature) { "g" } include_examples "reports empty data" end context "ARRAYs" do context "of BYTEs" do let(:signature) { "ay" } include_examples "reports empty data" end context "of UINT64s" do let(:signature) { "at" } include_examples "reports empty data" end context "of STRUCT of 2 UINT16s" do let(:signature) { "a(qq)" } include_examples "reports empty data" end context "of DICT_ENTRIES" do let(:signature) { "a{yq}" } include_examples "reports empty data" end end context "STRUCTs" do # TODO: this is invalid but does not raise context "(generic 'r' struct)" do let(:signature) { "r" } end context "of two shorts" do let(:signature) { "(qq)" } include_examples "reports empty data" end end # makes sense here? or in array? remember invalid sigs are rejected elsewhere context "DICT_ENTRYs" do context "(generic 'e' dict_entry)" do let(:signature) { "e" } end end context "VARIANTs" do let(:signature) { "v" } include_examples "reports empty data" end end ruby-dbus-0.25.0/spec/zzz_quit_spec.rb0000755000004100000410000000065415000117217017734 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe "Quit the service" do it "Tells the service to quit and waits, to collate coverage data" 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" @obj.quit sleep 3 end end ruby-dbus-0.25.0/spec/server_robustness_spec.rb0000755000004100000410000000511515000117217021627 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true # 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.message).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.message).to_not match(/timeout/) end end context "an existing path without an object" do let(:obj) { @svc.object "/org" } it "errors without a timeout" do 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.message).to_not match(/timeout/) end 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.message).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.message).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.message).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.message).to_not match(/timeout/) end end end ruby-dbus-0.25.0/spec/bus_name_spec.rb0000755000004100000410000000204015000117217017615 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::BusName do describe ".valid?" do it "recognizes valid bus names" do expect(described_class.valid?("org.freedesktop.DBus")).to be_truthy expect(described_class.valid?(":1.42")).to be_truthy expect(described_class.valid?("org._7_zip.Archiver")).to be_truthy end it "recognizes invalid bus names" do expect(described_class.valid?("")).to be_falsey expect(described_class.valid?("Empty..Component")).to be_falsey expect(described_class.valid?(".Empty.First.Component")).to be_falsey expect(described_class.valid?("Empty.Last.Component.")).to be_falsey expect(described_class.valid?("Invalid.Ch@r@cter")).to be_falsey expect(described_class.valid?("/Invalid-Character")).to be_falsey long_name = "a.#{"long." * 100}name" expect(described_class.valid?(long_name)).to be_falsey expect(described_class.valid?("org.7_zip.Archiver")).to be_falsey end end end ruby-dbus-0.25.0/spec/value_spec.rb0000755000004100000410000000700315000117217017144 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true 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 it "aligns short integers correctly" do expect(@obj.i16_plus(10, -30)[0]).to eq(-20) end context "structs" do it "they are returned as FROZEN arrays" do struct = @obj.Coordinates[0] expect(struct).to be_an(Array) expect(struct).to be_frozen end it "they are returned also from structs" do struct = @obj.Coordinates2[0] expect(struct).to be_an(Array) expect(struct).to be_frozen end end end ruby-dbus-0.25.0/spec/thread_safety_spec.rb0000755000004100000410000000324315000117217020654 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true # Test thread safety require_relative "spec_helper" require "dbus" class TestSignalRace < DBus::Object dbus_interface "org.ruby.ServerTest" do dbus_signal :signal_without_arguments end end # Run *count* threads all doing *body*, wait for their finish def race_threads(count, &body) jobs = count.times.map do |j| Thread.new do Thread.current.abort_on_exception = true body.call(j) end end jobs.each(&:join) end # Repeat *count* times: { random sleep, *body* }, printing progress def repeat_with_jitter(count, &body) count.times do |i| sleep 0.1 * rand print "#{i} " $stdout.flush body.call end end describe "thread safety" do context "R/W: when the threads call methods with return values" do it "it works with separate bus connections" do race_threads(5) do |_j| # 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" repeat_with_jitter(10) do expect(obj.the_answer[0]).to eq(42) end end puts end end context "W/O: when the threads only send signals" do it "it works with a shared bus connection" do # shared connection bus = DBus::SessionBus.instance svc = bus.object_server obj = TestSignalRace.new "/org/ruby/Foo" svc.export obj race_threads(5) do |_j| repeat_with_jitter(10) do obj.signal_without_arguments end end svc.unexport(obj) puts end end end ruby-dbus-0.25.0/spec/platform_spec.rb0000755000004100000410000000046515000117217017661 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::Platform do describe ".macos?" do # code coverage chasing, as other tests mock it out it "doesn't crash" do expect { described_class.macos? }.to_not raise_error end end end ruby-dbus-0.25.0/spec/message_spec.rb0000755000004100000410000000122415000117217017453 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" # Pedantic full coverage test. # The happy paths are covered via calling classes describe DBus::Message do describe "#marshall" do it "raises when the object path is /org/freedesktop/DBus/Local" do m = DBus::Message.new(DBus::Message::SIGNAL) # the path is valid, it just must not be sent m.path = DBus::ObjectPath.new("/org/freedesktop/DBus/Local") m.interface = "org.example.spam" m.member = "Spam" expect { m.marshall }.to raise_error(RuntimeError, /Cannot send a message with the reserved path/) end end end ruby-dbus-0.25.0/spec/tools/0000755000004100000410000000000015000117217015626 5ustar www-datawww-dataruby-dbus-0.25.0/spec/tools/test_env0000755000004100000410000000141515000117217017404 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 set -o errexit #export DBUS_VERBOSE=1 #export RUBYOPT="-d" export RUBYOPT="$RUBYOPT -w" MYDIR=$(dirname $0) TOPDIR=$(realpath $MYDIR/../..) # Clean up at exit. trap "rm -rf \$RM_FILES" EXIT TERM INT export XDG_DATA_DIRS=`mktemp -d dbus.activation.XXXXXX` RM_FILES="$RM_FILES $XDG_DATA_DIRS" SVCDIR=$XDG_DATA_DIRS/dbus-1/services mkdir -p $SVCDIR for SVC in $TOPDIR/spec/mock-service/*.service; do SVCBASE=${SVC##*/} sed -e "s@\(Exec\)=/usr/bin/@\1=$TOPDIR/spec/mock-service/@" $SVC > $SVCDIR/$SVCBASE done # no command given -> run an interactive shell if [ $# = 0 ]; then set -- env PS1="TEST_ENV \w> " bash fi $MYDIR/dbus-launch-simple \ "$@"ruby-dbus-0.25.0/spec/tools/dbus-limited-session.conf0000644000004100000410000000412015000117217022535 0ustar www-datawww-data session EXTERNAL DBUS_COOKIE_SHA1 ANONYMOUS unix:tmpdir=/tmp unix:dir=/tmp tcp:host=127.0.0.1 50 5000 ruby-dbus-0.25.0/spec/tools/dbus-launch-simple0000755000004100000410000000237115000117217021253 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" # For debugging: # DBUS_DAEMON=~/svn/dbus/bus/dbus-daemon DBUS_VERBOSE=1 ./spec/tools/test_env env PS1="TEST $PS1" bash : ${DBUS_DAEMON=dbus-daemon} $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 & } # Clean up at exit. trap "kill \$KILLS; rm -rf \$RM_FILES" EXIT TERM INT my_dbus_launch # run the payload; the return value is passed on "$@" ruby-dbus-0.25.0/spec/introspection_spec.rb0000755000004100000410000000202415000117217020726 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true 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.25.0/spec/connection_spec.rb0000755000004100000410000000243415000117217020172 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::PeerConnection do let(:address) { ENV["DBUS_SESSION_BUS_ADDRESS"] } subject { described_class.new(address) } describe "#peer_service" do it "returns a PeerService with a nil name" do svc = subject.peer_service expect(svc).to be_a(DBus::ProxyService) expect(svc.name).to be_nil end end describe "#add_match, #remove_match" do it "doesn't crash trying to call AddMatch, RemoveMatch" do mr = DBus::MatchRule.new mr.member = "StateUpdated" mr.interface = "org.PulseAudio.Core1.Device" handler = ->(_msg) {} # Cheating a bit with the mocking: # a PulseAudio peer connection would error with # > DBus::Error: Method "AddMatch" with signature "s" on interface # > "org.freedesktop.DBus" doesn't exist # but here we do have a bus at the other end, which replies with # > DBus::Error: Client tried to send a message other than Hello without being registered # where "registering" is a libdbus-1 thing meaning "internal bookkeeping and send Hello" expect { subject.add_match(mr, &handler) }.to_not raise_error expect { subject.remove_match(mr) }.to_not raise_error end end end ruby-dbus-0.25.0/spec/property_spec.rb0000755000004100000410000002160015000117217017713 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" # FIXME: factor out DBus::TestFixtures::Value in spec_helper require "ostruct" require "yaml" data_dir = File.expand_path("data", __dir__) marshall_yaml_s = File.read("#{data_dir}/marshall.yaml") marshall_yaml = YAML.safe_load(marshall_yaml_s) 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 context "when reading a property fails" do it "gets an error, mentioning the qualified property name" do expect { @iface["Explosive"] } .to raise_error(DBus::Error, /getting.*SampleInterface.Explosive.*Something failed/) end end it "tests property nonreading" do expect { @iface["WriteMe"] }.to raise_error(DBus::Error, /not readable/) end context "writing properties" do it "tests property writing" do @iface["ReadOrWriteMe"] = "VALUE" expect(@iface["ReadOrWriteMe"]).to eq("VALUE") end context "when writing a read-only property" do it "gets an error, mentioning the qualified property name" do expect { @iface["ReadMe"] = "WROTE" } .to raise_error(DBus::Error, /SampleInterface.ReadMe.*not writable/) end end context "when writing a property fails" do it "gets an error, mentioning the qualified property name" do expect { @iface["WriteMe"] = "Bruno is a city in Czechia" } .to raise_error(DBus::Error, /setting.*SampleInterface.WriteMe/) end end end # https://github.com/mvidner/ruby-dbus/pull/19 it "tests service select timeout", slow: true 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 get all" do all = @iface.all_properties expect(all.keys.sort).to eq(["MyArray", "MyByte", "MyDict", "MyStruct", "MyVariant", "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(["MyArray", "MyByte", "MyDict", "MyStruct", "MyVariant", "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 it "errors for a property on an unknown interface" do # our idiomatic way would error out on interface lookup already, # so do it the low level way prop_if = @obj[DBus::PROPERTY_INTERFACE] expect { prop_if.Get("org.ruby.NoSuchInterface", "SomeProperty") }.to raise_error(DBus::Error) do |e| expect(e.name).to match(/UnknownProperty/) expect(e.message).to match(/no such interface/) end end it "errors for GetAll on an unknown interface" do # no idiomatic way? # so do it the low level way prop_if = @obj[DBus::PROPERTY_INTERFACE] expect { prop_if.GetAll("org.ruby.NoSuchInterface") }.to raise_error(DBus::Error) do |e| expect(e.name).to match(/UnknownProperty/) expect(e.message).to match(/no such interface/) end end it "receives a PropertiesChanged signal", slow: true do received = {} # TODO: for client side, provide a helper on_properties_changed, # or automate it even more in ProxyObject, ProxyObjectInterface prop_if = @obj[DBus::PROPERTY_INTERFACE] prop_if.on_signal("PropertiesChanged") do |_interface_name, changed_props, _invalidated_props| received.merge!(changed_props) end @iface["ReadOrWriteMe"] = "VALUE" @iface.SetTwoProperties("REAMDE", 255) # loop to process the signal. complicated :-( see signal_spec.rb loop = DBus::Main.new loop << @session_bus quitter = Thread.new do sleep 1 loop.quit end loop.run # quitter has told loop.run to quit quitter.join expect(received["ReadOrWriteMe"]).to eq("VALUE") expect(received["ReadMe"]).to eq("REAMDE") expect(received["MyByte"]).to eq(255) end context "a struct-typed property" do it "gets read as a struct, not an array (#97)" do struct = @iface["MyStruct"] expect(struct).to be_frozen end it "Get returns the correctly typed value (check with dbus-send)" do # As big as the DBus::Data branch is, # it still does not handle the :exact mode on the client/proxy side. # So we resort to parsing dbus-send output. cmd = "dbus-send --print-reply " \ "--dest=org.ruby.service " \ "/org/ruby/MyInstance " \ "org.freedesktop.DBus.Properties.Get " \ "string:org.ruby.SampleInterface " \ "string:MyStruct" reply = `#{cmd}` expect(reply).to match(/variant\s+struct {\s+string "three"\s+string "strings"\s+string "in a struct"\s+}/) end it "GetAll returns the correctly typed value (check with dbus-send)" do cmd = "dbus-send --print-reply " \ "--dest=org.ruby.service " \ "/org/ruby/MyInstance " \ "org.freedesktop.DBus.Properties.GetAll " \ "string:org.ruby.SampleInterface " reply = `#{cmd}` expect(reply).to match(/variant\s+struct {\s+string "three"\s+string "strings"\s+string "in a struct"\s+}/) end end context "an array-typed property" do it "gets read as an array" do val = @iface["MyArray"] expect(val).to eq([42, 43]) end end context "a dict-typed property" do it "gets read as a hash" do val = @iface["MyDict"] expect(val).to eq({ "one" => 1, "two" => "dva", "three" => [3, 3, 3] }) end it "Get returns the correctly typed value (check with dbus-send)" do cmd = "dbus-send --print-reply " \ "--dest=org.ruby.service " \ "/org/ruby/MyInstance " \ "org.freedesktop.DBus.Properties.Get " \ "string:org.ruby.SampleInterface " \ "string:MyDict" reply = `#{cmd}` # a bug about variant nesting lead to a "variant variant int32 1" value match_rx = /variant \s+ array \s \[ \s+ dict \s entry\( \s+ string \s "one" \s+ variant \s+ int32 \s 1 \s+ \)/x expect(reply).to match(match_rx) end end context "a variant-typed property" do it "gets read at all" do obj = @svc.object("/org/ruby/MyDerivedInstance") iface = obj["org.ruby.SampleInterface"] val = iface["MyVariant"] expect(val).to eq([42, 43]) end end context "a byte-typed property" do # Slightly advanced RSpec: # https://rspec.info/documentation/3.9/rspec-expectations/RSpec/Matchers.html#satisfy-instance_method let(:a_byte_in_a_variant) do satisfying { |x| x.is_a?(DBus::Data::Variant) && x.member_type.to_s == DBus::Type::BYTE } # ^ This formatting keeps the matcher on a single line # which enables RSpec to cite it if it fails, instead of saying "block". end let(:prop_iface) { @obj[DBus::PROPERTY_INTERFACE] } it "gets set with a correct type (#108)" do expect(prop_iface).to receive(:Set).with( "org.ruby.SampleInterface", "MyByte", a_byte_in_a_variant ) @iface["MyByte"] = 1 end it "gets set with a correct type (#108), when using the DBus.variant workaround" do expect(prop_iface).to receive(:Set).with( "org.ruby.SampleInterface", "MyByte", a_byte_in_a_variant ) @iface["MyByte"] = DBus.variant("y", 1) end end context "marshall.yaml round-trip via a VARIANT property" do marshall_yaml.each do |test| t = OpenStruct.new(test) next if t.val.nil? # Round trips do not work yet because the properties # must present a plain Ruby value so the exact D-Bus type is lost. # Round trips will work once users can declare accepting DBus::Data # in properties and method arguments. it "Sets #{t.sig.inspect}:#{t.val.inspect} and Gets something back" do before = DBus::Data.make_typed(t.sig, t.val) expect { @iface["MyVariant"] = before }.to_not raise_error expect { _after = @iface["MyVariant"] }.to_not raise_error # round-trip: # expect(after).to eq(before.value) end end end end ruby-dbus-0.25.0/spec/main_loop_spec.rb0000755000004100000410000000515515000117217020013 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true # Test the main loop require_relative "spec_helper" require "dbus" describe "DBus.logger" do it "will log debug messages if $DEBUG is true" do logger_old = DBus.logger DBus.logger = nil debug_old = $DEBUG $DEBUG = true DBus.logger.debug "this debug message will always be shown" $DEBUG = debug_old DBus.logger = logger_old end end 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", slow: true 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.25.0/spec/proxy_service_spec.rb0000755000004100000410000000204615000117217020733 0ustar www-datawww-data#!/usr/bin/env rspec # frozen_string_literal: true require_relative "spec_helper" require "dbus" describe DBus::ProxyService do context "when a private bus is set up", tag_service: true do 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 # This method is used by dbus-gui-gtk. # Deprecate it? In favor of introspecting the tree gradually # or move it to the application code? describe "#introspect" do it "creates the whole node tree" do svc = bus.service("org.ruby.service") expect { svc.introspect }.to_not raise_error expect(svc.root.dig("org", "ruby", "MyInstance")).to be_a DBus::Node end end end end ruby-dbus-0.25.0/.rspec0000644000004100000410000000002515000117217014646 0ustar www-datawww-data--color --format doc ruby-dbus-0.25.0/Rakefile0000755000004100000410000000376615000117217015220 0ustar www-datawww-data#! /usr/bin/env ruby # frozen_string_literal: true require "rake" require "fileutils" require "tmpdir" require "shellwords" begin require "rubocop/rake_task" rescue LoadError nil end begin require "yard" 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 desc "Run RSpec code examples" task "bare:spec", [:options] do |_t, args| args.with_defaults(options: "") sh "rspec #{args[:options]}" end ["spec"].each do |tname| desc "Run bare:#{tname} in the proper environment" task tname, [:options] do |_t, args| args.with_defaults(options: "") cd "spec/tools" do sh "./test_env rake bare:#{tname}[#{args[:options].shellescape}]" end end 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 if Object.const_defined? :RuboCop RuboCop::RakeTask.new else desc "Run RuboCop (dummy)" task :rubocop do warn "RuboCop not installed" end end YARD::Rake::YardocTask.new if Object.const_defined? :YARD ruby-dbus-0.25.0/COPYING0000644000004100000410000006350415000117217014577 0ustar www-datawww-data GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ruby-dbus-0.25.0/ruby-dbus.gemspec0000644000004100000410000000247315000117217017023 0ustar www-datawww-data# frozen_string_literal: true # -*- ruby -*- 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-2.1-or-later" s.author = "Ruby DBus Team" s.email = "martin.github@vidner.net" s.homepage = "https://github.com/mvidner/ruby-dbus" s.files = Dir[ "{doc,examples,lib,spec}/**/*", "COPYING", "NEWS.md", "Rakefile", "README.md", "ruby-dbus.gemspec", "VERSION", ".rspec" ] s.require_path = "lib" s.required_ruby_version = ">= 2.4.0" s.add_runtime_dependency "logger" # Either of rexml and nokogiri is required # but AFAIK gemspec cannot express that. # Nokogiri is recommended as rexml is dead slow. s.add_runtime_dependency "rexml" # s.add_runtime_dependency "nokogiri" # workaround: rubocop-1.0 needs base64 which is no longer in stdlib in newer rubies s.add_development_dependency "base64" s.add_development_dependency "ostruct" s.add_development_dependency "packaging_rake_tasks" s.add_development_dependency "rake" s.add_development_dependency "rspec", "~> 3" s.add_development_dependency "simplecov" s.add_development_dependency "simplecov-lcov" end ruby-dbus-0.25.0/VERSION0000644000004100000410000000000715000117217014601 0ustar www-datawww-data0.25.0 ruby-dbus-0.25.0/README.md0000644000004100000410000000537415000117217015024 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://github.com/mvidner/ruby-dbus/actions?query=branch%3Amaster [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://github.com/mvidner/ruby-dbus/workflows/CI/badge.svg?branch=master [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_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.4 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.25.0/examples/0000755000004100000410000000000015000117217015352 5ustar www-datawww-dataruby-dbus-0.25.0/examples/rhythmbox/0000755000004100000410000000000015000117217017376 5ustar www-datawww-dataruby-dbus-0.25.0/examples/rhythmbox/playpause.rb0000755000004100000410000000104315000117217021727 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true 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.25.0/examples/no-bus/0000755000004100000410000000000015000117217016555 5ustar www-datawww-dataruby-dbus-0.25.0/examples/no-bus/pulseaudio.rb0000755000004100000410000000246315000117217021264 0ustar www-datawww-data#! /usr/bin/env ruby # frozen_string_literal: true # find the library without external help $LOAD_PATH.unshift File.expand_path("../../lib", __dir__) require "dbus" def peer_address bus = DBus::SessionBus.instance svc = bus["org.PulseAudio1"] obj = svc["/org/pulseaudio/server_lookup1"] ifc = obj["org.PulseAudio.ServerLookup1"] adr = ifc["Address"] puts "PA address: #{adr}" adr end address = peer_address begin conn = DBus::PeerConnection.new(address) rescue Errno::ENOENT puts "Address exists but could not connect; telling PA to load the protocol" system "pactl load-module module-dbus-protocol" conn = DBus::PeerConnection.new(address) end no_svc = conn.peer_service obj = no_svc["/org/pulseaudio/core1"] ifc = obj["org.PulseAudio.Core1"] puts "PA version: #{ifc["Version"]}" puts "Waiting for volume changes, try adjusting it. Ctrl-C to exit." vol_ifc = "org.PulseAudio.Core1.Device" vol_member = "VolumeUpdated" # PA needs explicit enabling of signals ifc.ListenForSignal("#{vol_ifc}.#{vol_member}", []) match_rule = DBus::MatchRule.new match_rule.interface = vol_ifc match_rule.member = vol_member conn.add_match(match_rule) do |msg| # a single argument that is an array volumes = msg.params[0] puts "VolumeUpdated: #{volumes.join(", ")}" end loop = DBus::Main.new loop << conn loop.run ruby-dbus-0.25.0/examples/doc/0000755000004100000410000000000015000117217016117 5ustar www-datawww-dataruby-dbus-0.25.0/examples/doc/_extract_examples0000755000004100000410000000172715000117217021563 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true if ARGV[0].nil? puts "Usage: #{$PROGRAM_NAME} file.md" exit end base_url = "https://github.com/mvidner/ruby-dbus/blob/master/" base_url += ARGV[0].gsub("../", "") 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| anchor = title.downcase.gsub(/ +/, "-") e.write setup e.write "# #{base_url}##{anchor}\n" 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.25.0/examples/doc/README.md0000644000004100000410000000023215000117217017373 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.25.0/examples/utils/0000755000004100000410000000000015000117217016512 5ustar www-datawww-dataruby-dbus-0.25.0/examples/utils/listnames.rb0000755000004100000410000000055015000117217021041 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true require "dbus" d = if ARGV.member?("--system") DBus::SystemBus.instance else DBus::SessionBus.instance end d.proxy.ListNames[0].each do |n| puts "\t#{n}" qns = d.proxy.ListQueuedOwners(n)[0] next if qns.size == 1 && qns.first == n qns.each do |qn| puts "\t\t#{qn}" end end ruby-dbus-0.25.0/examples/utils/notify.rb0000755000004100000410000000057315000117217020357 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true 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.25.0/examples/service/0000755000004100000410000000000015000117217017012 5ustar www-datawww-dataruby-dbus-0.25.0/examples/service/complex-property.rb0000755000004100000410000000073115000117217022674 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true require "dbus" # Complex property class Test < DBus::Object dbus_interface "net.vidner.Scratch" do dbus_attr_reader :progress, "(stttt)" end def initialize(opath) @progress = ["working", 1, 0, 100, 42].freeze super(opath) end end bus = DBus::SessionBus.instance bus.object_server.export(Test.new("/net/vidner/Scratch")) bus.request_name("net.vidner.Scratch") DBus::Main.new.tap { |m| m << bus }.run ruby-dbus-0.25.0/examples/service/call_service.rb0000755000004100000410000000107615000117217022001 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true 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("Hey", "there!") p player["org.ruby.AnotherInterface"].Reverse("Hello world!") main = DBus::Main.new main << session_bus main.run ruby-dbus-0.25.0/examples/service/service_newapi.rb0000755000004100000410000000221415000117217022344 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true require "dbus" 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 myobj = Test.new("/org/ruby/MyInstance") bus.object_server.export(myobj) bus.request_name("org.ruby.service") 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.25.0/examples/simple/0000755000004100000410000000000015000117217016643 5ustar www-datawww-dataruby-dbus-0.25.0/examples/simple/get_id.rb0000755000004100000410000000100515000117217020422 0ustar www-datawww-data#! /usr/bin/env ruby # frozen_string_literal: true # find the library without external help $LOAD_PATH.unshift File.expand_path("../../lib", __dir__) require "dbus" busname = ARGV.fetch(0, "system") bus = busname == "session" ? DBus::SessionBus.instance : 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 #{busname} bus id is #{bus_id}" ruby-dbus-0.25.0/examples/simple/call_introspect.rb0000755000004100000410000000143315000117217022361 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true 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.25.0/examples/simple/properties.rb0000755000004100000410000000077415000117217021377 0ustar www-datawww-data#! /usr/bin/env ruby # frozen_string_literal: true 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.25.0/examples/no-introspect/0000755000004100000410000000000015000117217020156 5ustar www-datawww-dataruby-dbus-0.25.0/examples/no-introspect/nm-test.rb0000755000004100000410000000113415000117217022074 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true # # 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.25.0/examples/no-introspect/tracker-test.rb0000755000004100000410000000121615000117217023116 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true # # 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"])