ruby-dbus-0.25.0/ 0000755 0000041 0000041 00000000000 15000117217 013534 5 ustar www-data www-data ruby-dbus-0.25.0/NEWS.md 0000644 0000041 0000041 00000051671 15000117217 014644 0 ustar www-data www-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/ 0000755 0000041 0000041 00000000000 15000117217 014301 5 ustar www-data www-data ruby-dbus-0.25.0/doc/Tutorial.md 0000644 0000041 0000041 00000041600 15000117217 016427 0 ustar www-data www-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.md 0000644 0000041 0000041 00000024506 15000117217 016530 0 ustar www-data www-data Ruby 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/ 0000755 0000041 0000041 00000000000 15000117217 014302 5 ustar www-data www-data ruby-dbus-0.25.0/lib/dbus.rb 0000644 0000041 0000041 00000005023 15000117217 015564 0 ustar www-data www-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/ 0000755 0000041 0000041 00000000000 15000117217 015237 5 ustar www-data www-data ruby-dbus-0.25.0/lib/dbus/proxy_service.rb 0000644 0000041 0000041 00000006410 15000117217 020466 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000003330 15000117217 022021 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000010533 15000117217 020422 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000001633 15000117217 017046 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000022053 15000117217 017760 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000012503 15000117217 022314 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000001146 15000117217 017412 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000001606 15000117217 020051 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000005041 15000117217 020061 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000003022 15000117217 016712 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000011344 15000117217 016367 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000030352 15000117217 017372 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000005066 15000117217 021722 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000052117 15000117217 017040 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000047144 15000117217 016507 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000005730 15000117217 017555 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000012535 15000117217 020422 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000025732 15000117217 016366 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000013563 15000117217 020303 0 ustar www-data www-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/ 0000755 0000041 0000041 00000000000 15000117217 017047 5 ustar www-data www-data ruby-dbus-0.25.0/lib/dbus/core_ext/module/ 0000755 0000041 0000041 00000000000 15000117217 020334 5 ustar www-data www-data ruby-dbus-0.25.0/lib/dbus/core_ext/module/redefine_method.rb 0000644 0000041 0000041 00000003245 15000117217 024006 0 ustar www-data www-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/ 0000755 0000041 0000041 00000000000 15000117217 020154 5 ustar www-data www-data ruby-dbus-0.25.0/lib/dbus/core_ext/class/attribute.rb 0000644 0000041 0000041 00000006672 15000117217 022517 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000005703 15000117217 017535 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000026626 15000117217 016541 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000002036 15000117217 017356 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000004122 15000117217 016507 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000002022 15000117217 020104 0 ustar www-data www-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.xml 0000644 0000041 0000041 00000006022 15000117217 021716 0 ustar www-data www-data
ruby-dbus-0.25.0/lib/dbus/message.rb 0000644 0000041 0000041 00000021264 15000117217 017215 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000004375 15000117217 020535 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000030650 15000117217 017727 0 ustar www-data www-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