pax_global_header00006660000000000000000000000064136613352360014522gustar00rootroot0000000000000052 comment=37ede4242b48def73ada46c2747a4c5cae6abf45 dbussy-1.3/000077500000000000000000000000001366133523600126765ustar00rootroot00000000000000dbussy-1.3/.gitignore000066400000000000000000000000261366133523600146640ustar00rootroot00000000000000/__pycache__/ /build/ dbussy-1.3/COPYING000066400000000000000000000635101366133523600137360ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! dbussy-1.3/README000066400000000000000000000734451366133523600135730ustar00rootroot00000000000000DBussy is yet another Python binding for accessing D-Bus . I know there is already dbus-python , among others . So why do we need another one? The main issue is one of event loops. Most of the existing bindings seem to be based around GLib. However, Python now has its own “asyncio” event-loop architecture . This goes back to Python 3.4, but as of 3.5, you now have full-fledged coroutines (“async def” and “await”) as a language feature. Every GUI toolkit already provides its own event loop; so why did the Python developers decide to add yet another one? The answer seems clear: to provide a language-standard API for event loops, and a reference implementation for this API. It should be possible to adapt other event loops to this same API, and then Python code written to work with asyncio becomes event-loop agnostic. What Is D-Bus? ============== D-Bus is a high-level interprocess communication protocol. It also provides a standard daemon, that is included with the main Linux desktop environments, that implements a set of standard “buses”: a “system” bus that is created at system boot time, and a “session” bus that belongs to each user who logs into one of these desktop environments. Processes can register their services on one of these buses--the system bus for systemwide access, or the session bus for per-user access--where other processes can find them by name and connect to them. Or they can accept connections on entirely separate networking sockets, without any dependency on the D-Bus daemon. libdbus, the reference implementation for the low-level D-Bus protocol, supports both modes of operation. D-Bus is based around the concept of passing messages conforming to a standard, extensible format. Messages are of four types: * a “method call” * a “method return” (normal response to a method call) * an “error” (abnormal response to a method call) * a “signal” notification A method-call message is how one process requests a service of another process via D-Bus; the usual response would be a method-return message in the other direction indicating the completion status of the service being performed; it is also possible to send method-call messages without expecting a reply. If there was something wrong with the method-call message (e.g. inappropriate parameters, lack of permissions), then the response would be an error message. One could also send a method-return with information indicating a failure to perform the requested service; presumably the choice between the types of response is that an error return indicates a condition that is not supposed to happen--a bug in the requesting program. Signal messages are sent as notifications of interesting events pertaining to the current session (for the session bus) or the entire system (for the system bus). They are usually not sent to a specific destination, but can be picked up by all interested processes on the bus. There are no replies to signals; if the receiving process cannot or will not process a particular message, it simply ignores it. Messages optionally include the following information: * a destination “bus name” indicating the process that is to receive the message (this is not the name of the bus, but the name of a process on the bus) * an “object path” which looks like a POSIX absolute file name (always beginning with a slash and never ending with a slash, except for the root object “/”); the meaning of this is up to the receiving process, but it is intended to indicate some object within the hierarchy exposed by the process * an “interface name” which identifies the particular message protocol * a “method name” which identifies the particular function to be performed within that interface. Bus names and interface names look like domain names with the components reversed, so the top level is at the beginning. If you are familiar with package names in Java, they take the same form, and with the same intent: to reduce the chance of name conflicts. D-Bus also includes an extensive, but not extensible, type system for encoding data in a message. This data represents arguments to the method call or signal, return results for a method return or the error name and message for an error. A message contains a sequence of 0, 1 or more items of such data, each of which can be of various types: “basic” types (e.g. integer, float, string) or “container” types (structs, arrays, dictionaries) which in turn contain more values, each of which in turn can be of a basic or (recursively) another container type. A “signature” is a string encoding the type of a value, or sequence of values; there is also a “variant” type, which means the type of the value is encoded dynamically with the value itself, separate from the signature. The importance of type signatures is really up to the particular programs that are trying to communicate: some might insist on values exactly matching the expected type signature, whereas others might be more lenient. For example, while the D-Bus type system specifies different basic types for different sizes of integers of signed or unsigned varieties, most Python code will probably not care about the specific distinctions, and treat all these values as of type “int”. Standard Interfaces ------------------- D-Bus defines some standard interfaces which are meant to be understood by most if not all services. One fundamental one is the “org.freedesktop.DBus.Introspectable” interface; this defines an “Introspect” method, that is expected to return an XML string that describes all the interfaces understood by the object identified by the object path, as well as listing all the available child objects that can be accessed by appending a slash and the child name to the parent object path, if any. Introspection is a very important part of D-Bus: it is what allows users to discover what services are available on their installations, and throw together ad-hoc scripts in Python or other high-level languages to make convenient use of such services, without having to write a lot of interfacing code. Another commonly-supported interface is called “org.freedesktop.DBus.Properties”. This one defines the concept of *properties*, which are pieces of data notionally attached to object paths, and which might be readable, writable or both. This interface defines standard methods to get a property value for an object, set a new property value, or get all properties defined on an object. It also specifies a signal that can be sent by a server process as a general notification to all peers on the bus about changes to its property values. Enter DBussy ============ DBussy allows you to take advantage of asyncio, but it doesn’t force you to use it. DBussy is meant to give you access to (nearly) all the functionality of the underlying libdbus library . libdbus is a very low-level, very general library, designed to be called from C code, that makes no assumptions about event loops at all. Consider the basic task in a client program of sending a D-Bus request message and waiting for a reply; or consider a server waiting for a message to come in. libdbus offers 3 different ways to handle this: * poll repeatedly until the message appears * block the thread until the message comes in * specify a callback to be notified when the message is available DBussy offers access to all these ways. But it also gives you the option of engaging the asyncio event loop. This means you can be doing other things in the loop, and when a message comes in, it can be passed automatically to a callback that you previously specified. It also gives clients another way of sending a message and waiting for the reply: using a coroutine. For example async def request_reply(connection) : message = dbussy.Message.new_method_call(...) ... other setup of message args etc ... reply = await connection.send_await_reply(message, timeout) ... process reply ... #end request_reply loop = asyncio.get_event_loop() dbus_task = loop.create_task(request_reply(connection)) ... maybe create other tasks to run while awaiting reply ... loop.run_until_complete(dbus_task) On the server side, you can correspondingly use coroutines to handle time-consuming requests without blocking the main loop. A message filter or object-path message callback can return, instead of DBUS.HANDLER_RESULT_HANDLED, a coroutine. The wrapper code will give libdbus a result of DBUS.HANDLER_RESULT_HANDLED on your behalf, after creating a task to execute the coroutine on the event loop. The coroutine can then go on to handle the actual processing of the request, and return the reply at some later stage. The dbussy module also offers several more Pythonic facilities beyond those of the underlying libdbus, including a higher-level representation of type signatures as Type objects (and subclasses thereof), and an Introspection object hierarchy that can be easily converted to and from the standard D-Bus introspection XML representation. No Type-Guessing! ================= Unlike some other Python bindings for D-Bus libraries, DBussy never tries to guess how to convert Python types to D-Bus types. For example, D-Bus provides many different sizes of integer, in both signed and unsigned variants; when retrieving an integer parameter from a message, DBussy will convert all of these to Python integers, but when putting a Python integer into a message parameter, there must always be a signature somewhere specifying which specific D-Bus integer type to convert it to. Also, D-Bus variants are always represented in Python as a 2-tuple: the first element is the signature of the actual type being passed, and the second is the value of that type. Multithreading Caveats ====================== While libdbus was supposed to be usable with multithreaded code, in practice this has turned out to be problematic . The whole point of Python’s asyncio framework is to avoid multithreading anyway. The dbussy module does use multithreading in a very limited way, in just one place: the Connection.open_async() call. It does this to give the effect of a nonblocking equivalent to a call for which libdbus only offers a blocking version. Ravel: The Higher-Level Interface ================================= Rather than directly manipulating D-Bus message objects, it is usually more convenient to have a representation where D-Bus object paths are directly mapped to Python objects, and D-Bus method and signal calls are similarly mapped to calls on methods of those Python objects. So on the client side, the local Python objects become “proxies” for the actual objects implemented by the remote server. And on the server side, the implementation of an interface can be wrapped up in an “interface class” with methods that are automatically invoked in response to incoming D-Bus requests. Interface classes can also be used on the client side: in this situation, the method calls are just stubs used for type-checking outgoing requests, while the signal definitions can be real functions which are invoked in response to incoming signal messages. Conversely, on the server side, the signal definitions are stubs used for type-checking, while the method definitions (in the D-Bus sense) are real functions implementing those calls. An interface class can also be both client-side and server-side in one, which means all the method definitions are real, none are stubs. So it can be used both for type-checking outgoing messages and handling incoming ones. For example, this is true of both the common standard interfaces (Introspectable and Properties), since most if not all peers are expected to support them. (Signal definitions are a special case: even in a client-and-server-side interface, they can be marked as stubs--as in the standard PropertyHandler interface. This allows you to register such an interface for introspection purposes, without having to accept its handling of any signals.) Both kinds of interface representations are provided by the “ravel” module--interface classes on the client or server side, and proxy interfaces on the client side. Ravel also offers different ways to construct a proxy interface: you can define it yourself, or you can have Ravel construct it automatically for you by introspecting the server-side object. Either way, you start by creating a ravel.Connection object, which is a wrapper around a lower-level dbussy.Connection object. You can get one for the session or system bus by calling ravel.session_bus() and ravel.system_bus() respectively, or you can use a ravel.Server object (wrapping around the corresponding dbussy.Server) to accept connections on your own network address, separate from the D-Bus daemon. The Client Side: Proxy Interfaces --------------------------------- Proxy interfaces can be easily constructed in different ways. One way is to start with a proxy for a bus peer with a particular name. You get one of these with an expression that treats the connection as though it were a mapping: peer = conn[«bus_name»] Then you do another lookup on this mapping to get a reference to a particular object path at that peer: obj = peer[«object_path»] Now, you can get a proxy for a desired interface thus: iface = obj.get_interface(«interface_name») which causes automatic introspection of that object path on the peer to obtain all the necessary type information for that interface (if it is not one of the standard interfaces). So calling a Python method on this object results = iface.«method»(«args») translates automatically to the corresponding D-Bus method call to that object and interface on the remote server, with full type checking done on both arguments and results. Note that the method result is always a list. D-Bus properties are automatically mapped to Python properties, so you can access their values and assign new ones in the usual Python way. For example, adding 1 to a numeric property (written out the long way to demonstrate property access on both the LHS and RHS of the assignment): iface = conn[«bus_name»][«path»] \ .get_interface(«interface_name») iface.«prop» = iface.«prop» + 1 The above are *blocking* calls, which means the current thread is blocked while waiting for the reply to the method call. If you want to do things in a more event-loop-friendly fashion, then use get_async_interface instead of get_interface, which returns a coroutine object that evaluates to an asynchronous version of the proxy object when it finally completes. Method calls and property accesses on this are automatically also coroutine calls, so you can use them in await-constructs in your coroutines, or create asyncio tasks to run them etc. Here is an example use of the above calls, which pops up a GUI notification displaying a short message for 5 seconds. Because the introspection of this interface supplies names for the arguments, it is possible to pass them to the method call by keyword: ravel.session_bus()["org.freedesktop.Notifications"]["/org/freedesktop/Notifications"] \ .get_interface("org.freedesktop.Notifications") \ .Notify \ ( app_name = "test", replaces_id = 0, app_icon = "dialog-information", summary = "Hello World!", body = "DBussy works!", actions = [], hints = {}, timeout = 5000, ) (But note that the argument names might differ, depending on your Linux distro version. If you get errors saying certain argument names are not understood, try it without the argument names. Or do your own introspection of the interface, to decide what argument names should be used.) Proxy Interfaces: Alternative Order ----------------------------------- The above access to proxy interfaces could be described as “bus name-path-interface”, after the order in which the components are specified. Proxies can also be obtained in “bus name-interface-path” order. This can be convenient for obtaining a proxy interface object that can then be used to make calls on multiple objects. In this method, an initial *root* proxy is obtained thus: iface_root = conn[«bus_name»].get_interface \ ( path = «initial_path», interface = «interface_name», ) Note you need to specify an object path for this initial introspection; it is probably best to use the shortest (highest-level) path that supports that interface. Depending on the peer, the root path “/” might work. From this, you get the proxy for the interface on an actual object by using the object path as a lookup key, e.g. iface = iface_root[«path»] From here, you can invoke the method calls and access properties in the same way as before, e.g. iface.«method»(«args») ... iface.«prop» ... Asynchronous Properties ----------------------- As mentioned, both property and method access can be done asynchronously on an event loop. Asynchronous *reading* of a property is easy enough to express: val = await obj.«prop» But how do you do asynchronous *writing* of the property? The obvious construct await obj.«prop» = newval produces a syntax error: Python doesn’t (at least as of 3.6!) allow “await” on the left-hand side of an assignment. Instead, you write it as though it were a blocking call: obj.«prop» = newval but because the interface is defined as asynchronous, this causes a task to be queued on the event loop to oversee the completion of the set-property call, and your code can continue execution before this task completes. The main consequence of this is that any error exception will be raised asynchronously. But if you don’t like the idea that execution will be deferred, you can await the completion of all such pending property-setting calls with the following call on the root proxy: await iface_root.set_prop_flush() or on the actual proxy interface: await ravel.set_prop_flush(iface) whichever is more convenient. This means you can batch up a whole series of property-setting calls on any number of objects on the same interface and bus name, then wait for them all to complete with a single flush call. Interface Classes ----------------- The more structured high-level interface offered by Ravel is built around the concept of an *interface class*, which is a Python class that represents a D-Bus interface, either as an actual implementation or as a “proxy” for making calls to another bus peer. You can then register instances of this class on a bus connection at selected points in your object path hierarchy, to handle either only specific objects at those paths or as a fallback to also deal with objects at points below those, that do not have their own instance of this class registered. An interface class is identified by applying the @ravel.interface() decorator to the class definition, specifying the kind of interface (for use client-side, server-side or both), and the interface name, e.g. @ravel.interface(ravel.INTERFACE.SERVER, name = "com.example.my_interface") class MyClass : ... #end MyClass The meanings of the first, “kind”, argument to @ravel.interface are as follows: * INTERFACE.SERVER -- you are the server implementing the method calls defined by this interface. However, the signal definitions are just “stubs” used for type-checking when you send those signals over the bus. This interface definition can also be introspected to inform users about the facilities provided by the interface. * INTERFACE.CLIENT -- you are a client wanting to communicate with a server that implements this interface. The method calls are just stubs used for type-checking when you send those calls over the bus. The signal definitions can be your actual functions that you want to be invoked when those signals are received, or they can also be stubs. * INTERFACE.CLIENT_AND_SERVER -- both of the above; you implement the methods, and maybe the signals as well, and you can also use their definitions to send corresponding method and signal calls to peers that implement the same interface. The standard interfaces (Peer, Introspectable, Properties) are defined in this way. Within such a class, Python methods that are to handle D-Bus method calls are identified with the @ravel.method() decorator, e.g.: @ravel.method \ ( name = ..., in_signature = ..., out_signature = ..., args_keyword = ..., arg_keys = ..., arg_attrs = ..., result_keyword = ..., result_keys = ..., result_attrs = ..., connection_keyword = ..., message_keyword = ..., path_keyword = ..., bus_keyword = ..., set_result_keyword = ..., ... ) def my_method(...) : ... #end my_method As you can see, there are a large number of options for implementing such a method. It can also be defined as a coroutine with async def if you have an event loop attached, and Ravel will automatically queue the task for execution and await any returned result. Partial summary of arguments: * name -- the D-Bus method name. If omitted, defaults to the Python function name. * in_signature -- the D-Bus signature specifying the arguments (zero or more) to the method. * out_signature -- the D-Bus signature specifying the results (zero or more) the method will return. * args_keyword -- the name of an argument to the Python function that will be set to the arguments from the message method call. The arguments will be passed as a list, or a dict, or an attributed class, depending the specification of arg_keys and arg_attrs (see below). * path_keyword -- if specified, then the object path field from the incoming method call will be passed to the Python function as the value of the argument with this name. * message_keyword -- if specified, then the dbussy.Message object for the incoming method call will be passed to the Python function as the value of the argument with this name. * connection_keyword -- if specified, then the dbussy.Connection object will be passed to the Python function as the value of the argument with this name. * bus_keyword -- if specified, then the ravel.Connection object will be passed to the Python function as the value of the argument with this name. * set_result_keyword -- if specified, then a function of a single argument will be passed to the Python function as the value of the argument with this name; the argument passed by calling this function becomes the method result. Passing arguments: the argument with the name given by args_keyword will hold the extracted arguments from the method call message. If neither of arg_keys or arg_attrs is specified, then the arguments are passed as a list. If arg_keys is specified, then it must be a sequence of names that must match the number of types specified by the in_signature; in this case, the args will be passed as a dict with the keys given in arg_keys associated in order with the argument values. If arg_attrs is specified instead of arg_keys, then it must be a sequence of names that must match the number of types specified by the in_signature; a mutable attributed class object is created by calling ravel.def_attr_class, with the attribute names taken from arg_keys assigned in order to the argument values. Returning results: the function can return the result values to be inserted into the method-return message as the function result, by assigning to elements of a mutable result argument (passed as the argument named by result_keyword, or by calling the set_result function that was passed via the set_result_keyword (above). If neither result_keys nor result_attrs is specified, then the result is expected to be a sequence of values matching the out_signature. If it is returned as the function result, then it can be a tuple or list; but if result_keyword is specified, then the value of this is a list, and the values in the sequence must be assigned to the elements of this list in-place. If result_keys is specified, then the result is a dict mapping the names from result_keys to the values of the result sequence in order. If result_attrs is specified, then the result is a mutable attributed class object created by calling ravel.def_attr_class, mapping the names from result_attrs to the values of the result sequence in order. If result_keyword is not specified, then the result object is expected to be returned as the function result; otherwise, it is passed as the value of the argument named by result_keyword, and the handler is supposed to update its elements in-place. Signal definitions look similar, except they return no results: @ravel.signal \ ( name = ..., in_signature = ..., args_keyword = ..., arg_keys = ..., arg_attrs = ..., connection_keyword = ..., message_keyword = ..., path_keyword = ..., bus_keyword = ..., stub = ..., ... ) def my_signal(...) : ... #end my_signal Also note the “stub” argument--this has meaning on a client-side interface to indicate that the interface class does not implement the listener for the signal, but that it is registered separately with a listen_signal call. This is used for the PropertiesChanged signal in ravel.PropertyHandler (the standard handler for the DBUS.INTERFACE_PROPERTIES interface), so that you do not have to replace the class just to install your own listeners for this signal. Properties are defined by implementing getter and/or setter methods, identified by @propgetter() and @propsetter() decorators respectively: @ravel.propgetter \ ( name = ..., type = ..., name_keyword = ..., connection_keyword = ..., message_keyword = ..., path_keyword = ..., bus_keyword = ..., change_notification = ... ) def my_propgetter(...) : ... return \ «value» #end my_propgetter @ravel.propsetter \ ( name = ..., type = ..., name_keyword = ..., type_keyword = ..., value_keyword = ..., connection_keyword = ..., message_keyword = ..., path_keyword = ..., bus_keyword = ... ) : def my_propsetter(...) : ... #end my_propsetter Note the following arguments: * type -- the type signature for permitted property values. * change_notification -- one of the dbussy.PROP_CHANGE_NOTIFICATION values indicating whether (and what kind of) signals should be generated for changes to this property value. This is specified on the @propgetter(), because there is no point notifying about write-only properties. * type_keyword -- for passing the actual type of the new property value to the setter. * value_keyword -- for passing the new property value to the setter. Getters and setters can be coroutines. Custom User Data ---------------- With Ravel’s interface classes, it is possible to attach your own user data items to arbitrary points in the object path tree. To obtain the user data dictionary for a given object path, do either user_data = bus.user_data["/com/example/myapp"] or user_data = bus.user_data["com", "example", "myapp"] The result is a dictionary into which you can insert whatever key-value pairs you like, e.g.: user_data["com.example.myapp.attribs"] = MyObj(...) Predefined Interface Classes ---------------------------- Ravel provides predefined interface classes for the org.freedesktop.DBus.Peer, org.freedesktop.DBus.Introspectable and org.freedesktop.DBus.Properties interfaces, and these are automatically registered on Connection instances. The Peer interface is just a stub, since the actual implementation is hard-coded into libdbus itself; it is there to provide automatic introspection of this interface. The ravel.IntrospectionHandler class defines the standard Introspectable interface, and provides automatic introspection of all interfaces registered with a ravel.Connection (including itself and the other standard interfaces). It extracts the information specified to the class, method, signal and property-handler decorators, and generates the appropriate XML form for returning to D-Bus queries. The ravel.PropertyHandler class defines the standard Properties interface, and automatically dispatches to @propgetter() and @propsetter() methods as defined in your registered interface classes. The ravel.ManagedObjectsHandler class defines the standard ObjectManager interface. It handles sending out of notifications as you call the object_added() and object_removed() methods on a Connection. It is not automatically registered on a Connection object; you have to add it manually. If you already have a ravel.Connection object «conn», then you can do so with «conn».register_additional_standard(managed_objects = True) Alternatively, you can request this registration at the time of creating the Connection object in one step, for example with bus = ravel.session_bus(managed_objects = True) DBussy Examples =============== Sample code illustrating how to use DBussy/Ravel is available in my dbussy_examples repo on GitLab and GitHub . How Do You Pronounce “DBussy”? ============================== The name is a pun on “dbus” and the name of French Impressionist composer Claude Debussy. The most natural way to pronounce it would be the same as his name. At least, that’s my story, and I’m sticking to it. Lawrence D'Oliveiro 2020 February 2 dbussy-1.3/dbussy.py000066400000000000000000010230541366133523600145660ustar00rootroot00000000000000""" Pure-Python binding for D-Bus , built around libdbus . This Python binding supports hooking into event loops via Python’s standard asyncio module. """ #+ # Copyright 2017-2020 Lawrence D'Oliveiro . # Licensed under the GNU Lesser General Public License v2.1 or later. #- import os import builtins import operator import array import enum import ctypes as ct from weakref import \ ref as weak_ref, \ WeakValueDictionary import threading import io import atexit import asyncio from xml.etree import \ ElementTree as XMLElementTree from xml.sax.saxutils import \ quoteattr as quote_xml_attr dbus = ct.cdll.LoadLibrary("libdbus-1.so.3") class DBUS : "useful definitions adapted from the D-Bus includes. You will need to use the" \ " constants, but apart from that, see the more Pythonic wrappers defined outside" \ " this class in preference to accessing low-level structures directly." # General ctypes gotcha: when passing addresses of ctypes-constructed objects # to routine calls, do not construct the objects directly in the call. Otherwise # the refcount goes to 0 before the routine is actually entered, and the object # can get prematurely disposed. Always store the object reference into a local # variable, and pass the value of the variable instead. # from dbus-protocol.h: # Message byte order LITTLE_ENDIAN = 'l' BIG_ENDIAN = 'B' # Protocol version. MAJOR_PROTOCOL_VERSION = 1 # Type code that is never equal to a legitimate type code TYPE_INVALID = 0 # Primitive types TYPE_BYTE = ord('y') # 8-bit unsigned integer TYPE_BOOLEAN = ord('b') # boolean TYPE_INT16 = ord('n') # 16-bit signed integer TYPE_UINT16 = ord('q') # 16-bit unsigned integer TYPE_INT32 = ord('i') # 32-bit signed integer TYPE_UINT32 = ord('u') # 32-bit unsigned integer TYPE_INT64 = ord('x') # 64-bit signed integer TYPE_UINT64 = ord('t') # 64-bit unsigned integer TYPE_DOUBLE = ord('d') # 8-byte double in IEEE 754 format TYPE_STRING = ord('s') # UTF-8 encoded, nul-terminated Unicode string TYPE_OBJECT_PATH = ord('o') # D-Bus object path TYPE_SIGNATURE = ord('g') # D-Bus type signature TYPE_UNIX_FD = ord('h') # unix file descriptor basic_to_ctypes = \ { # ctypes objects suitable for holding values of D-Bus types TYPE_BYTE : ct.c_ubyte, TYPE_BOOLEAN : ct.c_ubyte, TYPE_INT16 : ct.c_short, TYPE_UINT16 : ct.c_ushort, TYPE_INT32 : ct.c_int, TYPE_UINT32 : ct.c_uint, TYPE_INT64 : ct.c_longlong, TYPE_UINT64 : ct.c_ulonglong, TYPE_DOUBLE : ct.c_double, TYPE_STRING : ct.c_char_p, TYPE_OBJECT_PATH : ct.c_char_p, TYPE_SIGNATURE : ct.c_char_p, TYPE_UNIX_FD : ct.c_int, } def int_subtype(i, bits, signed) : "returns integer i after checking that it fits in the given number of bits." if not isinstance(i, int) : raise TypeError("value is not int: %s" % repr(i)) #end if if signed : lo = - 1 << bits - 1 hi = (1 << bits - 1) - 1 else : lo = 0 hi = (1 << bits) - 1 #end if if i < lo or i > hi : raise ValueError \ ( "%d not in range of %s %d-bit value" % (i, ("unsigned", "signed")[signed], bits) ) #end if return \ i #end int_subtype subtype_boolean = lambda i : DBUS.int_subtype(i, 1, False) subtype_byte = lambda i : DBUS.int_subtype(i, 8, False) subtype_int16 = lambda i : DBUS.int_subtype(i, 16, True) subtype_uint16 = lambda i : DBUS.int_subtype(i, 16, False) subtype_int32 = lambda i : DBUS.int_subtype(i, 32, True) subtype_uint32 = lambda i : DBUS.int_subtype(i, 32, False) subtype_int64 = lambda i : DBUS.int_subtype(i, 64, True) subtype_uint64 = lambda i : DBUS.int_subtype(i, 64, False) int_convert = \ { # range checks for the various D-Bus integer types TYPE_BOOLEAN : subtype_boolean, TYPE_BYTE : subtype_byte, TYPE_INT16 : subtype_int16, TYPE_UINT16 : subtype_uint16, TYPE_INT32 : subtype_int32, TYPE_UINT32 : subtype_uint32, TYPE_INT64 : subtype_int64, TYPE_UINT64 : subtype_uint64, } # subclasses for distinguishing various special kinds of D-Bus values: class ObjectPath(str) : "an object path string." def __repr__(self) : return \ "%s(%s)" % (self.__class__.__name__, super().__repr__()) #end __repr__ #end ObjectPath class Signature(str) : "a type-signature string." def __repr__(self) : return \ "%s(%s)" % (self.__class__.__name__, super().__repr__()) #end __repr__ #end Signature class UnixFD(int) : "a file-descriptor integer." def __repr__(self) : return \ "%s(%s)" % (self.__class__.__name__, super().__repr__()) #end __repr__ #end UnixFD basic_subclasses = \ { TYPE_BOOLEAN : bool, TYPE_OBJECT_PATH : ObjectPath, TYPE_SIGNATURE : Signature, TYPE_UNIX_FD : UnixFD, } # Compound types TYPE_ARRAY = ord('a') # D-Bus array type TYPE_VARIANT = ord('v') # D-Bus variant type TYPE_STRUCT = ord('r') # a struct; however, type signatures use STRUCT_BEGIN/END_CHAR TYPE_DICT_ENTRY = ord('e') # a dict entry; however, type signatures use DICT_ENTRY_BEGIN/END_CHAR NUMBER_OF_TYPES = 16 # does not include TYPE_INVALID or STRUCT/DICT_ENTRY_BEGIN/END_CHAR # characters other than typecodes that appear in type signatures STRUCT_BEGIN_CHAR = ord('(') # start of a struct type in a type signature STRUCT_END_CHAR = ord(')') # end of a struct type in a type signature DICT_ENTRY_BEGIN_CHAR = ord('{') # start of a dict entry type in a type signature DICT_ENTRY_END_CHAR = ord('}') # end of a dict entry type in a type signature MAXIMUM_NAME_LENGTH = 255 # max length in bytes of a bus name, interface or member (object paths are unlimited) MAXIMUM_SIGNATURE_LENGTH = 255 # fits in a byte MAXIMUM_MATCH_RULE_LENGTH = 1024 MAXIMUM_MATCH_RULE_ARG_NUMBER = 63 MAXIMUM_ARRAY_LENGTH = 67108864 # 2 * 26 MAXIMUM_ARRAY_LENGTH_BITS = 26 # to store the max array size MAXIMUM_MESSAGE_LENGTH = MAXIMUM_ARRAY_LENGTH * 2 MAXIMUM_MESSAGE_LENGTH_BITS = 27 MAXIMUM_MESSAGE_UNIX_FDS = MAXIMUM_MESSAGE_LENGTH // 4 # FDs are at least 32 bits MAXIMUM_MESSAGE_UNIX_FDS_BITS = MAXIMUM_MESSAGE_LENGTH_BITS - 2 MAXIMUM_TYPE_RECURSION_DEPTH = 32 # Types of message MESSAGE_TYPE_INVALID = 0 # never a valid message type MESSAGE_TYPE_METHOD_CALL = 1 MESSAGE_TYPE_METHOD_RETURN = 2 MESSAGE_TYPE_ERROR = 3 MESSAGE_TYPE_SIGNAL = 4 NUM_MESSAGE_TYPES = 5 # Header flags HEADER_FLAG_NO_REPLY_EXPECTED = 0x1 HEADER_FLAG_NO_AUTO_START = 0x2 HEADER_FLAG_ALLOW_INTERACTIVE_AUTHORIZATION = 0x4 # Header fields HEADER_FIELD_INVALID = 0 HEADER_FIELD_PATH = 1 HEADER_FIELD_INTERFACE = 2 HEADER_FIELD_MEMBER = 3 HEADER_FIELD_ERROR_NAME = 4 HEADER_FIELD_REPLY_SERIAL = 5 HEADER_FIELD_DESTINATION = 6 HEADER_FIELD_SENDER = 7 HEADER_FIELD_SIGNATURE = 8 HEADER_FIELD_UNIX_FDS = 9 HEADER_FIELD_LAST = HEADER_FIELD_UNIX_FDS HEADER_SIGNATURE = bytes \ (( TYPE_BYTE, TYPE_BYTE, TYPE_BYTE, TYPE_BYTE, TYPE_UINT32, TYPE_UINT32, TYPE_ARRAY, STRUCT_BEGIN_CHAR, TYPE_BYTE, TYPE_VARIANT, STRUCT_END_CHAR, )) MINIMUM_HEADER_SIZE = 16 # smallest header size that can occur (missing required fields, though) # Errors ERROR_FAILED = "org.freedesktop.DBus.Error.Failed" # generic error ERROR_NO_MEMORY = "org.freedesktop.DBus.Error.NoMemory" ERROR_SERVICE_UNKNOWN = "org.freedesktop.DBus.Error.ServiceUnknown" ERROR_NAME_HAS_NO_OWNER = "org.freedesktop.DBus.Error.NameHasNoOwner" ERROR_NO_REPLY = "org.freedesktop.DBus.Error.NoReply" ERROR_IO_ERROR = "org.freedesktop.DBus.Error.IOError" ERROR_BAD_ADDRESS = "org.freedesktop.DBus.Error.BadAddress" ERROR_NOT_SUPPORTED = "org.freedesktop.DBus.Error.NotSupported" ERROR_LIMITS_EXCEEDED = "org.freedesktop.DBus.Error.LimitsExceeded" ERROR_ACCESS_DENIED = "org.freedesktop.DBus.Error.AccessDenied" ERROR_AUTH_FAILED = "org.freedesktop.DBus.Error.AuthFailed" ERROR_NO_SERVER = "org.freedesktop.DBus.Error.NoServer" ERROR_TIMEOUT = "org.freedesktop.DBus.Error.Timeout" ERROR_NO_NETWORK = "org.freedesktop.DBus.Error.NoNetwork" ERROR_ADDRESS_IN_USE = "org.freedesktop.DBus.Error.AddressInUse" ERROR_DISCONNECTED = "org.freedesktop.DBus.Error.Disconnected" ERROR_INVALID_ARGS = "org.freedesktop.DBus.Error.InvalidArgs" ERROR_FILE_NOT_FOUND = "org.freedesktop.DBus.Error.FileNotFound" ERROR_FILE_EXISTS = "org.freedesktop.DBus.Error.FileExists" ERROR_UNKNOWN_METHOD = "org.freedesktop.DBus.Error.UnknownMethod" ERROR_UNKNOWN_OBJECT = "org.freedesktop.DBus.Error.UnknownObject" ERROR_UNKNOWN_INTERFACE = "org.freedesktop.DBus.Error.UnknownInterface" ERROR_UNKNOWN_PROPERTY = "org.freedesktop.DBus.Error.UnknownProperty" ERROR_PROPERTY_READ_ONLY = "org.freedesktop.DBus.Error.PropertyReadOnly" ERROR_TIMED_OUT = "org.freedesktop.DBus.Error.TimedOut" ERROR_MATCH_RULE_NOT_FOUND = "org.freedesktop.DBus.Error.MatchRuleNotFound" ERROR_MATCH_RULE_INVALID = "org.freedesktop.DBus.Error.MatchRuleInvalid" ERROR_SPAWN_EXEC_FAILED = "org.freedesktop.DBus.Error.Spawn.ExecFailed" ERROR_SPAWN_FORK_FAILED = "org.freedesktop.DBus.Error.Spawn.ForkFailed" ERROR_SPAWN_CHILD_EXITED = "org.freedesktop.DBus.Error.Spawn.ChildExited" ERROR_SPAWN_CHILD_SIGNALED = "org.freedesktop.DBus.Error.Spawn.ChildSignaled" ERROR_SPAWN_FAILED = "org.freedesktop.DBus.Error.Spawn.Failed" ERROR_SPAWN_SETUP_FAILED = "org.freedesktop.DBus.Error.Spawn.FailedToSetup" ERROR_SPAWN_CONFIG_INVALID = "org.freedesktop.DBus.Error.Spawn.ConfigInvalid" ERROR_SPAWN_SERVICE_INVALID = "org.freedesktop.DBus.Error.Spawn.ServiceNotValid" ERROR_SPAWN_SERVICE_NOT_FOUND = "org.freedesktop.DBus.Error.Spawn.ServiceNotFound" ERROR_SPAWN_PERMISSIONS_INVALID = "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid" ERROR_SPAWN_FILE_INVALID = "org.freedesktop.DBus.Error.Spawn.FileInvalid" ERROR_SPAWN_NO_MEMORY = "org.freedesktop.DBus.Error.Spawn.NoMemory" ERROR_UNIX_PROCESS_ID_UNKNOWN = "org.freedesktop.DBus.Error.UnixProcessIdUnknown" ERROR_INVALID_SIGNATURE = "org.freedesktop.DBus.Error.InvalidSignature" ERROR_INVALID_FILE_CONTENT = "org.freedesktop.DBus.Error.InvalidFileContent" ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN = "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown" ERROR_ADT_AUDIT_DATA_UNKNOWN = "org.freedesktop.DBus.Error.AdtAuditDataUnknown" ERROR_OBJECT_PATH_IN_USE = "org.freedesktop.DBus.Error.ObjectPathInUse" ERROR_INCONSISTENT_MESSAGE = "org.freedesktop.DBus.Error.InconsistentMessage" ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED = "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired" # XML introspection format INTROSPECT_1_0_XML_NAMESPACE = "http://www.freedesktop.org/standards/dbus" INTROSPECT_1_0_XML_PUBLIC_IDENTIFIER = "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" INTROSPECT_1_0_XML_SYSTEM_IDENTIFIER = "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd" INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE = \ ( "\n" ) # from dbus-shared.h: # well-known bus types BusType = ct.c_uint BUS_SESSION = 0 BUS_SYSTEM = 1 BUS_STARTER = 2 # results that a message handler can return BusHandlerResult = ct.c_uint HANDLER_RESULT_HANDLED = 0 # no need to try more handlers HANDLER_RESULT_NOT_YET_HANDLED = 1 # see if other handlers want it HANDLER_RESULT_NEED_MEMORY = 2 # try again later with more memory # Bus names SERVICE_DBUS = "org.freedesktop.DBus" # used to talk to the bus itself # Paths PATH_DBUS = "/org/freedesktop/DBus" # object path used to talk to the bus itself PATH_LOCAL = "/org/freedesktop/DBus/Local" # path used in local/in-process-generated messages # Interfaces INTERFACE_DBUS = "org.freedesktop.DBus" # interface exported by the object with SERVICE_DBUS and PATH_DBUS INTERFACE_MONITORING = "org.freedesktop.DBus.Monitoring" # monitoring interface exported by the dbus-daemon INTERFACE_VERBOSE = "org.freedesktop.DBus.Verbose" # verbose interface exported by the dbus-daemon INTERFACE_INTROSPECTABLE = "org.freedesktop.DBus.Introspectable" # interface supported by introspectable objects INTERFACE_PROPERTIES = "org.freedesktop.DBus.Properties" # interface supported by objects with properties INTERFACE_PEER = "org.freedesktop.DBus.Peer" # interface supported by most dbus peers INTERFACE_LOCAL = "org.freedesktop.DBus.Local" # methods can only be invoked locally # Owner flags for request_name NAME_FLAG_ALLOW_REPLACEMENT = 0x1 NAME_FLAG_REPLACE_EXISTING = 0x2 NAME_FLAG_DO_NOT_QUEUE = 0x4 # Replies to request for a name REQUEST_NAME_REPLY_PRIMARY_OWNER = 1 REQUEST_NAME_REPLY_IN_QUEUE = 2 REQUEST_NAME_REPLY_EXISTS = 3 REQUEST_NAME_REPLY_ALREADY_OWNER = 4 # Replies to releasing a name RELEASE_NAME_REPLY_RELEASED = 1 RELEASE_NAME_REPLY_NON_EXISTENT = 2 RELEASE_NAME_REPLY_NOT_OWNER = 3 # Replies to service starts START_REPLY_SUCCESS = 1 START_REPLY_ALREADY_RUNNING = 2 # from dbus-types.h: bool_t = ct.c_uint # from dbus-memory.h: FreeFunction = ct.CFUNCTYPE(None, ct.c_void_p) # from dbus-connection.h: HandlerResult = ct.c_uint class Error(ct.Structure) : _fields_ = \ [ ("name", ct.c_char_p), ("message", ct.c_char_p), ("padding", 2 * ct.c_void_p), ] #end Error ErrorPtr = ct.POINTER(Error) WatchFlags = ct.c_uint WATCH_READABLE = 1 << 0 WATCH_WRITABLE = 1 << 1 WATCH_ERROR = 1 << 2 WATCH_HANGUP = 1 << 3 DispatchStatus = ct.c_uint DISPATCH_DATA_REMAINS = 0 # more data available DISPATCH_COMPLETE = 1 # all available data has been processed DISPATCH_NEED_MEMORY = 2 # not enough memory to continue AddWatchFunction = ct.CFUNCTYPE(bool_t, ct.c_void_p, ct.c_void_p) # add_watch(DBusWatch, user_data) returns success/failure WatchToggledFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p) # watch_toggled(DBusWatch, user_data) RemoveWatchFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p) # remove_watch(DBusWatch, user_data) AddTimeoutFunction = ct.CFUNCTYPE(bool_t, ct.c_void_p, ct.c_void_p) # add_timeout(DBusTimeout, user_data) returns success/failure TimeoutToggledFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p) # timeout_toggled(DBusTimeout, user_data) RemoveTimeoutFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p) # remove_timeout(DBusTimeout, user_data) DispatchStatusFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.POINTER(DispatchStatus), ct.c_void_p) # dispatch_status(DBusConnection, DBusDispatchStatus, user_data) WakeupMainFunction = ct.CFUNCTYPE(None, ct.c_void_p) # wakeup_main(user_data) AllowUnixUserFunction = ct.CFUNCTYPE(bool_t, ct.c_void_p, ct.c_ulong, ct.c_void_p) # allow_unix_user(DBusConnection, uid, user_data) returns success/failure AllowWindowsUserFunction = ct.CFUNCTYPE(bool_t, ct.c_void_p, ct.c_void_p, ct.c_void_p) # allow_windows_user(DBusConnection, user_sid, user_data)returns success/failure PendingCallNotifyFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p) # notify(DBusPendingCall, user_data) HandleMessageFunction = ct.CFUNCTYPE(HandlerResult, ct.c_void_p, ct.c_void_p, ct.c_void_p) # handle_message(DBusConnection, DBusMessage, user_data) ObjectPathUnregisterFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p) # unregister(DBusConnection, user_data) ObjectPathMessageFunction = ct.CFUNCTYPE(HandlerResult, ct.c_void_p, ct.c_void_p, ct.c_void_p) # handle_message(DBusConnection, DBusMessage, user_data) class ObjectPathVTable(ct.Structure) : pass #end ObjectPathVTable ObjectPathVTable._fields_ = \ [ ("unregister_function", ObjectPathUnregisterFunction), ("message_function", ObjectPathMessageFunction), ("internal_pad1", ct.CFUNCTYPE(None, ct.c_void_p)), ("internal_pad2", ct.CFUNCTYPE(None, ct.c_void_p)), ("internal_pad3", ct.CFUNCTYPE(None, ct.c_void_p)), ("internal_pad4", ct.CFUNCTYPE(None, ct.c_void_p)), ] ObjectPathVTablePtr = ct.POINTER(ObjectPathVTable) # from dbus-pending-call.h: TIMEOUT_INFINITE = 0x7fffffff TIMEOUT_USE_DEFAULT = -1 # from dbus-message.h: class MessageIter(ct.Structure) : "contains no public fields." _fields_ = \ [ ("dummy1", ct.c_void_p), ("dummy2", ct.c_void_p), ("dummy3", ct.c_uint), ("dummy4", ct.c_int), ("dummy5", ct.c_int), ("dummy6", ct.c_int), ("dummy7", ct.c_int), ("dummy8", ct.c_int), ("dummy9", ct.c_int), ("dummy10", ct.c_int), ("dummy11", ct.c_int), ("pad1", ct.c_int), ("pad2", ct.c_void_p), ("pad3", ct.c_void_p), ] #end MessageIter MessageIterPtr = ct.POINTER(MessageIter) # from dbus-server.h: NewConnectionFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p, ct.c_void_p) # new_connection(DBusServer, DBusConnection, user_data) # from dbus-signature.h: class SignatureIter(ct.Structure) : "contains no public fields." _fields_ = \ [ ("dummy1", ct.c_void_p), ("dummy2", ct.c_void_p), ("dummy8", ct.c_uint), ("dummy12", ct.c_int), ("dummy17", ct.c_int), ] #end SignatureIter SignatureIterPtr = ct.POINTER(SignatureIter) #end DBUS class DBUSX: "additional definitions not part of the official interfaces" DEFAULT_TIMEOUT = 25 # seconds, from dbus-connection-internal.h in libdbus source # For reference implementation for how to connect to daemon, # see libdbus sources, dbus/dbus-bus.c (internal_bus_get routine # and stuff that it calls) # environment variables used to find addresses of bus daemons SESSION_BUS_ADDRESS_VAR = "DBUS_SESSION_BUS_ADDRESS" SYSTEM_BUS_ADDRESS_VAR = "DBUS_SYSTEM_BUS_ADDRESS" STARTER_BUS_ADDRESS_VAR = "DBUS_STARTER_ADDRESS" STARTER_BUS_ADDRESS_TYPE = "DBUS_STARTER_BUS_TYPE" # values for value of STARTER_BUS_ADDRESS_TYPE # If cannot determine type, then default to session bus BUS_TYPE_SESSION = "session" BUS_TYPE_SYSTEM = "system" SYSTEM_BUS_ADDRESS = "unix:path=/var/run/dbus/system_bus_socket" # default system bus daemon address if value of SYSTEM_BUS_ADDRESS_VAR is not defined SESSION_BUS_ADDRESS = "autolaunch:" # default session bus daemon address if value of SESSION_BUS_ADDRESS_VAR is not defined INTERFACE_OBJECT_MANAGER = "org.freedesktop.DBus.ObjectManager" # no symbolic name for this in standard headers as yet #end DBUSX #+ # Useful stuff #- def _wderef(w_self, parent) : self = w_self() assert self != None, "%s has gone away" % parent return \ self #end _wderef def call_async(func, funcargs = (), timeout = None, abort = None, loop = None) : "invokes func on a separate temporary thread and returns a Future that" \ " can be used to wait for its completion and obtain its result. If timeout" \ " is not None, then waiters on the Future will get a TimeoutError exception" \ " if the function has not completed execution after that number of seconds." \ " This allows easy invocation of blocking I/O functions in an asyncio-" \ "compatible fashion. But note that the operation cannot be cancelled" \ " if the timeout elapses; instead, you can specify an abort callback" \ " which will be invoked with whatever result is eventually returned from" \ " func." if loop == None : loop = asyncio.get_event_loop() #end if timeout_task = None def func_done(ref_awaiting, result) : awaiting = ref_awaiting() if awaiting != None : if not awaiting.done() : awaiting.set_result(result) if timeout_task != None : timeout_task.cancel() #end if else : if abort != None : abort(result) #end if #end if #end if #end func_done def do_func_timedout(ref_awaiting) : awaiting = ref_awaiting() if awaiting != None : if not awaiting.done() : awaiting.set_exception(TimeoutError()) # Python doesn’t give me any (easy) way to cancel the thread running the # do_func() call, so just let it run to completion, whereupon func_done() # will get rid of the result. Even if I could delete the thread, can I be sure # that would clean up memory and OS/library resources properly? #end if #end if #end do_func_timedout def do_func(ref_awaiting) : # makes the blocking call on a separate thread. result = func(*funcargs) # A Future is not itself threadsafe, but I can thread-safely # run a callback on the main thread to set it. loop.call_soon_threadsafe(func_done, ref_awaiting, result) #end do_func #begin call_async awaiting = loop.create_future() ref_awaiting = weak_ref(awaiting) # weak ref to avoid circular refs with loop subthread = threading.Thread(target = do_func, args = (ref_awaiting,)) subthread.start() if timeout != None : timeout_task = loop.call_later(timeout, do_func_timedout, ref_awaiting) #end if return \ awaiting #end call_async #+ # Higher-level interface to type system #- class TYPE(enum.Enum) : "D-Bus type codes wrapped up in an enumeration." BYTE = ord('y') # 8-bit unsigned integer BOOLEAN = ord('b') # boolean INT16 = ord('n') # 16-bit signed integer UINT16 = ord('q') # 16-bit unsigned integer INT32 = ord('i') # 32-bit signed integer UINT32 = ord('u') # 32-bit unsigned integer INT64 = ord('x') # 64-bit signed integer UINT64 = ord('t') # 64-bit unsigned integer DOUBLE = ord('d') # 8-byte double in IEEE 754 format STRING = ord('s') # UTF-8 encoded, nul-terminated Unicode string OBJECT_PATH = ord('o') # D-Bus object path SIGNATURE = ord('g') # D-Bus type signature UNIX_FD = ord('h') # unix file descriptor ARRAY = ord('a') # array of elements all of same type, or possibly dict STRUCT = ord('r') # sequence of elements of arbitrary types VARIANT = ord('v') # a single element of dynamic type @property def is_basic(self) : "does this code represent a basic (non-container) type." return \ self.value in DBUS.basic_to_ctypes #end is_basic #end TYPE class Type : "base class for all Types. The “signature” property returns the fully-encoded" \ " signature string for the entire Type." @property def signature(self) : raise NotImplementedError("subclass forgot to override signature property") #end signature def __eq__(t1, t2) : raise NotImplementedError("subclass forgot to override __eq__ method") #end __eq__ def validate(self, val) : "returns val if it is an acceptable value of this Type, else raises" \ " TypeError or ValueError." raise NotImplementedError("subclass forgot to override validate method") #end validate def __repr__(self) : return \ "%s(sig = %s)" % (type(self).__name__, repr(self.signature)) #end __repr__ #end Type class BasicType(Type) : "a basic (non-container) type." __slots__ = ("code",) def __init__(self, code) : if not isinstance(code, TYPE) or not code.is_basic : raise TypeError("only basic TYPE.xxx values allowed") #end if self.code = code #end __init__ def __repr__(self) : return \ "%s(%s)" % (type(self).__name__, repr(self.code)) #end __repr__ @property def signature(self) : return \ chr(self.code.value) #end signature def __eq__(t1, t2) : return \ isinstance(t2, BasicType) and t1.code == t2.code #end __eq__ def validate(self, val) : if self.code.value in DBUS.int_convert : val = DBUS.int_convert[self.code.value](val) elif self.code == TYPE.DOUBLE : if not isinstance(val, float) : raise TypeError("expecting a float, not %s: %s" % (type(val).__name__, repr(val))) #end if elif self.code == TYPE.UNIX_FD : val = DBUS.subtype_uint32(val) elif DBUS.basic_to_ctypes[self.code.value] == ct.c_char_p : if not isinstance(val, str) : raise TypeError("expecting a string, not %s: %s" % (type(val).__name__, repr(val))) #end if else : raise RuntimeError("unknown basic type %s" % repr(self.code)) #end if return \ val #end validate #end BasicType class VariantType(Type) : "the variant type--a single element of a type determined at run-time." @property def signature(self) : return \ chr(TYPE.VARIANT.value) #end signature def __repr__(self) : return \ "%s()" % type(self).__name__ #end __repr__ def __eq__(t1, t2) : return \ isinstance(t2, VariantType) #end __eq__ def validate(self, val) : if not isinstance(val, (tuple, list)) or len(val) != 2 : raise ValueError("expecting a (type, value) pair") #end if valtype, val = val valtype = parse_single_signature(valtype) return \ (valtype, valtype.validate(val)) #end validate #end VariantType class StructType(Type) : "a sequence of one or more arbitrary types (empty structs are not allowed)." __slots__ = ("elttypes",) def __init__(self, *types) : if len(types) == 0 : raise TypeError("must have at least one element type") #end if if not all(isinstance(t, Type) for t in types) : raise TypeError("struct elements must be Types") #end if self.elttypes = tuple(types) #end __init__ def __repr__(self) : return \ "%s(%s)" % (type(self).__name__, repr(self.elttypes)) #end __repr__ @property def signature(self) : return \ "(%s)" % "".join(t.signature for t in self.elttypes) #end signature def __eq__(t1, t2) : return \ ( isinstance(t2, StructType) and len(t1.elttypes) == len(t2.elttypes) and all(e1 == e2 for e1, e2 in zip(t1.elttypes, t2.elttypes)) ) #end __eq__ def validate(self, val) : if not isinstance(val, (tuple, list)) or len(val) != len(self.elttypes) : raise TypeError \ ( "need a list or tuple of %d elements, not %s" % (len(self.elttypes), repr(val)) ) #end if return \ type(val)(elttype.validate(elt) for elttype, elt in zip(self.elttypes, val)) #end validate #end StructType class ArrayType(Type) : "an array of zero or more elements all of the same type." __slots__ = ("elttype",) def __init__(self, elttype) : if not isinstance(elttype, Type) : raise TypeError("invalid array element type") #end if self.elttype = elttype #end __init__ def __repr__(self) : return \ "%s[%s]" % (type(self).__name__, repr(self.elttype)) #end __repr__ @property def signature(self) : return \ chr(TYPE.ARRAY.value) + self.elttype.signature #end signature def __eq__(t1, t2) : return \ isinstance(t2, ArrayType) and t1.elttype == t2.elttype #end __eq__ def validate(self, val) : if not isinstance(val, (tuple, list)) : raise TypeError("need a tuple or list, not %s: %s" % (type(val).__name__, repr(val))) #end if return \ type(val)(self.elttype.validate(elt) for elt in val) #end validate #end ArrayType class DictType(Type) : "a dictionary mapping zero or more keys to values." __slots__ = ("keytype", "valuetype") def __init__(self, keytype, valuetype) : if not isinstance(keytype, BasicType) or not isinstance(valuetype, Type) : raise TypeError("invalid dict key/value type") #end if self.keytype = keytype self.valuetype = valuetype #end keytype def __repr__(self) : return \ "%s[%s : %s]" % (type(self).__name__, repr(self.keytype), repr(self.valuetype)) #end __repr__ @property def signature(self) : return \ "%s{%s%s}" % (chr(TYPE.ARRAY.value), self.keytype.signature, self.valuetype.signature) #end signature @property def entry_signature(self) : "signature for a dict entry." return \ "{%s%s}" % (self.keytype.signature, self.valuetype.signature) #end entry_signature def __eq__(t1, t2) : return \ isinstance(t2, DictType) and t1.keytype == t2.keytype and t1.valuetype == t2.valuetype #end __eq__ def validate(self, val) : if not isinstance(val, dict) : raise TypeError("need a dict, not %s: %s" % (type(val).__name__, repr(val))) #end if return \ type(val) \ ( (self.keytype.validate(key), self.valuetype.validate(val[key])) for key in val ) #end validate #end DictType def data_key(data) : "returns a unique value that allows data to be used as a dict/set key." if isinstance(data, (bytes, float, frozenset, int, str, tuple)) : result = data else : # data itself is non-hashable result = id(data) #end if return \ result #end data_key #+ # Library prototypes #- # from dbus-connection.h: dbus.dbus_connection_open.restype = ct.c_void_p dbus.dbus_connection_open.argtypes = (ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_connection_open_private.restype = ct.c_void_p dbus.dbus_connection_open_private.argtypes = (ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_connection_ref.restype = ct.c_void_p dbus.dbus_connection_ref.argtypes = (ct.c_void_p,) dbus.dbus_connection_unref.restype = None dbus.dbus_connection_unref.argtypes = (ct.c_void_p,) dbus.dbus_connection_close.restype = None dbus.dbus_connection_close.argtypes = (ct.c_void_p,) dbus.dbus_connection_get_is_connected.restype = DBUS.bool_t dbus.dbus_connection_get_is_connected.argtypes = (ct.c_void_p,) dbus.dbus_connection_get_is_authenticated.restype = DBUS.bool_t dbus.dbus_connection_get_is_authenticated.argtypes = (ct.c_void_p,) dbus.dbus_connection_get_is_anonymous.restype = DBUS.bool_t dbus.dbus_connection_get_is_anonymous.argtypes = (ct.c_void_p,) dbus.dbus_connection_get_server_id.restype = ct.c_void_p dbus.dbus_connection_get_server_id.argtypes = (ct.c_void_p,) dbus.dbus_connection_can_send_type.restype = DBUS.bool_t dbus.dbus_connection_can_send_type.argtypes = (ct.c_void_p, ct.c_int) dbus.dbus_connection_set_exit_on_disconnect.restype = None dbus.dbus_connection_set_exit_on_disconnect.argtypes = (ct.c_void_p, DBUS.bool_t) dbus.dbus_connection_preallocate_send.restype = ct.c_void_p dbus.dbus_connection_preallocate_send.argtypes = (ct.c_void_p,) dbus.dbus_connection_free_preallocated_send.restype = None dbus.dbus_connection_free_preallocated_send.argtypes = (ct.c_void_p, ct.c_void_p) dbus.dbus_connection_send_preallocated.restype = None dbus.dbus_connection_send_preallocated.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.POINTER(ct.c_uint)) dbus.dbus_connection_has_messages_to_send.restype = DBUS.bool_t dbus.dbus_connection_has_messages_to_send.argtypes = (ct.c_void_p,) dbus.dbus_connection_send.restype = DBUS.bool_t dbus.dbus_connection_send.argtypes = (ct.c_void_p, ct.c_void_p, ct.POINTER(ct.c_uint)) dbus.dbus_connection_send_with_reply.restype = DBUS.bool_t dbus.dbus_connection_send_with_reply.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_int) dbus.dbus_connection_send_with_reply_and_block.restype = ct.c_void_p dbus.dbus_connection_send_with_reply_and_block.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_int, DBUS.ErrorPtr) dbus.dbus_connection_flush.restype = None dbus.dbus_connection_flush.argtypes = (ct.c_void_p,) dbus.dbus_connection_read_write_dispatch.restype = DBUS.bool_t dbus.dbus_connection_read_write_dispatch.argtypes = (ct.c_void_p, ct.c_int) dbus.dbus_connection_read_write.restype = DBUS.bool_t dbus.dbus_connection_read_write.argtypes = (ct.c_void_p, ct.c_int) dbus.dbus_connection_borrow_message.restype = ct.c_void_p dbus.dbus_connection_borrow_message.argtypes = (ct.c_void_p,) dbus.dbus_connection_return_message.restype = None dbus.dbus_connection_return_message.argtypes = (ct.c_void_p, ct.c_void_p) dbus.dbus_connection_steal_borrowed_message.restype = None dbus.dbus_connection_steal_borrowed_message.argtypes = (ct.c_void_p, ct.c_void_p) dbus.dbus_connection_pop_message.restype = ct.c_void_p dbus.dbus_connection_pop_message.argtypes = (ct.c_void_p,) dbus.dbus_connection_get_dispatch_status.restype = ct.c_uint dbus.dbus_connection_get_dispatch_status.argtypes = (ct.c_void_p,) dbus.dbus_connection_dispatch.restype = ct.c_uint dbus.dbus_connection_dispatch.argtypes = (ct.c_void_p,) dbus.dbus_connection_set_watch_functions.restype = DBUS.bool_t dbus.dbus_connection_set_watch_functions.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_connection_set_timeout_functions.restype = DBUS.bool_t dbus.dbus_connection_set_timeout_functions.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_connection_set_wakeup_main_function.restype = None dbus.dbus_connection_set_wakeup_main_function.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_connection_set_dispatch_status_function.restype = None dbus.dbus_connection_set_dispatch_status_function.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_connection_get_unix_user.restype = DBUS.bool_t dbus.dbus_connection_get_unix_user.argtypes = (ct.c_void_p, ct.POINTER(ct.c_ulong)) dbus.dbus_connection_get_unix_process_id.restype = DBUS.bool_t dbus.dbus_connection_get_unix_process_id.argtypes = (ct.c_void_p, ct.POINTER(ct.c_ulong)) dbus.dbus_connection_get_adt_audit_session_data.restype = DBUS.bool_t dbus.dbus_connection_get_adt_audit_session_data.argtypes = (ct.c_void_p, ct.c_void_p, ct.POINTER(ct.c_uint)) dbus.dbus_connection_set_unix_user_function.restype = None dbus.dbus_connection_set_unix_user_function.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_connection_get_windows_user.restype = DBUS.bool_t dbus.dbus_connection_get_windows_user.argtypes = (ct.c_void_p, ct.c_void_p) dbus.dbus_connection_set_windows_user_function.restype = None dbus.dbus_connection_set_windows_user_function.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_connection_set_allow_anonymous.restype = None dbus.dbus_connection_set_allow_anonymous.argtypes = (ct.c_void_p, DBUS.bool_t) dbus.dbus_connection_set_route_peer_messages.restype = None dbus.dbus_connection_set_route_peer_messages.argtypes = (ct.c_void_p, DBUS.bool_t) dbus.dbus_connection_add_filter.restype = DBUS.bool_t dbus.dbus_connection_add_filter.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_connection_remove_filter.restype = None dbus.dbus_connection_remove_filter.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_connection_allocate_data_slot.restype = DBUS.bool_t dbus.dbus_connection_allocate_data_slot.argtypes = (ct.POINTER(ct.c_uint),) dbus.dbus_connection_free_data_slot.restype = None dbus.dbus_connection_free_data_slot.argtypes = (ct.c_uint,) dbus.dbus_connection_set_data.restype = DBUS.bool_t dbus.dbus_connection_set_data.argtypes = (ct.c_void_p, ct.c_uint, ct.c_void_p, ct.c_void_p) dbus.dbus_connection_get_data.restype = ct.c_void_p dbus.dbus_connection_get_data.argtypes = (ct.c_void_p, ct.c_uint) dbus.dbus_connection_set_change_sigpipe.restype = None dbus.dbus_connection_set_change_sigpipe.argtypes = (DBUS.bool_t,) dbus.dbus_connection_set_max_message_size.restype = None dbus.dbus_connection_set_max_message_size.argtypes = (ct.c_void_p, ct.c_long) dbus.dbus_connection_get_max_message_size.restype = ct.c_long dbus.dbus_connection_get_max_message_size.argtypes = (ct.c_void_p,) dbus.dbus_connection_set_max_received_size.restype = None dbus.dbus_connection_set_max_received_size.argtypes = (ct.c_void_p, ct.c_long) dbus.dbus_connection_get_max_received_size.restype = ct.c_long dbus.dbus_connection_get_max_received_size.argtypes = (ct.c_void_p,) dbus.dbus_connection_set_max_message_unix_fds.restype = None dbus.dbus_connection_set_max_message_unix_fds.argtypes = (ct.c_void_p, ct.c_long) dbus.dbus_connection_get_max_message_unix_fds.restype = ct.c_long dbus.dbus_connection_get_max_message_unix_fds.argtypes = (ct.c_void_p,) dbus.dbus_connection_set_max_received_unix_fds.restype = None dbus.dbus_connection_set_max_received_unix_fds.argtypes = (ct.c_void_p, ct.c_long) dbus.dbus_connection_get_max_received_unix_fds.restype = ct.c_long dbus.dbus_connection_get_max_received_unix_fds.argtypes = (ct.c_void_p,) dbus.dbus_connection_get_outgoing_size.restype = ct.c_long dbus.dbus_connection_get_outgoing_size.argtypes = (ct.c_void_p,) dbus.dbus_connection_get_outgoing_unix_fds.restype = ct.c_long dbus.dbus_connection_get_outgoing_unix_fds.argtypes = (ct.c_void_p,) dbus.dbus_connection_register_object_path.restype = DBUS.bool_t dbus.dbus_connection_register_object_path.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ObjectPathVTablePtr, ct.c_void_p) dbus.dbus_connection_try_register_object_path.restype = DBUS.bool_t dbus.dbus_connection_try_register_object_path.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ObjectPathVTablePtr, ct.c_void_p, DBUS.ErrorPtr) dbus.dbus_connection_register_fallback.restype = DBUS.bool_t dbus.dbus_connection_register_fallback.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ObjectPathVTablePtr, ct.c_void_p) dbus.dbus_connection_try_register_fallback.restype = DBUS.bool_t dbus.dbus_connection_try_register_fallback.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ObjectPathVTablePtr, ct.c_void_p, DBUS.ErrorPtr) dbus.dbus_connection_get_object_path_data.restype = DBUS.bool_t dbus.dbus_connection_get_object_path_data.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_void_p) dbus.dbus_connection_list_registered.restype = DBUS.bool_t dbus.dbus_connection_list_registered.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_void_p) dbus.dbus_connection_get_unix_fd.restype = DBUS.bool_t dbus.dbus_connection_get_unix_fd.argtypes = (ct.c_void_p, ct.POINTER(ct.c_int)) dbus.dbus_connection_get_socket.restype = DBUS.bool_t dbus.dbus_connection_get_socket.argtypes = (ct.c_void_p, ct.POINTER(ct.c_int)) dbus.dbus_connection_unregister_object_path.restype = DBUS.bool_t dbus.dbus_connection_unregister_object_path.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_watch_get_unix_fd.restype = ct.c_int dbus.dbus_watch_get_unix_fd.argtypes = (ct.c_void_p,) dbus.dbus_watch_get_socket.restype = ct.c_int dbus.dbus_watch_get_socket.argtypes = (ct.c_void_p,) dbus.dbus_watch_get_flags.restype = ct.c_uint dbus.dbus_watch_get_flags.argtypes = (ct.c_void_p,) dbus.dbus_watch_get_data.restype = ct.c_void_p dbus.dbus_watch_get_data.argtypes = (ct.c_void_p,) dbus.dbus_watch_set_data.restype = None dbus.dbus_watch_set_data.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_watch_handle.restype = DBUS.bool_t dbus.dbus_watch_handle.argtypes = (ct.c_void_p, ct.c_uint) dbus.dbus_watch_get_enabled.restype = DBUS.bool_t dbus.dbus_watch_get_enabled.argtypes = (ct.c_void_p,) dbus.dbus_timeout_get_interval.restype = ct.c_int dbus.dbus_timeout_get_interval.argtypes = (ct.c_void_p,) dbus.dbus_timeout_get_data.restype = ct.c_void_p dbus.dbus_timeout_get_data.argtypes = (ct.c_void_p,) dbus.dbus_timeout_set_data.restype = None dbus.dbus_timeout_set_data.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_timeout_handle.restype = DBUS.bool_t dbus.dbus_timeout_handle.argtypes = (ct.c_void_p,) dbus.dbus_timeout_get_enabled.restype = DBUS.bool_t dbus.dbus_timeout_get_enabled.argtypes = (ct.c_void_p,) # from dbus-bus.h: dbus.dbus_bus_get.restype = ct.c_void_p dbus.dbus_bus_get.argtypes = (ct.c_uint, DBUS.ErrorPtr) dbus.dbus_bus_get_private.restype = ct.c_void_p dbus.dbus_bus_get_private.argtypes = (ct.c_uint, DBUS.ErrorPtr) dbus.dbus_bus_register.restype = DBUS.bool_t dbus.dbus_bus_register.argtypes = (ct.c_void_p, DBUS.ErrorPtr) dbus.dbus_bus_set_unique_name.restype = DBUS.bool_t dbus.dbus_bus_set_unique_name.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_bus_get_unique_name.restype = ct.c_char_p dbus.dbus_bus_get_unique_name.argtypes = (ct.c_void_p,) dbus.dbus_bus_get_unix_user.restype = ct.c_ulong dbus.dbus_bus_get_unix_user.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_bus_get_id.restype = ct.c_void_p dbus.dbus_bus_get_id.argtypes = (ct.c_void_p, DBUS.ErrorPtr) dbus.dbus_bus_request_name.restype = ct.c_int dbus.dbus_bus_request_name.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_uint, DBUS.ErrorPtr) dbus.dbus_bus_release_name.restype = ct.c_int dbus.dbus_bus_release_name.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_bus_name_has_owner.restype = DBUS.bool_t dbus.dbus_bus_name_has_owner.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_bus_start_service_by_name.restype = DBUS.bool_t dbus.dbus_bus_start_service_by_name.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_uint, ct.POINTER(ct.c_uint), DBUS.ErrorPtr) dbus.dbus_bus_add_match.restype = None dbus.dbus_bus_add_match.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_bus_remove_match.restype = None dbus.dbus_bus_remove_match.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_error_init.restype = None dbus.dbus_error_init.argtypes = (DBUS.ErrorPtr,) dbus.dbus_error_free.restype = None dbus.dbus_error_free.argtypes = (DBUS.ErrorPtr,) dbus.dbus_move_error.restype = None dbus.dbus_move_error.argtypes = (DBUS.ErrorPtr, DBUS.ErrorPtr) dbus.dbus_error_has_name.restype = DBUS.bool_t dbus.dbus_error_has_name.argtypes = (DBUS.ErrorPtr, ct.c_char_p) dbus.dbus_error_is_set.restype = DBUS.bool_t dbus.dbus_error_is_set.argtypes = (DBUS.ErrorPtr,) dbus.dbus_set_error.restype = None dbus.dbus_set_error.argtypes = (DBUS.ErrorPtr, ct.c_char_p, ct.c_char_p, ct.c_char_p) # note I can’t handle varargs # from dbus-pending-call.h: dbus.dbus_pending_call_ref.restype = ct.c_void_p dbus.dbus_pending_call_ref.argtypes = (ct.c_void_p,) dbus.dbus_pending_call_unref.restype = None dbus.dbus_pending_call_unref.argtypes = (ct.c_void_p,) dbus.dbus_pending_call_set_notify.restype = DBUS.bool_t dbus.dbus_pending_call_set_notify.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_pending_call_cancel.restype = None dbus.dbus_pending_call_cancel.argtypes = (ct.c_void_p,) dbus.dbus_pending_call_get_completed.restype = DBUS.bool_t dbus.dbus_pending_call_get_completed.argtypes = (ct.c_void_p,) dbus.dbus_pending_call_steal_reply.restype = ct.c_void_p dbus.dbus_pending_call_steal_reply.argtypes = (ct.c_void_p,) dbus.dbus_pending_call_block.restype = None dbus.dbus_pending_call_block.argtypes = (ct.c_void_p,) dbus.dbus_pending_call_allocate_data_slot.restype = DBUS.bool_t dbus.dbus_pending_call_allocate_data_slot.argtypes = (ct.POINTER(ct.c_int),) dbus.dbus_pending_call_free_data_slot.restype = None dbus.dbus_pending_call_free_data_slot.argtypes = (ct.c_int,) dbus.dbus_pending_call_set_data.restype = DBUS.bool_t dbus.dbus_pending_call_set_data.argtypes = (ct.c_void_p, ct.c_int, ct.c_void_p, ct.c_void_p) dbus.dbus_pending_call_get_data.restype = ct.c_void_p dbus.dbus_pending_call_get_data.argtypes = (ct.c_void_p, ct.c_int) # from dbus-message.h: dbus.dbus_message_new.restype = ct.c_void_p dbus.dbus_message_new.argtypes = (ct.c_int,) dbus.dbus_message_new_method_call.restype = ct.c_void_p dbus.dbus_message_new_method_call.argtypes = (ct.c_char_p, ct.c_char_p, ct.c_char_p, ct.c_char_p) dbus.dbus_message_new_method_return.restype = ct.c_void_p dbus.dbus_message_new_method_return.argtypes = (ct.c_void_p,) dbus.dbus_message_new_signal.restype = ct.c_void_p dbus.dbus_message_new_signal.argtypes = (ct.c_char_p, ct.c_char_p, ct.c_char_p) dbus.dbus_message_new_error.restype = ct.c_void_p dbus.dbus_message_new_error.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_char_p) dbus.dbus_message_new_error_printf.restype = ct.c_void_p dbus.dbus_message_new_error_printf.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_char_p, ct.c_char_p) # note I can’t handle varargs dbus.dbus_message_copy.restype = ct.c_void_p dbus.dbus_message_copy.argtypes = (ct.c_void_p,) dbus.dbus_message_ref.restype = ct.c_void_p dbus.dbus_message_ref.argtypes = (ct.c_void_p,) dbus.dbus_message_unref.restype = None dbus.dbus_message_unref.argtypes = (ct.c_void_p,) dbus.dbus_message_get_type.restype = ct.c_int dbus.dbus_message_get_type.argtypes = (ct.c_void_p,) dbus.dbus_message_set_path.restype = DBUS.bool_t dbus.dbus_message_set_path.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_get_path.restype = ct.c_char_p dbus.dbus_message_get_path.argtypes = (ct.c_void_p,) dbus.dbus_message_has_path.restype = DBUS.bool_t dbus.dbus_message_has_path.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_set_interface.restype = DBUS.bool_t dbus.dbus_message_set_interface.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_get_interface.restype = ct.c_char_p dbus.dbus_message_get_interface.argtypes = (ct.c_void_p,) dbus.dbus_message_has_interface.restype = DBUS.bool_t dbus.dbus_message_has_interface.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_set_member.restype = DBUS.bool_t dbus.dbus_message_set_member.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_get_member.restype = ct.c_char_p dbus.dbus_message_get_member.argtypes = (ct.c_void_p,) dbus.dbus_message_has_member.restype = DBUS.bool_t dbus.dbus_message_has_member.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_set_error_name.restype = DBUS.bool_t dbus.dbus_message_set_error_name.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_get_error_name.restype = ct.c_char_p dbus.dbus_message_get_error_name.argtypes = (ct.c_void_p,) dbus.dbus_message_set_destination.restype = DBUS.bool_t dbus.dbus_message_set_destination.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_get_destination.restype = ct.c_char_p dbus.dbus_message_get_destination.argtypes = (ct.c_void_p,) dbus.dbus_message_set_sender.restype = DBUS.bool_t dbus.dbus_message_set_sender.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_get_sender.restype = ct.c_char_p dbus.dbus_message_get_sender.argtypes = (ct.c_void_p,) dbus.dbus_message_get_signature.restype = ct.c_char_p dbus.dbus_message_get_signature.argtypes = (ct.c_void_p,) dbus.dbus_message_set_no_reply.restype = None dbus.dbus_message_set_no_reply.argtypes = (ct.c_void_p, DBUS.bool_t) dbus.dbus_message_get_no_reply.restype = DBUS.bool_t dbus.dbus_message_get_no_reply.argtypes = (ct.c_void_p,) dbus.dbus_message_is_method_call.restype = DBUS.bool_t dbus.dbus_message_is_method_call.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_char_p) dbus.dbus_message_is_signal.restype = DBUS.bool_t dbus.dbus_message_is_signal.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_char_p) dbus.dbus_message_is_error.restype = DBUS.bool_t dbus.dbus_message_is_error.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_has_destination.restype = DBUS.bool_t dbus.dbus_message_has_destination.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_has_sender.restype = DBUS.bool_t dbus.dbus_message_has_sender.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_has_signature.restype = DBUS.bool_t dbus.dbus_message_has_signature.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_message_get_serial.restype = ct.c_uint dbus.dbus_message_get_serial.argtypes = (ct.c_void_p,) dbus.dbus_message_set_serial.restype = None dbus.dbus_message_set_serial.argtypes = (ct.c_void_p, ct.c_uint) dbus.dbus_message_set_reply_serial.restype = DBUS.bool_t dbus.dbus_message_set_reply_serial.argtypes = (ct.c_void_p, ct.c_uint) dbus.dbus_message_get_reply_serial.restype = ct.c_uint dbus.dbus_message_get_reply_serial.argtypes = (ct.c_void_p,) dbus.dbus_message_set_auto_start.restype = None dbus.dbus_message_set_auto_start.argtypes = (ct.c_void_p, DBUS.bool_t) dbus.dbus_message_get_auto_start.restype = DBUS.bool_t dbus.dbus_message_get_auto_start.argtypes = (ct.c_void_p,) dbus.dbus_message_get_path_decomposed.restype = DBUS.bool_t dbus.dbus_message_get_path_decomposed.argtypes = (ct.c_void_p, ct.c_void_p) dbus.dbus_message_append_args.restype = DBUS.bool_t dbus.dbus_message_append_args.argtypes = (ct.c_void_p, ct.c_int, ct.c_void_p, ct.c_int) # note I can’t handle varargs # probably cannot make use of dbus.dbus_message_append_args_valist dbus.dbus_message_get_args.restype = DBUS.bool_t dbus.dbus_message_get_args.argtypes = (ct.c_void_p, DBUS.ErrorPtr, ct.c_int, ct.c_void_p, ct.c_int) # note I can’t handle varargs # probably cannot make use of dbus.dbus_message_get_args_valist dbus.dbus_message_contains_unix_fds.restype = DBUS.bool_t dbus.dbus_message_contains_unix_fds.argtypes = (ct.c_void_p,) dbus.dbus_message_iter_init.restype = DBUS.bool_t dbus.dbus_message_iter_init.argtypes = (ct.c_void_p, DBUS.MessageIterPtr) dbus.dbus_message_iter_has_next.restype = DBUS.bool_t dbus.dbus_message_iter_has_next.argtypes = (DBUS.MessageIterPtr,) dbus.dbus_message_iter_next.restype = DBUS.bool_t dbus.dbus_message_iter_next.argtypes = (DBUS.MessageIterPtr,) dbus.dbus_message_iter_get_signature.restype = ct.c_void_p dbus.dbus_message_iter_next.argtypes = (DBUS.MessageIterPtr,) dbus.dbus_message_iter_get_signature.restype = ct.c_void_p dbus.dbus_message_iter_get_signature.argtypes = (DBUS.MessageIterPtr,) dbus.dbus_message_iter_get_arg_type.restype = ct.c_int dbus.dbus_message_iter_get_arg_type.argtypes = (DBUS.MessageIterPtr,) dbus.dbus_message_iter_get_element_type.restype = ct.c_int dbus.dbus_message_iter_get_element_type.argtypes = (DBUS.MessageIterPtr,) dbus.dbus_message_iter_recurse.restype = None dbus.dbus_message_iter_recurse.argtypes = (DBUS.MessageIterPtr, DBUS.MessageIterPtr) dbus.dbus_message_iter_get_basic.restype = None dbus.dbus_message_iter_get_basic.argtypes = (DBUS.MessageIterPtr, ct.c_void_p) if hasattr(dbus, "dbus_message_iter_get_element_count") : dbus.dbus_message_iter_get_element_count.restype = ct.c_int dbus.dbus_message_iter_get_element_count.argtypes = (DBUS.MessageIterPtr,) #end if # dbus_message_iter_get_array_len deprecated dbus.dbus_message_iter_get_fixed_array.restype = None dbus.dbus_message_iter_get_fixed_array.argtypes = (DBUS.MessageIterPtr, ct.c_void_p, ct.POINTER(ct.c_int)) dbus.dbus_message_iter_init_append.restype = None dbus.dbus_message_iter_init_append.argtypes = (ct.c_void_p, DBUS.MessageIterPtr) dbus.dbus_message_iter_append_basic.restype = DBUS.bool_t dbus.dbus_message_iter_append_basic.argtypes = (DBUS.MessageIterPtr, ct.c_int, ct.c_void_p) dbus.dbus_message_iter_append_fixed_array.restype = DBUS.bool_t dbus.dbus_message_iter_append_fixed_array.argtypes = (DBUS.MessageIterPtr, ct.c_int, ct.c_void_p, ct.c_int) dbus.dbus_message_iter_open_container.restype = DBUS.bool_t dbus.dbus_message_iter_open_container.argtypes = (DBUS.MessageIterPtr, ct.c_int, ct.c_char_p, DBUS.MessageIterPtr) dbus.dbus_message_iter_close_container.restype = DBUS.bool_t dbus.dbus_message_iter_close_container.argtypes = (DBUS.MessageIterPtr, DBUS.MessageIterPtr) dbus.dbus_message_iter_abandon_container.restype = None dbus.dbus_message_iter_abandon_container.argtypes = (DBUS.MessageIterPtr, DBUS.MessageIterPtr) dbus.dbus_message_lock.restype = None dbus.dbus_message_lock.argtypes = (DBUS.MessageIterPtr,) dbus.dbus_set_error_from_message.restype = DBUS.bool_t dbus.dbus_set_error_from_message.argtypes = (DBUS.ErrorPtr, ct.c_void_p) dbus.dbus_message_allocate_data_slot.restype = DBUS.bool_t dbus.dbus_message_allocate_data_slot.argtypes = (ct.POINTER(ct.c_int),) dbus.dbus_message_free_data_slot.restype = None dbus.dbus_message_free_data_slot.argtypes = (ct.POINTER(ct.c_int),) dbus.dbus_message_set_data.restype = DBUS.bool_t dbus.dbus_message_set_data.argtypes = (ct.c_void_p, ct.c_int, ct.c_void_p, ct.c_void_p) dbus.dbus_message_get_data.restype = ct.c_void_p dbus.dbus_message_get_data.argtypes = (ct.c_void_p, ct.c_int) dbus.dbus_message_type_from_string.restype = ct.c_int dbus.dbus_message_type_from_string.argtypes = (ct.c_char_p,) dbus.dbus_message_type_to_string.restype = ct.c_char_p dbus.dbus_message_type_to_string.argtypes = (ct.c_int,) dbus.dbus_message_marshal.restype = DBUS.bool_t dbus.dbus_message_marshal.argtypes = (ct.c_void_p, ct.c_void_p, ct.POINTER(ct.c_int)) dbus.dbus_message_demarshal.restype = ct.c_void_p dbus.dbus_message_demarshal.argtypes = (ct.c_void_p, ct.c_int, DBUS.ErrorPtr) dbus.dbus_message_demarshal_bytes_needed.restype = ct.c_int dbus.dbus_message_demarshal_bytes_needed.argtypes = (ct.c_void_p, ct.c_int) if hasattr(dbus, "dbus_message_set_allow_interactive_authorization") : dbus.dbus_message_set_allow_interactive_authorization.restype = None dbus.dbus_message_set_allow_interactive_authorization.argtypes = (ct.c_void_p, DBUS.bool_t) #end if if hasattr(dbus, "dbus_message_get_allow_interactive_authorization") : dbus.dbus_message_get_allow_interactive_authorization.restype = DBUS.bool_t dbus.dbus_message_get_allow_interactive_authorization.argtypes = (ct.c_void_p,) #end if # from dbus-memory.h: dbus.dbus_malloc.restype = ct.c_void_p dbus.dbus_malloc.argtypes = (ct.c_size_t,) dbus.dbus_malloc0.restype = ct.c_void_p dbus.dbus_malloc0.argtypes = (ct.c_size_t,) dbus.dbus_realloc.restype = ct.c_void_p dbus.dbus_realloc.argtypes = (ct.c_void_p, ct.c_size_t) dbus.dbus_free.restype = None dbus.dbus_free.argtypes = (ct.c_void_p,) dbus.dbus_free_string_array.restype = None dbus.dbus_free_string_array.argtypes = (ct.c_void_p,) # from dbus-misc.h: dbus.dbus_get_local_machine_id.restype = ct.c_void_p dbus.dbus_get_local_machine_id.argtypes = () dbus.dbus_get_version.restype = None dbus.dbus_get_version.argtypes = (ct.POINTER(ct.c_int), ct.POINTER(ct.c_int), ct.POINTER(ct.c_int)) dbus.dbus_setenv.restype = DBUS.bool_t dbus.dbus_setenv.argtypes = (ct.c_char_p, ct.c_char_p) # from dbus-address.h: dbus.dbus_parse_address.restype = DBUS.bool_t dbus.dbus_parse_address.argtypes = (ct.c_char_p, ct.c_void_p, ct.POINTER(ct.c_int), DBUS.ErrorPtr) dbus.dbus_address_entry_get_value.restype = ct.c_char_p dbus.dbus_address_entry_get_value.argtypes = (ct.c_void_p, ct.c_char_p) dbus.dbus_address_entry_get_method.restype = ct.c_char_p dbus.dbus_address_entry_get_method.argtypes = (ct.c_void_p,) dbus.dbus_address_entries_free.restype = None dbus.dbus_address_entries_free.argtypes = (ct.c_void_p,) dbus.dbus_address_escape_value.restype = ct.c_void_p dbus.dbus_address_escape_value.argtypes = (ct.c_char_p,) dbus.dbus_address_unescape_value.restype = ct.c_void_p dbus.dbus_address_unescape_value.argtypes = (ct.c_char_p, DBUS.ErrorPtr) # from dbus-signature.h: dbus.dbus_signature_iter_init.restype = None dbus.dbus_signature_iter_init.argtypes = (DBUS.SignatureIterPtr, ct.c_char_p) dbus.dbus_signature_iter_get_current_type.restype = ct.c_int dbus.dbus_signature_iter_get_current_type.argtypes = (DBUS.SignatureIterPtr,) dbus.dbus_signature_iter_get_signature.restype = ct.c_void_p dbus.dbus_signature_iter_get_signature.argtypes = (DBUS.SignatureIterPtr,) dbus.dbus_signature_iter_get_element_type.restype = ct.c_int dbus.dbus_signature_iter_get_element_type.argtypes = (DBUS.SignatureIterPtr,) dbus.dbus_signature_iter_next.restype = DBUS.bool_t dbus.dbus_signature_iter_next.argtypes = (DBUS.SignatureIterPtr,) dbus.dbus_signature_iter_recurse.restype = None dbus.dbus_signature_iter_recurse.argtypes = (DBUS.SignatureIterPtr, DBUS.SignatureIterPtr) dbus.dbus_signature_validate.restype = DBUS.bool_t dbus.dbus_signature_validate.argtypes = (ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_signature_validate_single.restype = DBUS.bool_t dbus.dbus_signature_validate_single.argtypes = (ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_type_is_valid.restype = DBUS.bool_t dbus.dbus_type_is_valid.argtypes = (ct.c_int,) dbus.dbus_type_is_basic.restype = DBUS.bool_t dbus.dbus_type_is_basic.argtypes = (ct.c_int,) dbus.dbus_type_is_container.restype = DBUS.bool_t dbus.dbus_type_is_container.argtypes = (ct.c_int,) dbus.dbus_type_is_fixed.restype = DBUS.bool_t dbus.dbus_type_is_fixed.argtypes = (ct.c_int,) # from dbus-syntax.h: dbus.dbus_validate_path.restype = DBUS.bool_t dbus.dbus_validate_path.argtypes = (ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_validate_interface.restype = DBUS.bool_t dbus.dbus_validate_interface.argtypes = (ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_validate_member.restype = DBUS.bool_t dbus.dbus_validate_member.argtypes = (ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_validate_error_name.restype = DBUS.bool_t dbus.dbus_validate_error_name.argtypes = (ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_validate_bus_name.restype = DBUS.bool_t dbus.dbus_validate_bus_name.argtypes = (ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_validate_utf8.restype = DBUS.bool_t dbus.dbus_validate_utf8.argtypes = (ct.c_char_p, DBUS.ErrorPtr) # from dbus-server.h: dbus.dbus_server_listen.restype = ct.c_void_p dbus.dbus_server_listen.argtypes = (ct.c_char_p, DBUS.ErrorPtr) dbus.dbus_server_ref.restype = ct.c_void_p dbus.dbus_server_ref.argtypes = (ct.c_void_p,) dbus.dbus_server_unref.restype = ct.c_void_p dbus.dbus_server_unref.argtypes = (ct.c_void_p,) dbus.dbus_server_disconnect.restype = None dbus.dbus_server_disconnect.argtypes = (ct.c_void_p,) dbus.dbus_server_get_is_connected.restype = DBUS.bool_t dbus.dbus_server_get_is_connected.argtypes = (ct.c_void_p,) dbus.dbus_server_get_address.restype = ct.c_void_p dbus.dbus_server_get_address.argtypes = (ct.c_void_p,) dbus.dbus_server_get_id.restype = ct.c_void_p dbus.dbus_server_get_id.argtypes = (ct.c_void_p,) dbus.dbus_server_set_new_connection_function.restype = None dbus.dbus_server_set_new_connection_function.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_server_set_watch_functions.restype = DBUS.bool_t dbus.dbus_server_set_watch_functions.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_server_set_timeout_functions.restype = DBUS.bool_t dbus.dbus_server_set_timeout_functions.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p) dbus.dbus_server_set_auth_mechanisms.restype = DBUS.bool_t dbus.dbus_server_set_auth_mechanisms.argtypes = (ct.c_void_p, ct.c_void_p) dbus.dbus_server_allocate_data_slot.restype = DBUS.bool_t dbus.dbus_server_allocate_data_slot.argtypes = (ct.POINTER(ct.c_int),) dbus.dbus_server_free_data_slot.restype = DBUS.bool_t dbus.dbus_server_free_data_slot.argtypes = (ct.POINTER(ct.c_int),) dbus.dbus_server_set_data.restype = DBUS.bool_t dbus.dbus_server_set_data.argtypes = (ct.c_void_p, ct.c_int, ct.c_void_p, ct.c_void_p) dbus.dbus_server_set_data.restype = ct.c_void_p dbus.dbus_server_set_data.argtypes = (ct.c_void_p, ct.c_int) # TODO dbus-threads.h # Seems like the only call worth making is dbus_threads_init_default. #+ # High-level stuff follows #- class DBusError(Exception) : "for raising an exception that reports a D-Bus error name and accompanying message." __slots__ = ("name", "message") def __init__(self, name, message) : self.args = ("%s -- %s" % (name, message),) self.name = name self.message = message #end __init__ #end DBusError class CallFailed(Exception) : "used internally for reporting general failure from calling a libdbus routine." __slots__ = ("funcname",) def __init__(self, funcname) : self.args = ("%s failed" % funcname,) self.funcname = funcname #end __init__ #end CallFailed class _Abort(Exception) : pass #end _Abort class TaskKeeper : "Base class for classes that need to call EventLoop.create_task() to" \ " schedule caller-created coroutines for execution. asyncio only keeps" \ " weak references to Task objects when they are not being scheduled," \ " so to keep them from disappearing unexpectedly, I maintain a list of" \ " strong references here, and periodically clean them out as they end" \ " execution." __slots__ = ("__weakref__", "loop", "_cur_tasks") def _init(self) : # avoid __init__ so I don't get passed spurious args self.loop = None self._cur_tasks = [] #end _init def create_task(self, coro) : assert self.loop != None, "no event loop to attach coroutine to" task = self.loop.create_task(coro) if len(self._cur_tasks) == 0 : self.loop.call_soon(self._reaper, weak_ref(self)) #end if self._cur_tasks.append(task) #end create_task @staticmethod def _reaper(self) : self = self() # avoid reference circularity old_tasks = self._cur_tasks[:] new_tasks = self._cur_tasks new_tasks[:] = [] for task in old_tasks : if not task.done() : new_tasks.append(task) #end if #end for if len(new_tasks) != 0 : self.loop.call_soon(self._reaper, weak_ref(self)) #end if #end _reaper #end TaskKeeper # Misc: def get_local_machine_id() : "returns a systemwide unique ID that is supposed to remain constant at least" \ " until the next reboot. Two processes seeing the same value for this can assume" \ " they are on the same machine." c_result = dbus.dbus_get_local_machine_id() if c_result == None : raise CallFailed("dbus_get_local_machine_id") #end if result = ct.cast(c_result, ct.c_char_p).value.decode() dbus.dbus_free(c_result) return \ result #end get_local_machine_id def get_version() : "returns the libdbus library version as a tuple of integers (major, minor, micro)." major = ct.c_int() minor = ct.c_int() micro = ct.c_int() dbus.dbus_get_version(ct.byref(major), ct.byref(minor), ct.byref(micro)) return \ (major.value, minor.value, micro.value) #end get_version def setenv(key, value) : key = key.encode() if value != None : value = value.encode() #end if if not dbus.dbus_setenv(key, value) : raise CallFailed("dbus_setenv") #end if #end setenv def unsetenv(key) : setenv(key, None) #end unsetenv class Watch : "wrapper around a DBusWatch object. Do not instantiate directly; they" \ " are created and destroyed by libdbus.\n" \ "\n" \ "A Watch is the basic mechanism for plugging libdbus-created file descriptors" \ " into your event loop. When created, they are passed to your add-watch callback" \ " to manage; and conversely, when deleted, your remove-watch callback is notified." \ " (These callbacks are ones you attach to Server and Connection objects.)\n" \ "\n" \ "Check the enabled property to decide if you need to pay attention to this Watch, and" \ " look at the flags to see if you need to check for pending reads, or writes, or both." \ " Call the handle() method with the appropriate flags when you see that reads or writes" \ " are pending." # __slots__ = ("__weakref__", "_dbobj",) # to forestall typos _instances = WeakValueDictionary() def __new__(celf, _dbobj) : self = celf._instances.get(_dbobj) if self == None : self = super().__new__(celf) self._dbobj = _dbobj celf._instances[_dbobj] = self #end if return \ self #end __new__ # no __del__ method -- no underlying dispose API call @property def unix_fd(self) : "the underlying file descriptor for this Watch." return \ dbus.dbus_watch_get_unix_fd(self._dbobj) #end unix_fd def fileno(self) : "for use with Python’s “select” functions." return \ self.unix_fd #end fileno @property def socket(self) : return \ dbus.dbus_watch_get_socket(self._dbobj) #end socket @property def flags(self) : "returns WATCH_READABLE and/or WATCH_WRITABLE, indicating what to watch for." return \ dbus.dbus_watch_get_flags(self._dbobj) #end flags # TODO: get/set data def handle(self, flags) : "tells libdbus that there is something to be read or written." \ " flags are a combination of WATCH_xxx values." return \ dbus.dbus_watch_handle(self._dbobj, flags) != 0 #end handle @property def enabled(self) : "does libdbus want you to actually watch this Watch." return \ dbus.dbus_watch_get_enabled(self._dbobj) != 0 #end enabled #end Watch class Timeout : "wrapper around a DBusTimeout object. Do not instantiate directly; they" \ " are created and destroyed by libdbus.\n" \ "\n" \ " A Timeout is the basic mechanism for plugging libdbus-created timeouts" \ " into your event loop. When created, they are passed to your add-timeout" \ " callback to manage; and conversely, when deleted, your remove-timeout" \ " callback is notified. (These callbacks are ones you attach to Server and" \ " Connection objects.)\n" \ "\n" \ "Check the enabled property to decide if you need to pay attention to this" \ " Timeout. Call the handle() method when the timeout becomes due, as measured" \ " from when it was initially created or most recently enabled, whichever" \ " happened last." # __slots__ = ("__weakref__", "_dbobj",) # to forestall typos _instances = WeakValueDictionary() def __new__(celf, _dbobj) : self = celf._instances.get(_dbobj) if self == None : self = super().__new__(celf) self._dbobj = _dbobj celf._instances[_dbobj] = self #end if return \ self #end __new__ # no __del__ method -- no underlying dispose API call @property def interval(self) : "how long in float seconds until the timeout should fire." return \ dbus.dbus_timeout_get_interval(self._dbobj) / 1000 #end interval # TODO: get/set data def handle(self) : "tells libdbus the timeout has fired." return \ dbus.dbus_timeout_handle(self._dbobj) #end handle @property def enabled(self) : "does libdbus want you to actually schedule this Timeout." return \ dbus.dbus_timeout_get_enabled(self._dbobj) != 0 #end enabled #end Timeout class ObjectPathVTable(TaskKeeper) : "wrapper around an ObjectPathVTable struct. You can instantiate directly, or call" \ " the init method. An additional feature beyond the underlying libdbus capabilities" \ " is the option to specify an asyncio event loop. If the message handler returns" \ " a coroutine, then an asyncio task is created to run it, and a result of" \ " DBUS.HANDLER_RESULT_HANDLED is returned on behalf of the message handler;" \ " that way, the message function can do the minimum beyond some initial filtering of" \ " the message, leaving the time-consuming part of the work to the coroutine." __slots__ = \ ( "_dbobj", # need to keep references to ctypes-wrapped functions # so they don't disappear prematurely: "_wrap_unregister_func", "_wrap_message_func", ) # to forestall typos def __init__(self, *, loop = None, unregister = None, message = None) : super().__init__() super()._init() self._dbobj = DBUS.ObjectPathVTable() self.loop = loop self._wrap_unregister_func = None self._wrap_message_func = None if unregister != None : self.set_unregister(unregister) #end if if message != None : self.set_message(message) #end if #end __init__ @classmethod def init(celf, *, loop = None, unregister = None, message = None) : "for consistency with other classes that don’t want caller to instantiate directly." return \ celf \ ( loop = loop, unregister = unregister, message = message, ) #end init def set_unregister(self, unregister) : def wrap_unregister(c_conn, c_user_data) : conn = Connection(dbus.dbus_connection_ref(c_conn)) unregister(conn, conn._user_data.get(c_user_data)) #end wrap_unregister #begin set_unregister if unregister != None : self._wrap_unregister_func = DBUS.ObjectPathUnregisterFunction(wrap_unregister) else : self._wrap_unregister_func = None #end if self._dbobj.unregister_function = self._wrap_unregister_func return \ self #end set_unregister def set_message(self, message) : w_self = weak_ref(self) def wrap_message(c_conn, c_message, c_user_data) : self = _wderef(w_self, "vtable") conn = Connection(dbus.dbus_connection_ref(c_conn)) msg = Message(dbus.dbus_message_ref(c_message)) user_data = conn._user_data.get(c_user_data) result = message(conn, msg, user_data) if asyncio.iscoroutine(result) : self.create_task(result) result = DBUS.HANDLER_RESULT_HANDLED #end if return \ result #end wrap_message #begin set_message if message != None : self._wrap_message_func = DBUS.ObjectPathMessageFunction(wrap_message) else : self._wrap_message_func = None #end if self._dbobj.message_function = self._wrap_message_func return \ self #end set_message #end ObjectPathVTable class _DummyError : # like an Error, but is never set and so will never raise. @property def is_set(self) : return \ False #end is_set def raise_if_set(self) : pass #end raise_if_set #end _DummyError def _get_error(error) : # Common routine which processes an optional user-supplied Error # argument, and returns 2 Error-like objects: the first a real # Error object to be passed to the libdbus call, the second is # either the same Error object or a separate _DummyError object # on which to call raise_if_set() afterwards. The procedure for # using this is # # error, my_error = _get_error(error) # ... call libdbus routine, passing error._dbobj ... # my_error.raise_if_set() # # If the user passes None for error, then an internal Error object # is created, and returned as both results. That way, if it is # filled in by the libdbus call, calling raise_if_set() will # automatically raise the exception. # But if the user passed their own Error object, then it is # returned as the first result, and a _DummyError as the second # result. This means the raise_if_set() call becomes a noop, and # it is up to the caller to check if their Error object was filled # in or not. if error != None and not isinstance(error, Error) : raise TypeError("error must be an Error") #end if if error != None : my_error = _DummyError() else : my_error = Error() error = my_error #end if return \ error, my_error #end _get_error def _get_timeout(timeout) : # accepts a timeout in float seconds and converts it to integer milliseconds # as expected by libdbus. Special-cases DBUS.TIMEOUT_INFINITE and DBUS.TIMEOUT_USE_DEFAULT, # allowing these to be passed through unchanged. if not isinstance(timeout, int) or timeout not in (DBUS.TIMEOUT_INFINITE, DBUS.TIMEOUT_USE_DEFAULT) : timeout = round(timeout * 1000) #end if return \ timeout #end _get_timeout def _loop_attach(self, loop, dispatch) : # attaches a Server or Connection object to a given asyncio event loop. # If loop is None, then the default asyncio loop is used. The actual loop # value is also stored as the loop attribute of the object. if loop == None : loop = asyncio.get_event_loop() #end if watches = [] # do I need to keep track of Watch objects? timeouts = [] def call_dispatch() : status = dispatch() if status == DBUS.DISPATCH_NEED_MEMORY : raise DBusError(DBUS.ERROR_NO_MEMORY, "not enough memory for connection dispatch") #end if if status == DBUS.DISPATCH_DATA_REMAINS : loop.call_soon(call_dispatch) #end if #end call_dispatch def add_remove_watch(watch, add) : def handle_watch_event(flags) : # seems I need to remove the watch and add it again to # avoid an endless stream of notifications that cause # excessive CPU usage -- asyncio bug? add_remove_watch(watch, False) watch.handle(flags) if watch.enabled : add_remove_watch(watch, True) #end if if dispatch != None : call_dispatch() #end if #end handle_watch_event #end add_remove_watch if DBUS.WATCH_READABLE & watch.flags != 0 : if add : loop.add_reader(watch, handle_watch_event, DBUS.WATCH_READABLE) else : loop.remove_reader(watch) #end if #end if if DBUS.WATCH_WRITABLE & watch.flags != 0 : if add : loop.add_writer(watch, handle_watch_event, DBUS.WATCH_WRITABLE) else : loop.remove_writer(watch) #end if #end if #end add_remove_watch def handle_add_watch(watch, data) : if watch not in watches : watches.append(watch) add_remove_watch(watch, True) #end if return \ True #end handle_add_watch def handle_watch_toggled(watch, data) : add_remove_watch(watch, watch.enabled) #end handle_watch_toggled def handle_remove_watch(watch, data) : try : pos = watches.index(watch) except ValueError : pos = None #end try if pos != None : watches[pos : pos + 1] = [] add_remove_watch(watch, False) #end if #end handle_remove_watch def handle_timeout(timeout) : if timeout["due"] != None and timeout["due"] <= loop.time() and timeout["timeout"].enabled : timeout["timeout"].handle() #end if #end handle_timeout def handle_add_timeout(timeout, data) : if not any(timeout == t["timeout"] for t in timeouts) : entry = \ { "timeout" : timeout, "due" : (lambda : None, lambda : loop.time() + timeout.interval)[timeout.enabled](), } timeouts.append(entry) if timeout.enabled : loop.call_later(timeout.interval, handle_timeout, entry) #end if #end if return \ True #end handle_add_timeout def handle_timeout_toggled(timeout, data) : # not sure what to do if a Timeout gets toggled from enabled to disabled # and then to enabled again; effectively I update the due time from # the time of re-enabling. search = iter(timeouts) while True : entry = next(search, None) if entry == None : break #end if if entry["timeout"] == timeout : if timeout.enabled : entry["due"] = loop.time() + timeout.enterval loop.call_later(timeout.interval, handle_timeout, entry) else : entry["due"] = None #end if break #end if #end while #end handle_timeout_toggled def handle_remove_timeout(timeout, data) : new_timeouts = [] for entry in timeouts : if entry["timeout"] == timeout : entry["due"] = None # in case already queued, avoid segfault in handle_timeout else : new_timeouts.append(entry) #end if #end for timeouts[:] = new_timeouts #end handle_remove_timeout #begin _loop_attach self.set_watch_functions \ ( add_function = handle_add_watch, remove_function = handle_remove_watch, toggled_function = handle_watch_toggled, data = None ) self.set_timeout_functions \ ( add_function = handle_add_timeout, remove_function = handle_remove_timeout, toggled_function = handle_timeout_toggled, data = None ) self.loop = loop self = None # avoid circularity #end _loop_attach class _MatchActionEntry : __slots__ = ("rule", "actions") class _Action : __slots__ = ("func", "user_data") def __init__(self, func, user_data) : self.func = func self.user_data = user_data #end __init__ def __eq__(a, b) : # needed to allow equality comparison of set entries return \ ( a.func == b.func and data_key(a.user_data) == data_key(b.user_data) ) #end __eq__ def __hash__(self) : return \ hash((self.func, data_key(self.user_data))) #end __hash__ #end _Action def __init__(self, rule) : self.rule = rule self.actions = set() #end __init__ #end _MatchActionEntry @enum.unique class STOP_ON(enum.Enum) : "set of conditions on which to raise StopAsyncIteration:\n" \ "\n" \ " TIMEOUT - timeout has elapsed\n" \ " CLOSED - server/connection has closed.\n" \ "\n" \ "Otherwise None will be returned on timeout, and the usual BrokenPipeError" \ " exception will be raised when the connection is closed." TIMEOUT = 1 CLOSED = 2 #end STOP_ON class Connection(TaskKeeper) : "wrapper around a DBusConnection object. Do not instantiate directly; use the open" \ " or bus_get methods." # __slots__ = \ ( "_dbobj", "_filters", "_match_actions", "_receive_queue", "_receive_queue_enabled", "_awaiting_receive", "_user_data", # need to keep references to ctypes-wrapped functions # so they don't disappear prematurely: "_object_paths", "_add_watch_function", "_remove_watch_function", "_toggled_watch_function", "_free_watch_data", "_add_timeout_function", "_remove_timeout_function", "_toggled_timeout_function", "_free_timeout_data", "_wakeup_main", "_free_wakeup_main_data", "_dispatch_status", "_free_dispatch_status_data", "_allow_unix_user", "_free_unix_user_data", ) # to forestall typos _instances = WeakValueDictionary() _shared_connections = [None, None] def __new__(celf, _dbobj) : self = celf._instances.get(_dbobj) if self == None : self = super().__new__(celf) super()._init(self) self._dbobj = _dbobj self._user_data = {} self._filters = {} self._match_actions = {} self._receive_queue = None self._receive_queue_enabled = set() self._awaiting_receive = [] self._object_paths = {} celf._instances[_dbobj] = self else : dbus.dbus_connection_unref(self._dbobj) # lose extra reference created by caller #end if return \ self #end __new__ def __del__(self) : if self._dbobj != None : if self.loop != None : # remove via direct low-level libdbus calls dbus.dbus_connection_set_watch_functions(self._dbobj, None, None, None, None, None) dbus.dbus_connection_set_timeout_functions(self._dbobj, None, None, None, None, None) self.loop = None #end if # Any entries still in super(TaskKeeper, self)._cur_tasks will be lost # at this point. I leave it to asyncio to report them as destroyed # while still pending, and the caller to notice this as a program bug. dbus.dbus_connection_unref(self._dbobj) self._dbobj = None #end if #end __del__ @classmethod def open(celf, address, private, error = None) : "opens a Connection to a specified address, separate from the" \ " system or session buses." error, my_error = _get_error(error) result = (dbus.dbus_connection_open, dbus.dbus_connection_open_private)[private](address.encode(), error._dbobj) my_error.raise_if_set() if result != None : result = celf(result) #end if return \ result #end open @classmethod async def open_async(celf, address, private, error = None, loop = None, timeout = DBUS.TIMEOUT_INFINITE) : "opens a Connection to a specified address, separate from the" \ " system or session buses." # There is no nonblocking version of dbus_connection_open/dbus_connection_open_private, # so I invoke it in a separate thread. if loop == None : loop = asyncio.get_event_loop() #end if error, my_error = _get_error(error) if timeout == DBUS.TIMEOUT_USE_DEFAULT : timeout = DBUSX.DEFAULT_TIMEOUT elif timeout == DBUS.TIMEOUT_INFINITE : timeout = None #end if try : result = await call_async \ ( func = (dbus.dbus_connection_open, dbus.dbus_connection_open_private)[private], funcargs = (address.encode(), error._dbobj), timeout = timeout, abort = dbus.dbus_connection_unref, loop = loop ) except TimeoutError : result = None error.set(DBUS.ERROR_TIMEOUT, "connection did not open in time") #end try my_error.raise_if_set() if result != None : result = celf(result) result.attach_asyncio(loop) #end if return \ result #end open_async def _flush_awaiting_receive(self) : if self._receive_queue != None : while len(self._awaiting_receive) != 0 : waiting = self._awaiting_receive.pop(0) waiting.set_exception(BrokenPipeError("async receives have been disabled")) #end while #end if #end _flush_awaiting_receive def close(self) : self._flush_awaiting_receive() dbus.dbus_connection_close(self._dbobj) #end close @property def is_connected(self) : return \ dbus.dbus_connection_get_is_connected(self._dbobj) != 0 #end is_connected @property def is_authenticated(self) : return \ dbus.dbus_connection_get_is_authenticated(self._dbobj) != 0 #end is_authenticated @property def is_anonymous(self) : return \ dbus.dbus_connection_get_is_anonymous(self._dbobj) != 0 #end is_anonymous @property def server_id(self) : "asks the server at the other end for its unique id." c_result = dbus.dbus_connection_get_server_id(self._dbobj) result = ct.cast(c_result, ct.c_char_p).value.decode() dbus.dbus_free(c_result) return \ result #end server_id def can_send_type(self, type_code) : "can this Connection send values of the specified TYPE_XXX code." \ " Mainly useful for checking if we can send TYPE_UNIX_FD values." return \ dbus.dbus_connection_can_send_type(self._dbobj, type_code) != 0 #end can_send_type def set_exit_on_disconnect(self, exit_on_disconnect) : dbus.dbus_connection_set_exit_on_disconnect(self._dbobj, exit_on_disconnect) #end set_exit_on_disconnect def preallocate_send(self) : result = dbus.dbus_connection_preallocate_send(self._dbobj) if result == None : raise CallFailed("dbus_connection_preallocate_send") #end if return \ PreallocatedSend(result, self) #end preallocate_send def send_preallocated(self, preallocated, message) : if not isinstance(preallocated, PreallocatedSend) or not isinstance(message, Message) : raise TypeError("preallocated must be a PreallocatedSend and message must be a Message") #end if assert not preallocated._sent, "preallocated has already been sent" serial = ct.c_uint() dbus.dbus_connection_send_preallocated(self._dbobj, preallocated._dbobj, message._dbobj, ct.byref(serial)) preallocated._sent = True return \ serial.value #end send_preallocated def send(self, message) : "puts a message in the outgoing queue." if not isinstance(message, Message) : raise TypeError("message must be a Message") #end if serial = ct.c_uint() if not dbus.dbus_connection_send(self._dbobj, message._dbobj, ct.byref(serial)) : raise CallFailed("dbus_connection_send") #end if return \ serial.value #end send def send_with_reply(self, message, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "puts a message in the outgoing queue and returns a PendingCall" \ " that you can use to obtain the reply." if not isinstance(message, Message) : raise TypeError("message must be a Message") #end if pending_call = ct.c_void_p() if not dbus.dbus_connection_send_with_reply(self._dbobj, message._dbobj, ct.byref(pending_call), _get_timeout(timeout)) : raise CallFailed("dbus_connection_send_with_reply") #end if if pending_call.value != None : result = PendingCall(pending_call.value, self) else : result = None #end if return \ result #end send_with_reply def send_with_reply_and_block(self, message, timeout = DBUS.TIMEOUT_USE_DEFAULT, error = None) : "sends a message, blocks the thread until the reply is available, and returns it." if not isinstance(message, Message) : raise TypeError("message must be a Message") #end if error, my_error = _get_error(error) reply = dbus.dbus_connection_send_with_reply_and_block(self._dbobj, message._dbobj, _get_timeout(timeout), error._dbobj) my_error.raise_if_set() if reply != None : result = Message(reply) else : result = None #end if return \ result #end send_with_reply_and_block async def send_await_reply(self, message, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "queues a message, suspends the coroutine (letting the event loop do" \ " other things) until the reply is available, and returns it." if not isinstance(message, Message) : raise TypeError("message must be a Message") #end if assert self.loop != None, "no event loop to attach coroutine to" pending_call = ct.c_void_p() if not dbus.dbus_connection_send_with_reply(self._dbobj, message._dbobj, ct.byref(pending_call), _get_timeout(timeout)) : raise CallFailed("dbus_connection_send_with_reply") #end if if pending_call.value != None : pending = PendingCall(pending_call.value, self) else : pending = None #end if reply = None # to begin with if pending != None : reply = await pending.await_reply() #end if return \ reply #end send_await_reply def flush(self) : "makes sure all queued messages have been sent, blocking" \ " the thread until this is done." dbus.dbus_connection_flush(self._dbobj) #end flush def read_write_dispatch(self, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "dispatches the first available message, if any. Otherwise blocks the" \ " thread until it can read or write, and does so before returning. Returns" \ " True as long as the Connection remains connected." return \ dbus.dbus_connection_read_write_dispatch(self._dbobj, _get_timeout(timeout)) != 0 #end read_write_dispatch def read_write(self, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "blocks the thread until something can be read or written on the Connection," \ " and does so, returning True. If the Connection has been disconnected," \ " immediately returns False." return \ dbus.dbus_connection_read_write(self._dbobj, _get_timeout(timeout)) != 0 #end read_write def borrow_message(self) : "tries to peek at the next available message waiting to be read, returning" \ " None if these isn’t one. Call the Message’s return_borrowed() method" \ " to return it to the queue, or steal_borrowed() to confirm that you have" \ " read the message." msg = dbus.dbus_connection_borrow_message(self._dbobj) if msg != None : msg = Message(msg) msg._conn = self msg._borrowed = True #end if return \ msg #end borrow_message # returning/stealing borrowed messages done with # Message.return_borrowed and Message.steal_borrowed def pop_message(self) : "returns the next available incoming Message, if any, otherwise returns None." \ " Note this bypasses all message filtering/dispatching on this Connection." message = dbus.dbus_connection_pop_message(self._dbobj) if message != None : message = Message(message) #end if return \ message #end pop_message @property def dispatch_status(self) : "checks the state of the incoming message queue; returns a DISPATCH_XXX code." return \ dbus.dbus_connection_get_dispatch_status(self._dbobj) #end dispatch_status def dispatch(self) : "processes any available data, adding messages into the incoming" \ " queue as appropriate. returns a DISPATCH_XXX code." return \ dbus.dbus_connection_dispatch(self._dbobj) #end dispatch def set_watch_functions(self, add_function, remove_function, toggled_function, data, free_data = None) : "sets the callbacks for libdbus to use to notify you of Watch objects it wants" \ " you to manage." def wrap_add_function(c_watch, _data) : return \ add_function(Watch(c_watch), data) #end wrap_add_function def wrap_remove_function(c_watch, _data) : return \ remove_function(Watch(c_watch), data) #end wrap_remove_function def wrap_toggled_function(c_watch, _data) : return \ toggled_function(Watch(c_watch), data) #end wrap_toggled_function def wrap_free_data(_data) : free_data(data) #end wrap_free_data #begin set_watch_functions self._add_watch_function = DBUS.AddWatchFunction(wrap_add_function) self._remove_watch_function = DBUS.RemoveWatchFunction(wrap_remove_function) if toggled_function != None : self._toggled_watch_function = DBUS.WatchToggledFunction(wrap_toggled_function) else : self._toggled_watch_function = None #end if if free_data != None : self._free_watch_data = DBUS.FreeFunction(wrap_free_data) else : self._free_watch_data = None #end if if not dbus.dbus_connection_set_watch_functions(self._dbobj, self._add_watch_function, self._remove_watch_function, self._toggled_watch_function, None, self._free_watch_data) : raise CallFailed("dbus_connection_set_watch_functions") #end if #end set_watch_functions def set_timeout_functions(self, add_function, remove_function, toggled_function, data, free_data = None) : "sets the callbacks for libdbus to use to notify you of Timeout objects it wants" \ " you to manage." def wrap_add_function(c_timeout, _data) : return \ add_function(Timeout(c_timeout), data) #end wrap_add_function def wrap_remove_function(c_timeout, _data) : return \ remove_function(Timeout(c_timeout), data) #end wrap_remove_function def wrap_toggled_function(c_timeout, _data) : return \ toggled_function(Timeout(c_timeout), data) #end wrap_toggled_function def wrap_free_data(_data) : free_data(data) #end wrap_free_data #begin set_timeout_functions self._add_timeout_function = DBUS.AddTimeoutFunction(wrap_add_function) self._remove_timeout_function = DBUS.RemoveTimeoutFunction(wrap_remove_function) if toggled_function != None : self._toggled_timeout_function = DBUS.TimeoutToggledFunction(wrap_toggled_function) else : self._toggled_timeout_function = None #end if if free_data != None : self._free_timeout_data = DBUS.FreeFunction(wrap_free_data) else : self._free_timeout_data = None #end if if not dbus.dbus_connection_set_timeout_functions(self._dbobj, self._add_timeout_function, self._remove_timeout_function, self._toggled_timeout_function, None, self._free_timeout_data) : raise CallFailed("dbus_connection_set_timeout_functions") #end if #end set_timeout_functions def set_wakeup_main_function(self, wakeup_main, data, free_data = None) : "sets the callback to use for libdbus to notify you that something has" \ " happened requiring processing on the Connection." def wrap_wakeup_main(_data) : wakeup_main(data) #end wrap_wakeup_main def wrap_free_data(_data) : free_data(data) #end wrap_free_data #begin set_wakeup_main_function if wakeup_main != None : self._wakeup_main = DBUS.WakeupMainFunction(wrap_wakeup_main) else : self._wakeup_main = None #end if if free_data != None : self._free_wakeup_main_data = DBUS.FreeFunction(wrap_free_data) else : self._free_wakeup_main_data = None #end if dbus.dbus_connection_set_wakeup_main_function(self._dbobj, self._wakeup_main, None, self._free_wakeup_main_data) #end set_wakeup_main_function def set_dispatch_status_function(self, function, data, free_data = None) : "sets the callback to use for libdbus to notify you of a change in the" \ " dispatch status of the Connection." w_self = weak_ref(self) def wrap_dispatch_status(_conn, status, _data) : function(_wderef(w_self, "connection"), status, data) #end wrap_dispatch_status def wrap_free_data(_data) : free_data(data) #end wrap_free_data #begin set_dispatch_status_function self._dispatch_status = DBUS.DispatchStatusFunction(wrap_dispatch_status) if free_data != None : self._free_wakeup_main_data = DBUS.FreeFunction(wrap_free_data) else : self._free_wakeup_main_data = None #end if dbus.dbus_connection_set_dispatch_status_function(self._dbobj, self._dispatch_status, None, self._free_wakeup_main_data) #end set_dispatch_status_function @property def unix_fd(self) : c_fd = ct.c_int() if dbus.dbus_connection_get_unix_fd(self._dbobj, ct.byref(c_fd)) : result = c_fd.value else : result = None #end if return \ result #end unix_fd def fileno(self) : "for use with Python’s “select” functions." return \ self.unix_fd #end fileno @property def socket(self) : c_fd = ct.c_int() if dbus.dbus_connection_get_socket(self._dbobj, ct.byref(c_fd)) : result = c_fd.value else : result = None #end if return \ result #end socket @property def unix_process_id(self) : c_pid = ct.c_ulong() if dbus.dbus_connection_get_unix_process_id(self._dbobj, ct.byref(c_pid)) : result = c_pid.value else : result = None #end if return \ result #end unix_process_id @property def unix_user(self) : c_uid = ct.c_ulong() if dbus.dbus_connection_get_unix_user(self._dbobj, ct.byref(c_uid)) : result = c_uid.value else : result = None #end if return \ result #end unix_user # TODO: get_adt def set_unix_user_function(self, allow_unix_user, data, free_data = None) : w_self = weak_ref(self) def wrap_allow_unix_user(c_conn, uid, c_data) : return \ allow_unix_user(_wderef(w_self, "connection"), uid, data) #end wrap_allow_unix_user def wrap_free_data(_data) : free_data(data) #end wrap_free_data #begin set_unix_user_function if allow_unix_user != None : self._allow_unix_user = DBUS.AllowUnixUserFunction(wrap_allow_unix_user) else : self._allow_unix_user = None #end if if free_data != None : self._free_unix_user_data = DBUS.FreeFunction(wrap_free_data) else : self._free_unix_user_data = None #end if dbus.dbus_connection_set_unix_user_function(self._dbobj, self._allow_unix_user, None, self._free_unix_user_data) #end set_unix_user_function def set_allow_anonymous(self, allow) : dbus.dbus_connection_set_allow_anonymous(self._dbobj, allow) #end set_allow_anonymous def set_route_peer_messages(self, enable) : dbus.dbus_connection_set_route_peer_messages(self._dbobj, enable) #end set_route_peer_messages def add_filter(self, function, user_data, free_data = None) : "adds a filter callback that gets to look at all incoming messages" \ " before they get to the dispatch system. The same function can be added" \ " multiple times as long as the user_data is different." w_self = weak_ref(self) def wrap_function(c_conn, c_message, _data) : self = _wderef(w_self, "connection") message = Message(dbus.dbus_message_ref(c_message)) result = function(self, message, user_data) if asyncio.iscoroutine(result) : self.create_task(result) result = DBUS.HANDLER_RESULT_HANDLED #end if return \ result #end wrap_function def wrap_free_data(_data) : free_data(user_data) #end wrap_free_data #begin add_filter filter_key = (function, data_key(user_data)) filter_value = \ { "function" : DBUS.HandleMessageFunction(wrap_function), "free_data" : (lambda : None, lambda : DBUS.FreeFunction(wrap_free_data))[free_data != None](), } # pass user_data id because libdbus identifies filter entry by both function address and user data address if not dbus.dbus_connection_add_filter(self._dbobj, filter_value["function"], filter_key[1], filter_value["free_data"]) : raise CallFailed("dbus_connection_add_filter") #end if self._filters[filter_key] = filter_value # need to ensure wrapped functions don’t disappear prematurely #end add_filter def remove_filter(self, function, user_data) : "removes a message filter added by add_filter. The filter is identified" \ " by both the function object and the user_data that was passed." filter_key = (function, data_key(user_data)) if filter_key not in self._filters : raise KeyError("removing nonexistent Connection filter") #end if filter_value = self._filters[filter_key] # pass user_data id because libdbus identifies filter entry by both function address and user data address dbus.dbus_connection_remove_filter(self._dbobj, filter_value["function"], filter_key[1]) del self._filters[filter_key] #end remove_filter def register_object_path(self, path, vtable, user_data, error = None) : "registers an ObjectPathVTable as a dispatch handler for a specified" \ " path within your object hierarchy." if not isinstance(vtable, ObjectPathVTable) : raise TypeError("vtable must be an ObjectPathVTable") #end if self._object_paths[path] = {"vtable" : vtable, "user_data" : user_data} # ensure it doesn’t disappear prematurely error, my_error = _get_error(error) if user_data != None : c_user_data = id(user_data) self._user_data[c_user_data] = user_data else : c_user_data = None #end if dbus.dbus_connection_try_register_object_path(self._dbobj, path.encode(), vtable._dbobj, c_user_data, error._dbobj) my_error.raise_if_set() #end register_object_path def register_fallback(self, path, vtable, user_data, error = None) : "registers an ObjectPathVTable as a dispatch handler for an entire specified" \ " subtree within your object hierarchy." if not isinstance(vtable, ObjectPathVTable) : raise TypeError("vtable must be an ObjectPathVTable") #end if self._object_paths[path] = {"vtable" : vtable, "user_data" : user_data} # ensure it doesn’t disappear prematurely error, my_error = _get_error(error) if user_data != None : c_user_data = id(user_data) self._user_data[c_user_data] = user_data else : c_user_data = None #end if dbus.dbus_connection_try_register_fallback(self._dbobj, path.encode(), vtable._dbobj, c_user_data, error._dbobj) my_error.raise_if_set() #end register_fallback def unregister_object_path(self, path) : "removes a previously-registered ObjectPathVTable handler at a specified" \ " point (single object or entire subtree) within your object hierarchy." if path not in self._object_paths : raise KeyError("unregistering unregistered path") #end if if not dbus.dbus_connection_unregister_object_path(self._dbobj, path.encode()) : raise CallFailed("dbus_connection_unregister_object_path") #end if user_data = self._object_paths[path]["user_data"] c_user_data = id(user_data) nr_remaining_refs = sum(int(self._object_paths[p]["user_data"] == user_data) for p in self._object_paths if p != path) if nr_remaining_refs == 0 : try : del self._user_data[c_user_data] except KeyError : pass #end try #end if del self._object_paths[path] #end unregister_object_path def get_object_path_data(self, path) : "returns the user_data you passed when previously registering an ObjectPathVTable" \ " that covers this path in your object hierarchy, or None if no suitable match" \ " could be found." c_data_p = ct.c_void_p() if not dbus.dbus_connection_get_object_path_data(self._dbobj, path.encode(), ct.byref(c_data_p)) : raise CallFailed("dbus_connection_get_object_path_data") #end if return \ self._user_data.get(c_data_p.value) #end get_object_path_data def list_registered(self, parent_path) : "lists all the object paths for which you have ObjectPathVTable handlers registered." child_entries = ct.POINTER(ct.c_char_p)() if not dbus.dbus_connection_list_registered(self._dbobj, parent_path.encode(), ct.byref(child_entries)) : raise CallFailed("dbus_connection_list_registered") #end if result = [] i = 0 while True : entry = child_entries[i] if entry == None : break result.append(entry.decode()) i += 1 #end while dbus.dbus_free_string_array(child_entries) return \ result #end list_registered @staticmethod def _queue_received_message(self, message, _) : # message filter which queues messages as appropriate for receive_message_async. # Must be static so same function object can be passed to all add_filter/remove_filter # calls. queueit = message.type in self._receive_queue_enabled if queueit : self._receive_queue.append(message) while len(self._awaiting_receive) != 0 : # wake them all up, because I don’t know what message types # each might be waiting for waiting = self._awaiting_receive.pop(0) waiting.set_result(True) # result actually ignored #end while #end if return \ (DBUS.HANDLER_RESULT_NOT_YET_HANDLED, DBUS.HANDLER_RESULT_HANDLED)[queueit] #end _queue_received_message def enable_receive_message(self, queue_types) : "enables/disables message types for reception via receive_message_async." \ " queue_types is a set or sequence of DBUS.MESSAGE_TYPE_XXX values for" \ " the types of messages to be put into the receive queue, or None to" \ " disable all message types; this replaces queue_types passed to" \ " any prior enable_receive_message_async call on this Connection." assert self.loop != None, "no event loop to attach coroutines to" enable = queue_types != None and len(queue_types) != 0 if ( enable and not all ( m in ( DBUS.MESSAGE_TYPE_METHOD_CALL, DBUS.MESSAGE_TYPE_METHOD_RETURN, DBUS.MESSAGE_TYPE_ERROR, DBUS.MESSAGE_TYPE_SIGNAL, ) for m in queue_types ) ) : raise TypeError("invalid message type in queue_types: %s" % repr(queue_types)) #end if if enable : if self._receive_queue == None : self.add_filter(self._queue_received_message, None) self._receive_queue = [] #end if self._receive_queue_enabled.clear() self._receive_queue_enabled.update(queue_types) else : if self._receive_queue != None : self._flush_awaiting_receive() self.remove_filter(self._queue_received_message, None) self._receive_queue = None #end if #end if #end enable_receive_message async def receive_message_async(self, want_types = None, timeout = DBUS.TIMEOUT_INFINITE) : "receives the first available queued message of an appropriate type, blocking" \ " if none is available and timeout is nonzero. Returns None if the timeout" \ " elapses without a suitable message becoming available. want_types can be" \ " None to receive any of the previously-enabled message types, or a set or" \ " sequence of DBUS.MESSAGE_TYPE_XXX values to look only for messages of those" \ " types.\n" \ "\n" \ "You must have previously made a call to enable_receive_message to enable" \ " queueing of one or more message types on this Connection." assert self._receive_queue != None, "receive_message_async not enabled" # should I check if want_types contains anything not in self._receive_queue_enabled? if timeout == DBUS.TIMEOUT_USE_DEFAULT : timeout = DBUSX.DEFAULT_TIMEOUT #end if if timeout != DBUS.TIMEOUT_INFINITE : finish_time = self.loop.time() + timeout else : finish_time = None #end if result = ... # indicates “watch this space” while True : # keep rescanning queue until got something or timeout index = 0 # start next queue scan while True : if index == len(self._receive_queue) : # nothing currently suitable on queue if ( timeout == 0 or finish_time != None and self.loop.time() > finish_time ) : # waited too long, give up result = None break #end if if not self.is_connected : raise BrokenPipeError("Connection has been disconnected") #end if # wait and see if something turns up awaiting = self.loop.create_future() self._awaiting_receive.append(awaiting) if finish_time != None : wait_timeout = finish_time - self.loop.time() else : wait_timeout = None #end if await asyncio.wait \ ( (awaiting,), loop = self.loop, timeout = wait_timeout ) # ignore done & pending results because they # don’t match up with future I’m waiting for try : self._awaiting_receive.remove(awaiting) except ValueError : pass #end try awaiting.cancel() # just to avoid “Future exception was never retrieved” message break # start new queue scan #end if # check next queue item msg = self._receive_queue[index] if want_types == None or msg.type in want_types : # caller wants this one result = msg self._receive_queue.pop(index) # remove msg from queue break #end if index += 1 #end while if result != ... : # either got something or given up break #end while return \ result #end receive_message_async def iter_messages_async(self, want_types = None, stop_on = None, timeout = DBUS.TIMEOUT_INFINITE) : "wrapper around receive_message_async() to allow use with an async-for statement." \ " Lets you write\n" \ "\n" \ " async for message in «conn».iter_messages_async(«want_types», «stop_on», «timeout») :" \ " «process message»\n" \ " #end for\n" \ "\n" \ "to receive and process messages in a loop. stop_on is an optional set of" \ " STOP_ON.xxx values indicating the conditions under which the iterator will" \ " raise StopAsyncIteration to terminate the loop." if stop_on == None : stop_on = frozenset() elif ( not isinstance(stop_on, (set, frozenset)) or not all(isinstance(elt, STOP_ON) for elt in stop_on) ) : raise TypeError("stop_on must be None or set of STOP_ON") #end if assert self._receive_queue != None, "receive_message_async not enabled" return \ _MsgAiter(self, want_types, stop_on, timeout) #end iter_messages_async # TODO: allocate/free data slot -- staticmethods # TODO: get/set data def set_change_sigpipe(self, will_modify_sigpipe) : dbus.dbus_connection_set_change_sigpipe(self._dbobj, will_modify_sigpipe) #end set_change_sigpipe @property def max_message_size(self) : return \ dbus.dbus_connection_get_max_message_size(self._dbobj) #end max_message_size @max_message_size.setter def max_message_size(self, size) : dbus.dbus_connection_set_max_message_size(self._dbobj, size) #end max_message_size @property def max_received_size(self) : return \ dbus.dbus_connection_get_max_received_size(self._dbobj) #end max_received_size @max_received_size.setter def max_received_size(self, size) : dbus.dbus_connection_set_max_received_size(self._dbobj, size) #end max_received_size @property def max_message_unix_fds(self) : return \ dbus.dbus_connection_get_max_message_unix_fds(self._dbobj) #end max_message_unix_fds @max_message_unix_fds.setter def max_message_unix_fds(self, size) : dbus.dbus_connection_set_max_message_unix_fds(self._dbobj, size) #end max_message_unix_fds @property def max_received_unix_fds(self) : return \ dbus.dbus_connection_get_max_received_unix_fds(self._dbobj) #end max_received_unix_fds @max_received_unix_fds.setter def max_received_unix_fds(self, size) : dbus.dbus_connection_set_max_received_unix_fds(self._dbobj, size) #end max_received_unix_fds @property def outgoing_size(self) : return \ dbus.dbus_connection_get_outgoing_size(self._dbobj) #end outgoing_size @property def outgoing_unix_fds(self) : return \ dbus.dbus_connection_get_outgoing_unix_fds(self._dbobj) #end outgoing_unix_fds @property def has_messages_to_send(self) : return \ dbus.dbus_connection_has_messages_to_send(self._dbobj) != 0 #end has_messages_to_send # message bus APIs # @classmethod def bus_get(celf, type, private, error = None) : "returns a Connection to one of the predefined D-Bus buses; type is a BUS_xxx value." error, my_error = _get_error(error) result = (dbus.dbus_bus_get, dbus.dbus_bus_get_private)[private](type, error._dbobj) my_error.raise_if_set() if result != None : result = celf(result) #end if return \ result #end bus_get @classmethod async def bus_get_async(celf, type, private, error = None, loop = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : if loop == None : loop = asyncio.get_event_loop() #end if assert type in (DBUS.BUS_SESSION, DBUS.BUS_SYSTEM, DBUS.BUS_STARTER), \ "bus type must be BUS_SESSION, BUS_SYSTEM or BUS_STARTER" if type == DBUS.BUS_STARTER : starter_type = os.environ.get(DBUSX.STARTER_BUS_ADDRESS_TYPE) is_system_bus = starter_type != None and starter_type == DBUSX.BUS_TYPE_SYSTEM addr = os.environ.get(DBUSX.STARTER_BUS_ADDRESS_VAR) else : is_system_bus = type == DBUS.BUS_SYSTEM addr = os.environ.get \ ( (DBUSX.SESSION_BUS_ADDRESS_VAR, DBUSX.SYSTEM_BUS_ADDRESS_VAR)[is_system_bus] ) #end if if not private and celf._shared_connections[is_system_bus] != None : result = celf._shared_connections[is_system_bus] else : if addr == None : addr = (DBUSX.SESSION_BUS_ADDRESS, DBUSX.SYSTEM_BUS_ADDRESS)[is_system_bus] #end if try : result = await celf.open_async(addr, private, error, loop, timeout) if error != None and error.is_set : raise _Abort #end if await result.bus_register_async(error = error, timeout = timeout) if error != None and error.is_set : raise _Abort #end if if not private : celf._shared_connections[is_system_bus] = result #end if except _Abort : result = None #end try #end if return \ result #end bus_get_async def bus_register(self, error = None) : "Only to be used if you created the Connection with open() instead of bus_get();" \ " sends a “Hello” message to the D-Bus daemon to get a unique name assigned." \ " Can only be called once." error, my_error = _get_error(error) dbus.dbus_bus_register(self._dbobj, error._dbobj) my_error.raise_if_set() #end bus_register async def bus_register_async(self, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "Only to be used if you created the Connection with open() instead of bus_get();" \ " sends a “Hello” message to the D-Bus daemon to get a unique name assigned." \ " Can only be called once." assert self.loop != None, "no event loop to attach coroutine to" assert self.bus_unique_name == None, "bus already registered" message = Message.new_method_call \ ( destination = DBUS.SERVICE_DBUS, path = DBUS.PATH_DBUS, iface = DBUS.INTERFACE_DBUS, method = "Hello" ) reply = await self.send_await_reply(message, timeout = timeout) if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR : reply.set_error(error) else : self.bus_unique_name = reply.expect_return_objects("s")[0] #end if #end bus_register_async @property def bus_unique_name(self) : "returns None if the bus connection has not been registered. Note that the" \ " unique_name can only be set once." result = dbus.dbus_bus_get_unique_name(self._dbobj) if result != None : result = result.decode() #end if return \ result #end bus_unique_name @bus_unique_name.setter def bus_unique_name(self, unique_name) : if not dbus.dbus_bus_set_unique_name(self._dbobj, unique_name.encode()) : raise CallFailed("dbus_bus_set_unique_name") #end if #end bus_unique_name #+ # Calls to D-Bus Daemon #- @property def bus_id(self) : my_error = Error() c_result = dbus.dbus_bus_get_id(self._dbobj, my_error._dbobj) my_error.raise_if_set() result = ct.cast(c_result, ct.c_char_p).value.decode() dbus.dbus_free(c_result) return \ result #end bus_id @property async def bus_id_async(self) : message = Message.new_method_call \ ( destination = DBUS.SERVICE_DBUS, path = DBUS.PATH_DBUS, iface = DBUS.INTERFACE_DBUS, method = "GetId" ) reply = await self.send_await_reply(message) return \ reply.expect_return_objects("s")[0] #end bus_id_async def bus_get_unix_user(self, name, error = None) : error, my_error = _get_error(error) result = dbus.dbus_bus_get_unix_user(self._dbobj, name.encode(), error._dbobj) my_error.raise_if_set() return \ result #end bus_get_unix_user async def bus_get_unix_user_async(self, name, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : message = Message.new_method_call \ ( destination = DBUS.SERVICE_DBUS, path = DBUS.PATH_DBUS, iface = DBUS.INTERFACE_DBUS, method = "GetConnectionUnixUser" ) message.append_objects("s", name) reply = await self.send_await_reply(message, timeout = timeout) if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR : reply.set_error(error) result = None else : result = reply.expect_return_objects("u")[0] #end if return \ result #end bus_get_unix_user_async def bus_request_name(self, name, flags, error = None) : "asks the D-Bus daemon to register the specified bus name on your behalf," \ " blocking the thread until the reply is received. flags is a combination of" \ " NAME_FLAG_xxx bits. Result will be a REQUEST_NAME_REPLY_xxx value or -1 on error." error, my_error = _get_error(error) result = dbus.dbus_bus_request_name(self._dbobj, name.encode(), flags, error._dbobj) my_error.raise_if_set() return \ result #end bus_request_name async def bus_request_name_async(self, name, flags, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "asks the D-Bus daemon to register the specified bus name on your behalf. flags is" \ " a combination of NAME_FLAG_xxx bits. Result will be a REQUEST_NAME_REPLY_xxx value" \ " or None on error." message = Message.new_method_call \ ( destination = DBUS.SERVICE_DBUS, path = DBUS.PATH_DBUS, iface = DBUS.INTERFACE_DBUS, method = "RequestName" ) message.append_objects("su", name, flags) reply = await self.send_await_reply(message, timeout = timeout) if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR : reply.set_error(error) result = None else : result = reply.expect_return_objects("u")[0] #end if return \ result #end bus_request_name_async def bus_release_name(self, name, error = None) : "asks the D-Bus daemon to release your registration of the specified bus name," \ " blocking the thread until the reply is received." error, my_error = _get_error(error) result = dbus.dbus_bus_release_name(self._dbobj, name.encode(), error._dbobj) my_error.raise_if_set() return \ result #end bus_release_name async def bus_release_name_async(self, name, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "asks the D-Bus daemon to release your registration of the specified bus name." message = Message.new_method_call \ ( destination = DBUS.SERVICE_DBUS, path = DBUS.PATH_DBUS, iface = DBUS.INTERFACE_DBUS, method = "ReleaseName" ) message.append_objects("s", name) reply = await self.send_await_reply(message, timeout = timeout) if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR : reply.set_error(error) result = None else : result = reply.expect_return_objects("u")[0] #end if return \ result #end bus_release_name_async def bus_name_has_owner(self, name, error = None) : "asks the D-Bus daemon if anybody has claimed the specified bus name, blocking" \ " the thread until the reply is received." error, my_error = _get_error(error) result = dbus.dbus_bus_name_has_owner(self._dbobj, name.encode(), error._dbobj) my_error.raise_if_set() return \ result #end bus_name_has_owner async def bus_name_has_owner_async(self, name, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "asks the D-Bus daemon if anybody has claimed the specified bus name." message = Message.new_method_call \ ( destination = DBUS.SERVICE_DBUS, path = DBUS.PATH_DBUS, iface = DBUS.INTERFACE_DBUS, method = "NameHasOwner" ) message.append_objects("s", name) reply = await self.send_await_reply(message, timeout = timeout) if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR : reply.set_error(error) result = None else : result = reply.expect_return_objects("b")[0] #end if return \ result #end bus_name_has_owner_async def bus_start_service_by_name(self, name, flags = 0, error = None) : error, my_error = _get_error(error) outflags = ct.c_uint() success = dbus.dbus_bus_start_service_by_name(self._dbobj, name.encode(), flags, ct.byref(outflags), error._dbobj) my_error.raise_if_set() return \ outflags.value #end bus_start_service_by_name async def bus_start_service_by_name_async(self, name, flags = 0, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : message = Message.new_method_call \ ( destination = DBUS.SERVICE_DBUS, path = DBUS.PATH_DBUS, iface = DBUS.INTERFACE_DBUS, method = "StartServiceByName" ) message.append_objects("su", name, flags) reply = await self.send_await_reply(message, timeout = timeout) if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR : reply.set_error(error) result = None else : result = reply.expect_return_objects("u")[0] #end if return \ result #end bus_start_service_by_name def bus_add_match(self, rule, error = None) : "adds a match rule for messages you want to receive. By default you get all" \ " messages addressed to your bus name(s); but you can use this, for example," \ " to request notification of signals indicating useful events on the system." error, my_error = _get_error(error) dbus.dbus_bus_add_match(self._dbobj, format_rule(rule).encode(), error._dbobj) my_error.raise_if_set() #end bus_add_match async def bus_add_match_async(self, rule, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "adds a match rule for messages you want to receive. By default you get all" \ " messages addressed to your bus name(s); but you can use this, for example," \ " to request notification of signals indicating useful events on the system." message = Message.new_method_call \ ( destination = DBUS.SERVICE_DBUS, path = DBUS.PATH_DBUS, iface = DBUS.INTERFACE_DBUS, method = "AddMatch" ) message.append_objects("s", format_rule(rule)) reply = await self.send_await_reply(message, timeout = timeout) if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR : reply.set_error(error) else : reply.expect_return_objects("") #end if #end bus_add_match_async def bus_remove_match(self, rule, error = None) : "removes a previously-added match rule for messages you previously wanted" \ " to receive." error, my_error = _get_error(error) dbus.dbus_bus_remove_match(self._dbobj, format_rule(rule).encode(), error._dbobj) my_error.raise_if_set() #end bus_remove_match async def bus_remove_match_async(self, rule, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "removes a previously-added match rule for messages you previously wanted" \ " to receive." message = Message.new_method_call \ ( destination = DBUS.SERVICE_DBUS, path = DBUS.PATH_DBUS, iface = DBUS.INTERFACE_DBUS, method = "RemoveMatch" ) message.append_objects("s", format_rule(rule)) reply = await self.send_await_reply(message, timeout = timeout) if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR : reply.set_error(error) else : reply.expect_return_objects("") #end if #end bus_remove_match_async @staticmethod def _rule_action_match(self, message, _) : # installed as a message filter to invoke actions corresponding to rules # that the message matches. To avoid spurious method-not-handled errors # from eavesdropping on method calls not addressed to me, this routine # always returns a “handled” status. That means this same Connection # object should not be used for both eavesdropping and for normal # method calls. handled = False for entry in self._match_actions.values() : if matches_rule(message, entry.rule) : for action in entry.actions : result = action.func(self, message, action.user_data) if asyncio.iscoroutine(result) : self.create_task(result) #end if #end for handled = True # passed to at least one handler #end if #end for return \ (DBUS.HANDLER_RESULT_NOT_YET_HANDLED, DBUS.HANDLER_RESULT_HANDLED)[handled] #end _rule_action_match def bus_add_match_action(self, rule, func, user_data, error = None) : "adds a message filter that invokes func(conn, message, user_data)" \ " for each incoming message that matches the specified rule. Unlike" \ " the underlying add_filter and bus_add_match calls, this allows you" \ " to associate the action with the particular matching rule.\n" \ "\n" \ "Note that the message filter installed to process these rules always" \ " returns a DBUS.HANDLER_RESULT_HANDLED status; so either only use this" \ " to listen for signals, or do not use the same Connection object to" \ " handle normal method calls." rulekey = format_rule(rule) rule = unformat_rule(rule) if rulekey not in self._match_actions : self.bus_add_match(rulekey, error) # could fail here with bad rule if error == None or not error.is_set : if len(self._match_actions) == 0 : self.add_filter(self._rule_action_match, None) #end if self._match_actions[rulekey] = _MatchActionEntry(rule) #end if #end if if error == None or not error.is_set : self._match_actions[rulekey].actions.add(_MatchActionEntry._Action(func, user_data)) #end if #end bus_add_match_action def bus_remove_match_action(self, rule, func, user_data, error = None) : "removes a message filter previously installed with bus_add_match_action." rulekey = format_rule(rule) rule = unformat_rule(rule) self._match_actions[rulekey].actions.remove(_MatchActionEntry._Action(func, user_data)) if len(self._match_actions[rulekey].actions) == 0 : self.bus_remove_match(rulekey, error) # shouldn’t fail! del self._match_actions[rulekey] if len(self._match_actions) == 0 : self.remove_filter(self._rule_action_match, None) #end if #end if #end bus_remove_match_action async def bus_add_match_action_async(self, rule, func, user_data, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "adds a message filter that invokes func(conn, message, user_data)" \ " for each incoming message that matches the specified rule. Unlike" \ " the underlying add_filter and bus_add_match calls, this allows you" \ " to associate the action with the particular matching rule.\n" \ "\n" \ "Note that the message filter installed to process these rules always" \ " returns a DBUS.HANDLER_RESULT_HANDLED status; so either only use this" \ " to listen for signals, or do not use the same Connection object to" \ " handle normal method calls." rulekey = format_rule(rule) rule = unformat_rule(rule) if rulekey not in self._match_actions : await self.bus_add_match_async(rulekey, error, timeout) # could fail here with bad rule if error == None or not error.is_set : if len(self._match_actions) == 0 : self.add_filter(self._rule_action_match, None) #end if self._match_actions[rulekey] = _MatchActionEntry(rule) #end if #end if if error == None or not error.is_set : self._match_actions[rulekey].actions.add(_MatchActionEntry._Action(func, user_data)) #end if #end bus_add_match_action_async async def bus_remove_match_action_async(self, rule, func, user_data, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "removes a message filter previously installed with bus_add_match_action." rulekey = format_rule(rule) rule = unformat_rule(rule) self._match_actions[rulekey].actions.remove(_MatchActionEntry._Action(func, user_data)) if len(self._match_actions[rulekey].actions) == 0 : await self.bus_remove_match_async(rulekey, error, timeout) # shouldn’t fail! del self._match_actions[rulekey] if len(self._match_actions) == 0 : self.remove_filter(self._rule_action_match, None) #end if #end if #end bus_remove_match_action_async def become_monitor(self, rules) : "turns the connection into one that can only receive monitoring messages." message = Message.new_method_call \ ( destination = DBUS.SERVICE_DBUS, path = DBUS.PATH_DBUS, iface = DBUS.INTERFACE_MONITORING, method = "BecomeMonitor" ) message.append_objects("asu", (list(format_rule(rule) for rule in rules)), 0) self.send(message) #end become_monitor #+ # End calls to D-Bus Daemon #- def attach_asyncio(self, loop = None) : "attaches this Connection object to an asyncio event loop. If none is" \ " specified, the default event loop (as returned from asyncio.get_event_loop()" \ " is used." w_self = weak_ref(self) # to avoid a reference cycle def dispatch() : return \ _wderef(w_self, "connection").dispatch() #end dispatch #begin attach_asyncio assert self.loop == None, "already attached to an event loop" _loop_attach(self, loop, dispatch) #end attach_asyncio #end Connection class _MsgAiter : # internal class for use by Connection.iter_messages_async (above). def __init__(self, conn, want_types, stop_on, timeout) : self.conn = conn self.want_types = want_types self.stop_on = stop_on self.timeout = timeout #end __init__ def __aiter__(self) : # I’m my own iterator. return \ self #end __aiter__ async def __anext__(self) : stop_iter = False try : result = await self.conn.receive_message_async(self.want_types, self.timeout) if result == None and STOP_ON.TIMEOUT in self.stop_on : stop_iter = True #end if except BrokenPipeError : if STOP_ON.CLOSED not in self.stop_on : raise #end if stop_iter = True #end try if stop_iter : raise StopAsyncIteration("Connection.receive_message_async terminating") #end if return \ result #end __anext__ #end _MsgAiter class Server(TaskKeeper) : "wrapper around a DBusServer object. Do not instantiate directly; use" \ " the listen method.\n" \ "\n" \ "You only need this if you want to use D-Bus as a communication mechanism" \ " separate from the system/session buses provided by the D-Bus daemon: you" \ " create a Server object listening on a specified address, and clients can" \ " use Connection.open() to connect to you on that address." # # Doesn’t really need services of TaskKeeper for now, but might be # useful in future __slots__ = \ ( "_dbobj", "_new_connections", "_await_new_connections", "max_new_connections", "autoattach_new_connections", # need to keep references to ctypes-wrapped functions # so they don't disappear prematurely: "_new_connection_function", "_free_new_connection_data", "_add_watch_function", "_remove_watch_function", "_toggled_watch_function", "_free_watch_data", "_add_timeout_function", "_remove_timeout_function", "_toggled_timeout_function", "_free_timeout_data", ) # to forestall typos _instances = WeakValueDictionary() def __new__(celf, _dbobj) : self = celf._instances.get(_dbobj) if self == None : self = super().__new__(celf) super()._init(self) self._dbobj = _dbobj self._new_connections = None self._await_new_connections = None self.max_new_connections = None self.autoattach_new_connections = True self._new_connection_function = None self._free_new_connection_data = None self._add_watch_function = None self._remove_watch_function = None self._toggled_watch_function = None self._free_watch_data = None self._add_timeout_function = None self._remove_timeout_function = None self._toggled_timeout_function = None self._free_timeout_data = None celf._instances[_dbobj] = self else : dbus.dbus_server_unref(self._dbobj) # lose extra reference created by caller #end if return \ self #end __new__ def __del__(self) : if self._dbobj != None : if self.loop != None : # remove via direct low-level libdbus calls dbus.dbus_server_set_watch_functions(self._dbobj, None, None, None, None, None) dbus.dbus_server_set_timeout_functions(self._dbobj, None, None, None, None, None) self.loop = None #end if dbus.dbus_server_unref(self._dbobj) self._dbobj = None #end if #end __del__ @classmethod def listen(celf, address, error = None) : error, my_error = _get_error(error) result = dbus.dbus_server_listen(address.encode(), error._dbobj) my_error.raise_if_set() if result != None : result = celf(result) #end if return \ result #end listen def _flush_awaiting_connect(self) : if self._await_new_connections != None : while len(self._await_new_connections) != 0 : waiting = self._await_new_connections.pop(0) waiting.set_exception(BrokenPipeError("async listens have been disabled")) #end while #end if #end _flush_awaiting_connect def disconnect(self) : self._flush_awaiting_connect() dbus.dbus_server_disconnect(self._dbobj) #end disconnect @property def is_connected(self) : return \ dbus.dbus_server_get_is_connected(self._dbobj) != 0 #end is_connected @property def address(self) : c_result = dbus.dbus_server_get_address(self._dbobj) if c_result == None : raise CallFailed("dbus_server_get_address") #end if result = ct.cast(c_result, ct.c_char_p).value.decode() dbus.dbus_free(c_result) return \ result #end address @property def id(self) : c_result = dbus.dbus_server_get_id(self._dbobj) if c_result == None : raise CallFailed("dbus_server_get_id") #end if result = ct.cast(c_result, ct.c_char_p).value.decode() dbus.dbus_free(c_result) return \ result #end id def set_new_connection_function(self, function, data, free_data = None) : "sets the callback for libdbus to notify you of a new incoming connection." \ " It is up to you to save the Connection object for later processing of" \ " messages, or close it to reject the connection attempt." w_self = weak_ref(self) def wrap_function(c_self, c_conn, _data) : function(_wderef(w_self, "server"), Connection(dbus.dbus_connection_ref(c_conn)), data) # even though this is a new connection, I still have to reference it #end wrap_function def wrap_free_data(_data) : free_data(data) #end wrap_free_data #begin set_new_connection_function assert self.loop == None, "new connections are being managed by an event loop" self._new_connection_function = DBUS.NewConnectionFunction(wrap_function) if free_data != None : self._free_new_connection_data = DBUS.FreeFunction(wrap_free_data) else : self._free_new_connection_data = None #end if dbus.dbus_server_set_new_connection_function(self._dbobj, self._new_connection_function, None, self._free_new_connection_data) #end set_new_connection_function def set_watch_functions(self, add_function, remove_function, toggled_function, data, free_data = None) : "sets the callbacks for libdbus to use to notify you of Watch objects it wants" \ " you to manage." def wrap_add_function(c_watch, _data) : return \ add_function(Watch(c_watch), data) #end wrap_add_function def wrap_remove_function(c_watch, _data) : return \ remove_function(Watch(c_watch), data) #end wrap_remove_function def wrap_toggled_function(c_watch, _data) : return \ toggled_function(Watch(c_watch), data) #end wrap_toggled_function def wrap_free_data(_data) : free_data(data) #end wrap_free_data #begin set_watch_functions self._add_watch_function = DBUS.AddWatchFunction(wrap_add_function) self._remove_watch_function = DBUS.RemoveWatchFunction(wrap_remove_function) if toggled_function != None : self._toggled_watch_function = DBUS.WatchToggledFunction(wrap_toggled_function) else : self._toggled_watch_function = None #end if if free_data != None : self._free_watch_data = DBUS.FreeFunction(wrap_free_data) else : self._free_watch_data = None #end if if not dbus.dbus_server_set_watch_functions(self._dbobj, self._add_watch_function, self._remove_watch_function, self._toggled_watch_function, None, self._free_watch_data) : raise CallFailed("dbus_server_set_watch_functions") #end if #end set_watch_functions def set_timeout_functions(self, add_function, remove_function, toggled_function, data, free_data = None) : "sets the callbacks for libdbus to use to notify you of Timeout objects it wants" \ " you to manage." def wrap_add_function(c_timeout, _data) : return \ add_function(Timeout(c_timeout), data) #end wrap_add_function def wrap_remove_function(c_timeout, _data) : return \ remove_function(Timeout(c_timeout), data) #end wrap_remove_function def wrap_toggled_function(c_timeout, _data) : return \ toggled_function(Timeout(c_timeout), data) #end wrap_toggled_function def wrap_free_data(_data) : free_data(data) #end wrap_free_data #begin set_timeout_functions self._add_timeout_function = DBUS.AddTimeoutFunction(wrap_add_function) self._remove_timeout_function = DBUS.RemoveTimeoutFunction(wrap_remove_function) if toggled_function != None : self._toggled_timeout_function = DBUS.TimeoutToggledFunction(wrap_toggled_function) else : self._toggled_timeout_function = None #end if if free_data != None : self._free_timeout_data = DBUS.FreeFunction(wrap_free_data) else : self._free_timeout_data = None #end if if not dbus.dbus_server_set_timeout_functions(self._dbobj, self._add_timeout_function, self._remove_timeout_function, self._toggled_timeout_function, None, self._free_timeout_data) : raise CallFailed("dbus_server_set_timeout_functions") #end if #end set_timeout_functions def set_auth_mechanisms(self, mechanisms) : nr_mechanisms = len(mechanisms) c_mechanisms = (ct.c_char_p * (nr_mechanisms + 1))() for i in range(nr_mechanisms) : c_mechanisms[i] = mechanisms[i].encode() #end if c_mechanisms[nr_mechanisms] = None # marks end of array if not dbus.dbus_server_set_auth_mechanisms(self._dbobj, c_mechanisms) : raise CallFailed("dbus_server_set_auth_mechanisms") #end if #end set_auth_mechanisms # TODO: allocate/free slot (static methods) # TODO: get/set/data def attach_asyncio(self, loop = None) : "attaches this Server object to an asyncio event loop. If none is" \ " specified, the default event loop (as returned from asyncio.get_event_loop()" \ " is used.\n" \ "\n" \ "This call will also automatically attach a new_connection callback. You then use" \ " the await_new_connection coroutine to obtain new connections. If" \ " self.autoattach_new_connections, then Connection.attach_asyncio() will" \ " automatically be called to handle events for the new connection." def new_connection(self, conn, user_data) : if len(self._await_new_connections) != 0 : awaiting = self._await_new_connections.pop(0) awaiting.set_result(conn) else : # put it in _new_connections queue if ( self.max_new_connections != None and len(self._new_connections) >= self.max_new_connections ) : # too many connections pending, reject conn.close() else : self._new_connections.append(conn) #end if #end if #end new_connection #begin attach_asyncio assert self.loop == None, "already attached to an event loop" assert self._new_connection_function == None, "already set a new-connection function" self._new_connections = [] self._await_new_connections = [] self.set_new_connection_function(new_connection, None) _loop_attach(self, loop, None) #end attach_asyncio async def await_new_connection(self, timeout = DBUS.TIMEOUT_INFINITE) : "retrieves the next new Connection, if there is one available, otherwise" \ " suspends the current coroutine for up to the specified timeout duration" \ " while waiting for one to appear. Returns None if there is no new connection" \ " within that time." assert self.loop != None, "no event loop to attach coroutine to" if len(self._new_connections) != 0 : result = self._new_connections.pop(0) else : if not self.is_connected : raise BrokenPipeError("Server has been disconnected") #end if if timeout == 0 : # might as well short-circuit the whole waiting process result = None else : awaiting = self.loop.create_future() self._await_new_connections.append(awaiting) if timeout == DBUS.TIMEOUT_INFINITE : timeout = None else : if timeout == DBUS.TIMEOUT_USE_DEFAULT : timeout = DBUSX.DEFAULT_TIMEOUT #end if #end if await asyncio.wait \ ( (awaiting,), loop = self.loop, timeout = timeout ) # ignore done & pending results because they # don’t match up with future I’m waiting for if awaiting.done() : result = awaiting.result() else : self._await_new_connections.pop(self._await_new_connections.index(awaiting)) result = None #end if #end if #end if if result != None and self.autoattach_new_connections : result.attach_asyncio(self.loop) #end if return \ result #end await_new_connection def iter_connections_async(self, stop_on = None, timeout = DBUS.TIMEOUT_INFINITE) : "wrapper around await_new_connection() to allow use with an async-for" \ " statement. Lets you write\n" \ "\n" \ " async for conn in «server».iter_connections_async(«timeout») :" \ " «accept conn»\n" \ " #end for\n" \ "\n" \ "to receive and process incoming connections in a loop. stop_on is an optional set of" \ " STOP_ON.xxx values indicating the conditions under which the iterator will" \ " raise StopAsyncIteration to terminate the loop." assert self.loop != None, "no event loop to attach coroutine to" if stop_on == None : stop_on = frozenset() elif ( not isinstance(stop_on, (set, frozenset)) or not all(isinstance(elt, STOP_ON) for elt in stop_on) ) : raise TypeError("stop_on must be None or set of STOP_ON") #end if return \ _SrvAiter(self, stop_on, timeout) #end iter_connections_async #end Server class _SrvAiter : # internal class for use by Server.iter_connections_async (above). def __init__(self, srv, stop_on, timeout) : self.srv = srv self.stop_on = stop_on self.timeout = timeout #end __init__ def __aiter__(self) : # I’m my own iterator. return \ self #end __aiter__ async def __anext__(self) : stop_iter = False try : result = await self.srv.await_new_connection(self.timeout) if result == None and STOP_ON.TIMEOUT in self.stop_on : stop_iter = True #end if except BrokenPipeError : if STOP_ON.CLOSED not in self.stop_on : raise #end if stop_iter = True #end try if stop_iter : raise StopAsyncIteration("Server.iter_connections_async terminating") #end if return \ result #end __anext__ #end _SrvAiter class PreallocatedSend : "wrapper around a DBusPreallocatedSend object. Do not instantiate directly;" \ " get from Connection.preallocate_send method." # __slots__ = ("__weakref__", "_dbobj", "_w_parent", "_sent") # to forestall typos _instances = WeakValueDictionary() def __new__(celf, _dbobj, _parent) : self = celf._instances.get(_dbobj) if self == None : self = super().__new__(celf) self._dbobj = _dbobj self._w_parent = weak_ref(_parent) self._sent = False celf._instances[_dbobj] = self else : assert self._w_parent() == _parent #end if return \ self #end __new__ def __del__(self) : if self._dbobj != None : parent = self._w_parent() if parent != None and not self._sent : dbus.dbus_connection_free_preallocated_send(parent._dbobj, self._dbobj) #end if self._dbobj = None #end if #end __del__ def send(self, message) : "alternative to Connection.send_preallocated." if not isinstance(message, Message) : raise TypeError("message must be a Message") #end if assert not self._sent, "preallocated has already been sent" parent = self._w_parent() assert parent != None, "parent Connection has gone away" serial = ct.c_uint() dbus.dbus_connection_send_preallocated(parent._dbobj, self._dbobj, message._dbobj, ct.byref(serial)) self._sent = True return \ serial.value #end send #end PreallocatedSend class Message : "wrapper around a DBusMessage object. Do not instantiate directly; use one of the" \ " new_xxx or copy methods, or Connection.pop_message or Connection.borrow_message." # __slots__ = ("__weakref__", "_dbobj", "_conn", "_borrowed") # to forestall typos _instances = WeakValueDictionary() def __new__(celf, _dbobj) : self = celf._instances.get(_dbobj) if self == None : self = super().__new__(celf) self._dbobj = _dbobj self._conn = None self._borrowed = False celf._instances[_dbobj] = self else : dbus.dbus_message_unref(self._dbobj) # lose extra reference created by caller #end if return \ self #end __new__ def __del__(self) : if self._dbobj != None : assert not self._borrowed, "trying to dispose of borrowed message" dbus.dbus_message_unref(self._dbobj) self._dbobj = None #end if #end __del__ @classmethod def new(celf, type) : "type is one of the DBUS.MESSAGE_TYPE_xxx codes. Using one of the type-specific" \ " calls--new_error, new_method_call, new_method_return, new_signal--is probably" \ " more convenient." result = dbus.dbus_message_new(type) if result == None : raise CallFailed("dbus_message_new") #end if return \ celf(result) #end new def new_error(self, name, message) : "creates a new DBUS.MESSAGE_TYPE_ERROR message that is a reply to this Message." result = dbus.dbus_message_new_error(self._dbobj, name.encode(), (lambda : None, lambda : message.encode())[message != None]()) if result == None : raise CallFailed("dbus_message_new_error") #end if return \ type(self)(result) #end new_error # probably not much point trying to use new_error_printf @classmethod def new_method_call(celf, destination, path, iface, method) : "creates a new DBUS.MESSAGE_TYPE_METHOD_CALL message." result = dbus.dbus_message_new_method_call \ ( (lambda : None, lambda : destination.encode())[destination != None](), path.encode(), (lambda : None, lambda : iface.encode())[iface != None](), method.encode(), ) if result == None : raise CallFailed("dbus_message_new_method_call") #end if return \ celf(result) #end new_method_call def new_method_return(self) : "creates a new DBUS.MESSAGE_TYPE_METHOD_RETURN that is a reply to this Message." result = dbus.dbus_message_new_method_return(self._dbobj) if result == None : raise CallFailed("dbus_message_new_method_return") #end if return \ type(self)(result) #end new_method_return @classmethod def new_signal(celf, path, iface, name) : "creates a new DBUS.MESSAGE_TYPE_SIGNAL message." result = dbus.dbus_message_new_signal(path.encode(), iface.encode(), name.encode()) if result == None : raise CallFailed("dbus_message_new_signal") #end if return \ celf(result) #end new_signal def copy(self) : "creates a copy of this Message." result = dbus.dbus_message_copy(self._dbobj) if result == None : raise CallFailed("dbus_message_copy") #end if return \ type(self)(result) #end copy @property def type(self) : "returns the DBUS.MESSAGE_TYPE_XXX code for this Message." return \ dbus.dbus_message_get_type(self._dbobj) #end type # NYI append_args, get_args -- probably not useful, use my # objects and append_objects convenience methods (below) instead class ExtractIter : "for iterating over the arguments in a Message for reading. Do not" \ " instantiate directly; get from Message.iter_init or ExtractIter.recurse.\n" \ "\n" \ "You can use this as a Python iterator, in a for-loop, passing" \ " it to the next() built-in function etc. Do not mix such usage with calls to" \ " the has_next() and next() methods." __slots__ = ("_dbobj", "_parent", "_nulliter", "_startiter") # to forestall typos def __init__(self, _parent) : self._dbobj = DBUS.MessageIter() self._parent = _parent self._nulliter = False self._startiter = True #end __init__ @property def has_next(self) : return \ dbus.dbus_message_iter_has_next(self._dbobj) #end has_next def next(self) : if self._nulliter or not dbus.dbus_message_iter_next(self._dbobj) : raise StopIteration("end of message iterator") #end if self._startiter = False return \ self #end next def __iter__(self) : return \ self #end __iter__ def __next__(self) : if self._nulliter : raise StopIteration("empty message iterator") else : if self._startiter : self._startiter = False else : self.next() #end if #end if return \ self #end __next__ @property def arg_type(self) : "the type code for this argument." return \ dbus.dbus_message_iter_get_arg_type(self._dbobj) #end arg_type @property def element_type(self) : "the contained element type of this argument, assuming it is of a container type." return \ dbus.dbus_message_iter_get_element_type(self._dbobj) #end element_type def recurse(self) : "creates a sub-iterator for recursing into a container argument." subiter = type(self)(self) dbus.dbus_message_iter_recurse(self._dbobj, subiter._dbobj) return \ subiter #end recurse @property def signature(self) : c_result = dbus.dbus_message_iter_get_signature(self._dbobj) if c_result == None : raise CallFailed("dbus_message_iter_get_signature") #end if result = ct.cast(c_result, ct.c_char_p).value.decode() dbus.dbus_free(c_result) return \ result #end signature @property def basic(self) : "returns the argument value, assuming it is of a non-container type." argtype = self.arg_type c_result_type = DBUS.basic_to_ctypes[argtype] c_result = c_result_type() dbus.dbus_message_iter_get_basic(self._dbobj, ct.byref(c_result)) if c_result_type == ct.c_char_p : result = c_result.value.decode() else : result = c_result.value #end if if argtype in DBUS.basic_subclasses : result = DBUS.basic_subclasses[argtype](result) #end if return \ result #end basic @property def object(self) : "returns the current iterator item as a Python object. Will recursively" \ " process container objects." argtype = self.arg_type if argtype in DBUS.basic_to_ctypes : result = self.basic elif argtype == DBUS.TYPE_ARRAY : if self.element_type == DBUS.TYPE_DICT_ENTRY : result = {} subiter = self.recurse() while True : entry = next(subiter, None) if entry == None or entry.arg_type == DBUS.TYPE_INVALID : # TYPE_INVALID can be returned for an empty dict break if entry.arg_type != DBUS.TYPE_DICT_ENTRY : raise RuntimeError("invalid dict entry type %d" % entry.arg_type) #end if key, value = tuple(x.object for x in entry.recurse()) result[key] = value #end while elif type_is_fixed_array_elttype(self.element_type) : result = self.fixed_array else : result = list(x.object for x in self.recurse()) if len(result) != 0 and result[-1] == None : # fudge for iterating into an empty array result = result[:-1] #end if #end if elif argtype == DBUS.TYPE_STRUCT : result = list(x.object for x in self.recurse()) elif argtype == DBUS.TYPE_VARIANT : subiter = self.recurse() subiter = next(subiter) result = (DBUS.Signature(subiter.signature), subiter.object) elif argtype == DBUS.TYPE_INVALID : # fudge for iterating into an empty array result = None else : raise RuntimeError("unrecognized argtype %d" % argtype) #end if return \ result #end object if hasattr(dbus, "dbus_message_iter_get_element_count") : @property def element_count(self) : "returns the count of contained elements, assuming the current argument" \ " is of a container type." return \ dbus.dbus_message_iter_get_element_count(self._dbobj) #end element_count #end if @property def fixed_array(self) : "returns the array elements, assuming the current argument is an array" \ " with a non-container element type." c_element_type = DBUS.basic_to_ctypes[self.element_type] c_result = ct.POINTER(c_element_type)() c_nr_elts = ct.c_int() subiter = self.recurse() dbus.dbus_message_iter_get_fixed_array(subiter._dbobj, ct.byref(c_result), ct.byref(c_nr_elts)) result = [] for i in range(c_nr_elts.value) : elt = c_result[i] if c_element_type == ct.c_char_p : elt = elt.value.decode() #end if result.append(elt) #end for return \ result #end fixed_array #end ExtractIter class AppendIter : "for iterating over the arguments in a Message for appending." \ " Do not instantiate directly; get from Message.iter_init_append or" \ " AppendIter.open_container." __slots__ = ("_dbobj", "_parent") # to forestall typos def __init__(self, _parent) : self._dbobj = DBUS.MessageIter() self._parent = _parent #end __init__ def append_basic(self, type, value) : "appends a single value of a non-container type." if type in DBUS.int_convert : value = DBUS.int_convert[type](value) #end if c_type = DBUS.basic_to_ctypes[type] if c_type == ct.c_char_p : if not isinstance(value, str) : raise TypeError \ ( "expecting type %s, got %s" % (TYPE(type), builtins.type(value).__name__) ) #end if value = value.encode() #end if c_value = c_type(value) if not dbus.dbus_message_iter_append_basic(self._dbobj, type, ct.byref(c_value)) : raise CallFailed("dbus_message_iter_append_basic") #end if return \ self #end append_basic def append_fixed_array(self, element_type, values) : "appends an array of elements of a non-container type." c_elt_type = DBUS.basic_to_ctypes[element_type] nr_elts = len(values) c_arr = (nr_elts * c_elt_type)() for i in range(nr_elts) : if c_elt_type == ct.c_char_p : c_arr[i] = values[i].encode() else : c_arr[i] = values[i] #end if #end for c_arr_ptr = ct.pointer(c_arr) if not dbus.dbus_message_iter_append_fixed_array(self._dbobj, element_type, ct.byref(c_arr_ptr), nr_elts) : raise CallFailed("dbus_message_iter_append_fixed_array") #end if return \ self #end append_fixed_array def open_container(self, type, contained_signature) : "starts appending an argument of a container type, returning a sub-iterator" \ " for appending the contents of the argument. Can be called recursively for" \ " containers of containers etc." if contained_signature != None : c_sig = contained_signature.encode() else : c_sig = None #end if subiter = builtins.type(self)(self) if not dbus.dbus_message_iter_open_container(self._dbobj, type, c_sig, subiter._dbobj) : raise CallFailed("dbus_message_iter_open_container") #end if return \ subiter #end open_container def close(self) : "closes a sub-iterator, indicating the completion of construction" \ " of a container value." assert self._parent != None, "cannot close top-level iterator" if not dbus.dbus_message_iter_close_container(self._parent._dbobj, self._dbobj) : raise CallFailed("dbus_message_iter_close_container") #end if return \ self._parent #end close def abandon(self) : "closes a sub-iterator, indicating the abandonment of construction" \ " of a container value. The Message object is effectively unusable" \ " after this point and should be discarded." assert self._parent != None, "cannot abandon top-level iterator" dbus.dbus_message_iter_abandon_container(self._parent._dbobj, self._dbobj) return \ self._parent #end abandon #end AppendIter def iter_init(self) : "creates an iterator for extracting the arguments of the Message." iter = self.ExtractIter(None) if dbus.dbus_message_iter_init(self._dbobj, iter._dbobj) == 0 : iter._nulliter = True #end if return \ iter #end iter_init @property def objects(self) : "yields the arguments of the Message as Python objects." for iter in self.iter_init() : yield iter.object #end for #end objects @property def all_objects(self) : "all the arguments of the Message as a list of Python objects." return \ list(self.objects) #end all_objects def expect_objects(self, signature) : "expects the arguments of the Message to conform to the given signature," \ " raising a TypeError if not. If they match, returns them as a list." signature = unparse_signature(signature) if self.signature != signature : raise TypeError("message args don’t match: expected “%s”, got “%s”" % (signature, self.signature)) #end if return \ self.all_objects #end expect_objects def expect_return_objects(self, signature) : "expects the Message to be of type DBUS.MESSAGE_TYPE_METHOD_RETURN and its" \ " arguments to conform to the given signature. Raises the appropriate DBusError" \ " if the Message is of type DBUS.MESSAGE_TYPE_ERROR." if self.type == DBUS.MESSAGE_TYPE_METHOD_RETURN : result = self.expect_objects(signature) elif self.type == DBUS.MESSAGE_TYPE_ERROR : raise DBusError(self.error_name, self.expect_objects("s")[0]) else : raise ValueError("unexpected message type %d" % self.type) #end if return \ result #end expect_return_objects def iter_init_append(self) : "creates a Message.AppendIter for appending arguments to the Message." iter = self.AppendIter(None) dbus.dbus_message_iter_init_append(self._dbobj, iter._dbobj) return \ iter #end iter_init_append def append_objects(self, signature, *args) : "interprets Python values args according to signature and appends" \ " converted item(s) to the message args." def append_sub(siglist, eltlist, appenditer) : if len(siglist) != len(eltlist) : raise ValueError \ ( "mismatch between signature entries %s and number of sequence elements %s" % (repr(siglist), repr(eltlist)) ) #end if for elttype, elt in zip(siglist, eltlist) : if isinstance(elttype, BasicType) : appenditer.append_basic(elttype.code.value, elt) elif isinstance(elttype, DictType) : if not isinstance(elt, dict) : raise TypeError("dict expected for %s" % repr(elttype)) #end if subiter = appenditer.open_container(DBUS.TYPE_ARRAY, elttype.entry_signature) for key in sorted(elt) : # might as well insert in some kind of predictable order value = elt[key] subsubiter = subiter.open_container(DBUS.TYPE_DICT_ENTRY, None) append_sub([elttype.keytype, elttype.valuetype], [key, value], subsubiter) subsubiter.close() #end for subiter.close() elif isinstance(elttype, ArrayType) : # append 0 or more elements matching elttype.elttype arrelttype = elttype.elttype if type_is_fixed_array_elttype(arrelttype.code.value) : subiter = appenditer.open_container(DBUS.TYPE_ARRAY, arrelttype.signature) subiter.append_fixed_array(arrelttype.code.value, elt) subiter.close() else : subiter = appenditer.open_container(DBUS.TYPE_ARRAY, arrelttype.signature) if not isinstance(elt, (tuple, list)) : raise TypeError("expecting sequence of values for array") #end if for subval in elt : append_sub([arrelttype], [subval], subiter) #end for subiter.close() #end if elif isinstance(elttype, StructType) : if not isinstance(elt, (tuple, list)) : raise TypeError("expecting sequence of values for struct") #end if subiter = appenditer.open_container(DBUS.TYPE_STRUCT, None) append_sub(elttype.elttypes, elt, subiter) subiter.close() elif isinstance(elttype, VariantType) : if not isinstance(elt, (list, tuple)) or len(elt) != 2 : raise TypeError("sequence of 2 elements expected for variant: %s" % repr(elt)) #end if actual_type = parse_single_signature(elt[0]) subiter = appenditer.open_container(DBUS.TYPE_VARIANT, actual_type.signature) append_sub([actual_type], [elt[1]], subiter) subiter.close() else : raise RuntimeError("unrecognized type %s" % repr(elttype)) #end if #end for #end append_sub #begin append_objects append_sub(parse_signature(signature), args, self.iter_init_append()) return \ self #end append_objects @property def no_reply(self) : "whether the Message is not expecting a reply." return \ dbus.dbus_message_get_no_reply(self._dbobj) != 0 #end no_reply @no_reply.setter def no_reply(self, no_reply) : dbus.dbus_message_set_no_reply(self._dbobj, no_reply) #end no_reply @property def auto_start(self) : return \ dbus.dbus_message_get_auto_start(self._dbobj) != 0 #end auto_start @auto_start.setter def auto_start(self, auto_start) : dbus.dbus_message_set_auto_start(self._dbobj, auto_start) #end auto_start @property def path(self) : "the object path for a DBUS.MESSAGE_TYPE_METHOD_CALL or DBUS.DBUS.MESSAGE_TYPE_SIGNAL" \ " message." result = dbus.dbus_message_get_path(self._dbobj) if result != None : result = DBUS.ObjectPath(result.decode()) #end if return \ result #end path @path.setter def path(self, object_path) : if not dbus.dbus_message_set_path(self._dbobj, (lambda : None, lambda : object_path.encode())[object_path != None]()) : raise CallFailed("dbus_message_set_path") #end if #end path @property def path_decomposed(self) : "the object path for a DBUS.MESSAGE_TYPE_METHOD_CALL or DBUS.DBUS.MESSAGE_TYPE_SIGNAL" \ " message, decomposed into a list of the slash-separated components without the slashes." path = ct.POINTER(ct.c_char_p)() if not dbus.dbus_message_get_path_decomposed(self._dbobj, ct.byref(path)) : raise CallFailed("dbus_message_get_path_decomposed") #end if if bool(path) : result = [] i = 0 while True : entry = path[i] if entry == None : break result.append(entry.decode()) i += 1 #end while dbus.dbus_free_string_array(path) else : result = None #end if return \ result #end path_decomposed @property def interface(self) : "the interface name for a DBUS.MESSAGE_TYPE_METHOD_CALL or DBUS.MESSAGE_TYPE_SIGNAL" \ " message." result = dbus.dbus_message_get_interface(self._dbobj) if result != None : result = result.decode() #end if return \ result #end interface @interface.setter def interface(self, iface) : if not dbus.dbus_message_set_interface(self._dbobj, (lambda : None, lambda : iface.encode())[iface != None]()) : raise CallFailed("dbus_message_set_interface") #end if #end interface def has_interface(self, iface) : return \ dbus.dbus_message_has_interface(self._dbobj, iface.encode()) != 0 #end has_interface @property def member(self) : "the method name for a DBUS.MESSAGE_TYPE_METHOD_CALL message or the signal" \ " name for DBUS.MESSAGE_TYPE_SIGNAL." result = dbus.dbus_message_get_member(self._dbobj) if result != None : result = result.decode() #end if return \ result #end member @member.setter def member(self, member) : if not dbus.dbus_message_set_member(self._dbobj, (lambda : None, lambda : member.encode())[member != None]()) : raise CallFailed("dbus_message_set_member") #end if #end member def has_member(self, member) : return \ dbus.dbus_message_has_member(self._dbobj, member.encode()) != 0 #end has_member @property def error_name(self) : "the error name for a DBUS.MESSAGE_TYPE_ERROR message." result = dbus.dbus_message_get_error_name(self._dbobj) if result != None : result = result.decode() #end if return \ result #end error_name @error_name.setter def error_name(self, error_name) : if not dbus.dbus_message_set_error_name(self._dbobj, (lambda : None, lambda : error_name.encode())[error_name != None]()) : raise CallFailed("dbus_message_set_error_name") #end if #end error_name @property def destination(self) : "the bus name that the message is to be sent to." result = dbus.dbus_message_get_destination(self._dbobj) if result != None : result = result.decode() #end if return \ result #end destination @destination.setter def destination(self, destination) : if not dbus.dbus_message_set_destination(self._dbobj, (lambda : None, lambda : destination.encode())[destination != None]()) : raise CallFailed("dbus_message_set_destination") #end if #end destination @property def sender(self) : result = dbus.dbus_message_get_sender(self._dbobj) if result != None : result = result.decode() #end if return \ result #end sender @sender.setter def sender(self, sender) : if not dbus.dbus_message_set_sender(self._dbobj, (lambda : None, lambda : sender.encode())[sender != None]()) : raise CallFailed("dbus_message_set_sender") #end if #end sender @property def signature(self) : result = dbus.dbus_message_get_signature(self._dbobj) if result != None : result = DBUS.Signature(result.decode()) #end if return \ result #end signature def is_method_call(self, iface, method) : return \ dbus.dbus_message_is_method_call(self._dbobj, iface.encode(), method.encode()) != 0 #end is_method_call def is_signal(self, iface, signal_name) : return \ dbus.dbus_message_is_signal(self._dbobj, iface.encode(), signal_name.encode()) != 0 #end is_signal def is_error(self, iface, error_name) : return \ dbus.dbus_message_is_error(self._dbobj, error_name.encode()) != 0 #end is_error def has_destination(self, iface, destination) : return \ dbus.dbus_message_has_destination(self._dbobj, destination.encode()) != 0 #end has_destination def has_sender(self, iface, sender) : return \ dbus.dbus_message_has_sender(self._dbobj, sender.encode()) != 0 #end has_sender def has_signature(self, iface, signature) : return \ dbus.dbus_message_has_signature(self._dbobj, signature.encode()) != 0 #end has_signature def set_error(self, error) : "fills in error if this is an error message, else does nothing. Returns" \ " whether it was an error message or not." if not isinstance(error, Error) : raise TypeError("error must be an Error") #end if return \ dbus.dbus_set_error_from_message(error._dbobj, self._dbobj) != 0 #end set_error @property def contains_unix_fds(self) : return \ dbus.dbus_message_contains_unix_fds(self._dbobj) != 0 #end contains_unix_fds @property def serial(self) : "the serial number of the Message, to be referenced in replies." return \ dbus.dbus_message_get_serial(self._dbobj) #end serial @serial.setter def serial(self, serial) : dbus.dbus_message_set_serial(self._dbobj, serial) #end serial @property def reply_serial(self) : "the serial number of the original Message that that this" \ " DBUS.MESSAGE_TYPE_METHOD_RETURN message is a reply to." return \ dbus.dbus_message_get_reply_serial(self._dbobj) #end reply_serial @reply_serial.setter def reply_serial(self, serial) : if not dbus.dbus_message_set_reply_serial(self._dbobj, serial) : raise CallFailed("dbus_message_set_reply_serial") #end if #end serial def lock(self) : dbus.dbus_message_lock(self._dbobj) #end lock def return_borrowed(self) : assert self._borrowed and self._conn != None dbus.dbus_connection_return_message(self._conn._dbobj, self._dbobj) self._borrowed = False #end return_borrowed def steal_borrowed(self) : assert self._borrowed and self._conn != None dbus.dbus_connection_steal_borrowed_message(self._conn._dbobj, self._dbobj) self._borrowed = False return \ self #end steal_borrowed # TODO: allocate/free data slot -- static methods # (freeing slot can set passed-in var to -1 on actual free; do I care?) # TODO: set/get data @staticmethod def type_from_string(type_str) : "returns a MESSAGE_TYPE_xxx value." return \ dbus.dbus_message_type_from_string(type_str.encode()) #end type_from_string @staticmethod def type_to_string(type) : "type is a MESSAGE_TYPE_xxx value." return \ dbus.dbus_message_type_to_string(type).decode() #end type_to_string def marshal(self) : "serializes this Message into the wire protocol format and returns a bytes object." buf = ct.POINTER(ct.c_ubyte)() nr_bytes = ct.c_int() if not dbus.dbus_message_marshal(self._dbobj, ct.byref(buf), ct.byref(nr_bytes)) : raise CallFailed("dbus_message_marshal") #end if result = bytearray(nr_bytes.value) ct.memmove \ ( ct.addressof((ct.c_ubyte * nr_bytes.value).from_buffer(result)), buf, nr_bytes.value ) dbus.dbus_free(buf) return \ result #end marshal @classmethod def demarshal(celf, buf, error = None) : "deserializes a bytes or array-of-bytes object from the wire protocol" \ " format into a Message object." error, my_error = _get_error(error) if isinstance(buf, bytes) : baseadr = ct.cast(buf, ct.c_void_p).value elif isinstance(buf, bytearray) : baseadr = ct.addressof((ct.c_ubyte * len(buf)).from_buffer(buf)) elif isinstance(buf, array.array) and buf.typecode == "B" : baseadr = buf.buffer_info()[0] else : raise TypeError("buf is not bytes, bytearray or array.array of bytes") #end if msg = dbus.dbus_message_demarshal(baseadr, len(buf), error._dbobj) my_error.raise_if_set() if msg != None : msg = celf(msg) #end if return \ msg #end demarshal @classmethod def demarshal_bytes_needed(celf, buf) : "the number of bytes needed to deserialize a bytes or array-of-bytes" \ " object from the wire protocol format." if isinstance(buf, bytes) : baseadr = ct.cast(buf, ct.c_void_p).value elif isinstance(buf, bytearray) : baseadr = ct.addressof((ct.c_ubyte * len(buf)).from_buffer(buf)) elif isinstance(buf, array.array) and buf.typecode == "B" : baseadr = buf.buffer_info()[0] else : raise TypeError("buf is not bytes, bytearray or array.array of bytes") #end if return \ dbus.dbus_message_demarshal_bytes_needed(baseadr, len(buf)) #end demarshal_bytes_needed @property def interactive_authorization(self) : return \ dbus.dbus_message_get_interactive_authorization(self._dbobj) #end interactive_authorization @interactive_authorization.setter def interactive_authorization(self, allow) : dbus.dbus_message_set_interactive_authorization(self._dbobj, allow) #end interactive_authorization #end Message class PendingCall : "wrapper around a DBusPendingCall object. This represents a pending reply" \ " message that hasn’t been received yet. Do not instantiate directly; libdbus" \ " creates these as the result from calling send_with_reply() on a Message." # __slots__ = \ ( "__weakref__", "_dbobj", "_w_conn", "_wrap_notify", "_wrap_free", "_awaiting", ) # to forestall typos _instances = WeakValueDictionary() def __new__(celf, _dbobj, _conn) : self = celf._instances.get(_dbobj) if self == None : self = super().__new__(celf) self._dbobj = _dbobj self._w_conn = weak_ref(_conn) self._wrap_notify = None self._wrap_free = None self._awaiting = None celf._instances[_dbobj] = self else : dbus.dbus_pending_call_unref(self._dbobj) # lose extra reference created by caller #end if return \ self #end __new__ def __del__(self) : if self._dbobj != None : dbus.dbus_pending_call_unref(self._dbobj) self._dbobj = None #end if #end __del__ def set_notify(self, function, user_data, free_user_data = None) : "sets the callback for libdbus to notify you that the pending message" \ " has become available. Note: it appears to be possible for your notifier" \ " to be called spuriously before the message is actually available." w_self = weak_ref(self) def wrap_notify(c_pending, c_user_data) : function(_wderef(w_self, "pending call"), user_data) #end _wrap_notify def wrap_free(c_user_data) : free_user_data(user_data) #end _wrap_free #begin set_notify if function != None : self._wrap_notify = DBUS.PendingCallNotifyFunction(wrap_notify) else : self._wrap_notify = None #end if if free_user_data != None : self._wrap_free = DBUS.FreeFunction(wrap_free) else : self._wrap_free = None #end if if not dbus.dbus_pending_call_set_notify(self._dbobj, self._wrap_notify, None, self._wrap_free) : raise CallFailed("dbus_pending_call_set_notify") #end if #end set_notify def cancel(self) : "tells libdbus you no longer care about the pending incoming message." dbus.dbus_pending_call_cancel(self._dbobj) if self._awaiting != None : # This probably shouldn’t occur. Looking at the source of libdbus, # it doesn’t keep track of any “cancelled” state for the PendingCall, # it just detaches it from any notifications about an incoming reply. self._awaiting.cancel() #end if #end cancel @property def completed(self) : "checks whether the pending message is available." return \ dbus.dbus_pending_call_get_completed(self._dbobj) != 0 #end completed def steal_reply(self) : "retrieves the Message, assuming it is actually available." \ " You should check PendingCall.completed returns True first." result = dbus.dbus_pending_call_steal_reply(self._dbobj) if result != None : result = Message(result) #end if return \ result #end steal_reply async def await_reply(self) : "retrieves the Message. If it is not yet available, suspends the" \ " coroutine (letting the event loop do other things) until it becomes" \ " available. On a timeout, libdbus will construct and return an error" \ " return message." conn = self._w_conn() assert conn != None, "parent Connection has gone away" assert conn.loop != None, "no event loop on parent Connection to attach coroutine to" if self._wrap_notify != None or self._awaiting != None : raise asyncio.InvalidStateError("there is already a notify set on this PendingCall") #end if done = conn.loop.create_future() self._awaiting = done def pending_done(pending, wself) : if not done.done() : # just in case of self.cancel() being called self = wself() # Note it seems to be possible for callback to be triggered spuriously if self != None and self.completed : done.set_result(self.steal_reply()) #end if #end if #end pending_done self.set_notify(pending_done, weak_ref(self)) # avoid reference circularity self → pending_done → self reply = await done return \ reply #end await_reply def block(self) : "blocks the current thread until the pending message has become available." dbus.dbus_pending_call_block(self._dbobj) #end block # TODO: data slots (static methods), get/set data #end PendingCall class Error : "wrapper around a DBusError object. You can create one by calling the init method." # __slots__ = ("_dbobj",) # to forestall typos def __init__(self) : dbobj = DBUS.Error() dbus.dbus_error_init(dbobj) self._dbobj = dbobj #end __init__ def __del__(self) : if self._dbobj != None : dbus.dbus_error_free(self._dbobj) self._dbobj = None #end if #end __del__ @classmethod def init(celf) : "for consistency with other classes that don’t want caller to instantiate directly." return \ celf() #end init def set(self, name, msg) : "fills in the error name and message." dbus.dbus_set_error(self._dbobj, name.encode(), b"%s", msg.encode()) #end set @property def is_set(self) : "has the Error been filled in." return \ dbus.dbus_error_is_set(self._dbobj) != 0 #end is_set def has_name(self, name) : "has the Error got the specified name." return \ dbus.dbus_error_has_name(self._dbobj, name.encode()) != 0 #end has_name @property def name(self) : "the name of the Error, if it has been filled in." return \ (lambda : None, lambda : self._dbobj.name.decode())[self._dbobj.name != None]() #end name @property def message(self) : "the message string for the Error, if it has been filled in." return \ (lambda : None, lambda : self._dbobj.message.decode())[self._dbobj.message != None]() #end message def raise_if_set(self) : "raises a DBusError exception if this Error has been filled in." if self.is_set : raise DBusError(self.name, self.message) #end if #end raise_if_set def set_from_message(self, message) : "fills in this Error object from message if it is an error message." \ " Returns whether it was or not." if not isinstance(message, Message) : raise TypeError("message must be a Message") #end if return \ dbus.dbus_set_error_from_message(self._dbobj, message._dbobj) != 0 #end set_from_message #end Error class AddressEntries : "wrapper for arrays of DBusAddressEntry values. Do not instantiate directly;" \ " get from AddressEntries.parse. This object behaves like an array; you can obtain" \ " the number of elements with len(), and use array subscripting to access the elements." # __slots__ = ("__weakref__", "_dbobj", "_nrelts") # to forestall typos def __init__(self, _dbobj, _nrelts) : self._dbobj = _dbobj self._nrelts = _nrelts #end __init__ def __del__(self) : if self._dbobj != None : dbus.dbus_address_entries_free(self._dbobj) self._dbobj = None #end if #end __del__ class Entry : "a single AddressEntry. Do not instantiate directly; get from AddressEntries[]." \ " This object behaves like a dictionary in that you can use keys to get values;" \ " however, there is no libdbus API to check what keys are present; unrecognized" \ " keys return a value of None." __slots__ = ("_dbobj", "_parent", "_index") # to forestall typos def __init__(self, _parent, _index) : self._dbobj = _parent._dbobj self._parent = weak_ref(_parent) self._index = _index #end __init__ @property def method(self) : assert self._parent() != None, "AddressEntries object has gone" result = dbus.dbus_address_entry_get_method(self._dbobj[self._index]) if result != None : result = result.decode() #end if return \ result #end method def get_value(self, key) : assert self._parent() != None, "AddressEntries object has gone" c_result = dbus.dbus_address_entry_get_value(self._dbobj[self._index], key.encode()) if c_result != None : result = c_result.decode() else : result = None #end if return \ result #end get_value __getitem__ = get_value #end Entry @classmethod def parse(celf, address, error = None) : error, my_error = _get_error(error) c_result = ct.POINTER(ct.c_void_p)() nr_elts = ct.c_int() if not dbus.dbus_parse_address(address.encode(), ct.byref(c_result), ct.byref(nr_elts), error._dbobj) : c_result.contents = None nr_elts.value = 0 #end if my_error.raise_if_set() if c_result.contents != None : result = celf(c_result, nr_elts.value) else : result = None #end if return \ result #end parse def __len__(self) : return \ self._nrelts #end __len__ def __getitem__(self, index) : if not isinstance(index, int) or index < 0 or index >= self._nrelts : raise IndexError("AddressEntries[%d] out of range" % index) #end if return \ type(self).Entry(self, index) #end __getitem__ #end AddressEntries def address_escape_value(value) : c_result = dbus.dbus_address_escape_value(value.encode()) if c_result == None : raise CallFailed("dbus_address_escape_value") #end if result = ct.cast(c_result, ct.c_char_p).value.decode() dbus.dbus_free(c_result) return \ result #end address_escape_value def address_unescape_value(value, error = None) : error, my_error = _get_error(error) c_result = dbus.dbus_address_unescape_value(value.encode(), error._dbobj) my_error.raise_if_set() if c_result != None : result = ct.cast(c_result, ct.c_char_p).value.decode() dbus.dbus_free(c_result) elif not error.is_set : raise CallFailed("dbus_address_unescape_value") else : result = None #end if return \ result #end address_unescape_value def format_rule(rule) : "convenience routine to allow a match rule to be expressed as either" \ " a dict of {key : value} or the usual string \"key='value'\", automatically" \ " converting the former to the latter." def escape_val(val) : if "," in val : if "'" in val : out = "'" in_quotes = True for ch in val : if ch == "'" : if in_quotes : out += "'" in_quotes = False #end if out += "\\'" else : if not in_quotes : out += "'" in_quotes = True #end if out += ch #end if #end for if in_quotes : out += "'" #end if else : out = "'" + val + "'" #end if else : out = "" for ch in val : if ch in ("\\", "'") : out += "\\" #end if out += ch #end for #end if return \ out #end escape_val #begin format_rule if isinstance(rule, str) : pass elif isinstance(rule, dict) : rule = ",".join("%s=%s" % (k, escape_val(rule[k])) for k in sorted(rule)) # sort to ensure some kind of consistent ordering, just for # appearance’s sake else : raise TypeError("rule “%s” must be a dict or string" % repr(rule)) #end if return \ rule #end format_rule class _RuleParser : # internal definitions for rule parsing. class PARSE(enum.Enum) : EXPECT_NAME = 1 EXPECT_UNQUOTED_VALUE = 2 EXPECT_ESCAPED = 3 EXPECT_QUOTED_VALUE = 4 #end PARSE @classmethod def unformat_rule(celf, rule) : "converts a match rule string from the standard syntax to a dict of {key : value} entries." if isinstance(rule, dict) : pass elif isinstance(rule, str) : PARSE = celf.PARSE parsed = {} chars = iter(rule) state = PARSE.EXPECT_NAME curname = None curval = None while True : ch = next(chars, None) if ch == None : if state == PARSE.EXPECT_ESCAPED : raise SyntaxError("missing character after backslash") elif state == PARSE.EXPECT_QUOTED_VALUE : raise SyntaxError("missing closing apostrophe") else : # state in (PARSE.EXPECT_NAME, PARSE.EXPECT_UNQUOTED_VALUE) if curname != None : if curval != None : if curname in parsed : raise SyntaxError("duplicated attribute “%s”" % curname) #end if parsed[curname] = curval else : raise SyntaxError("missing value for attribute “%s”" % curname) #end if #end if #end if break #end if if state == PARSE.EXPECT_ESCAPED : if ch == "'" : usech = ch nextch = None else : usech = "\\" nextch = ch #end if ch = usech if curval == None : curval = ch else : curval += ch #end if ch = nextch # None indicates already processed state = PARSE.EXPECT_UNQUOTED_VALUE #end if if ch != None : if ch == "," and state != PARSE.EXPECT_QUOTED_VALUE : if state == PARSE.EXPECT_UNQUOTED_VALUE : if curname in parsed : raise SyntaxError("duplicated attribute “%s”" % curname) #end if if curval == None : curval = "" #end if parsed[curname] = curval curname = None curval = None state = PARSE.EXPECT_NAME else : raise SyntaxError("unexpected comma") #end if elif ch == "\\" and state != PARSE.EXPECT_QUOTED_VALUE : if state == PARSE.EXPECT_UNQUOTED_VALUE : state = PARSE.EXPECT_ESCAPED else : raise SyntaxError("unexpected backslash") #end if elif ch == "=" and state != PARSE.EXPECT_QUOTED_VALUE : if curname == None : raise SyntaxError("empty attribute name") #end if if state == PARSE.EXPECT_NAME : state = PARSE.EXPECT_UNQUOTED_VALUE else : raise SyntaxError("unexpected equals sign") #end if elif ch == "'" : if state == PARSE.EXPECT_UNQUOTED_VALUE : state = PARSE.EXPECT_QUOTED_VALUE elif state == PARSE.EXPECT_QUOTED_VALUE : state = PARSE.EXPECT_UNQUOTED_VALUE else : raise SyntaxError("unexpected apostrophe") #end if else : if state == PARSE.EXPECT_NAME : if curname == None : curname = ch else : curname += ch #end if elif state in (PARSE.EXPECT_QUOTED_VALUE, PARSE.EXPECT_UNQUOTED_VALUE) : if curval == None : curval = ch else : curval += ch #end if else : raise AssertionError("shouldn’t occur: parse state %s" % repr(state)) #end if #end if #end if #end while rule = parsed else : raise TypeError("rule “%s” must be a dict or string" % repr(rule)) #end if return \ rule #end unformat_rule #end _RuleParser unformat_rule = _RuleParser.unformat_rule del _RuleParser def matches_rule(message, rule, destinations = None) : "does Message message match against the specified rule." if not isinstance(message, Message) : raise TypeError("message must be a Message") #end if rule = unformat_rule(rule) eavesdrop = rule.get("eavesdrop", "false") == "true" def match_message_type(expect, actual) : return \ actual == Message.type_from_string(expect) #end match_message_type def match_path_namespace(expect, actual) : return \ ( actual != None and ( expect == actual or actual.startswith(expect) and (expect == "/" or actual[len(expect)] == "/") ) ) #end match_path_namespace def match_dotted_namespace(expect, actual) : return \ ( actual != None and ( expect == actual or actual.startswith(expect) and actual[len(expect)] == "." ) ) #end match_dotted_namespace def get_nth_arg(msg, n, expect_types) : msg_signature = parse_signature(msg.signature) if n >= len(msg_signature) : raise IndexError("arg nr %d beyond nr args %d" % (n, len(msg_signature))) #end if val = msg.all_objects[n] valtype = msg_signature[n] if valtype not in expect_types : if False : raise TypeError \ ( "expecting one of types %s, not %s for arg %d val %s" % ((repr(expect_types), repr(valtype), n, repr(val))) ) #end if val = None # never match #end if return \ val #end get_nth_arg def get_arg_0_str(message) : return \ get_nth_arg(message, 0, [BasicType(TYPE.STRING)]) #end get_arg_0_str def match_arg_paths(expect, actual) : return \ ( actual != None and ( expect == actual or expect.endswith("/") and actual.startswith(expect) or actual.endswith("/") and expect.startswith(actual) ) ) #end match_arg_paths match_types = \ ( # note that message attribute value of None will fail to match # any expected string value, which is exactly what we want ("type", None, match_message_type, None), ("sender", None, operator.eq, None), ("interface", None, operator.eq, None), ("member", None, operator.eq, None), ("path", None, operator.eq, None), ("destination", None, operator.eq, None), ("path_namespace", "path", match_path_namespace, None), ("arg0namespace", None, match_dotted_namespace, get_arg_0_str), # “arg«n»path” handled specially below ) #begin matches_rule keys_used = set(rule.keys()) - {"eavesdrop"} matches = \ ( eavesdrop or destinations == None or message.destination == None or message.destination in destinations ) if matches : try_matching = iter(match_types) while True : try_rule = next(try_matching, None) if try_rule == None : break rulekey, attrname, action, accessor = try_rule if attrname == None : attrname = rulekey #end if if rulekey in rule : if accessor != None : val = accessor(message) else : val = getattr(message, attrname) #end if keys_used.remove(rulekey) if not action(rule[rulekey], val) : matches = False break #end if #end if #end while #end if if matches : try_matching = iter(rule.keys()) while True : try_key = next(try_matching, None) if try_key == None : break if try_key.startswith("arg") and not try_key.endswith("namespace") : argnr = try_key[3:] is_path = argnr.endswith("path") if is_path : argnr = argnr[:-4] #end if argnr = int(argnr) if not (0 <= argnr < 64) : raise ValueError("argnr %d out of range" % argnr) #end if argval = get_nth_arg \ ( message, argnr, [BasicType(TYPE.STRING)] + ([], [BasicType(TYPE.OBJECT_PATH)])[is_path] ) keys_used.remove(try_key) if not (operator.eq, match_arg_paths)[is_path](rule[try_key], argval) : matches = False break #end if #end if #end while #end if if matches and len(keys_used) != 0 : # fixme: not checking for unrecognized rule keys if I didn’t try matching them all raise KeyError("unrecognized rule keywords: %s" % ", ".join(sorted(keys_used))) #end if return \ matches #end matches_rule class SignatureIter : "wraps a DBusSignatureIter object. Do not instantiate directly; use the init" \ " and recurse methods." # __slots__ = ("_dbobj", "_signature", "_startiter") # to forestall typos @classmethod def init(celf, signature) : self = celf() self._signature = ct.c_char_p(signature.encode()) # need to ensure storage stays valid dbus.dbus_signature_iter_init(self._dbobj, self._signature) return \ self #end init def __init__(self) : self._dbobj = DBUS.SignatureIter() self._signature = None # caller will set as necessary self._startiter = True #end __init__ def __iter__(self) : return \ self #end __iter__ def __next__(self) : if self._startiter : self._startiter = False else : self.next() #end if return \ self #end __next__ def next(self) : if dbus.dbus_signature_iter_next(self._dbobj) == 0 : raise StopIteration("end of signature iterator") #end if self._startiter = False return \ self #end next def recurse(self) : subiter = type(self)() dbus.dbus_signature_iter_recurse(self._dbobj, subiter._dbobj) return \ subiter #end recurse @property def current_type(self) : return \ dbus.dbus_signature_iter_get_current_type(self._dbobj) #end current_type @property def signature(self) : c_result = dbus.dbus_signature_iter_get_signature(self._dbobj) result = ct.cast(c_result, ct.c_char_p).value.decode() dbus.dbus_free(c_result) return \ result #end signature @property def parsed_signature(self) : return \ parse_single_signature(self.signature) #end parsed_signature @property def element_type(self) : return \ dbus.dbus_signature_iter_get_element_type(self._dbobj) #end element_type #end SignatureIter def signature_validate(signature, error = None) : "is signature a valid sequence of zero or more complete types." error, my_error = _get_error(error) result = dbus.dbus_signature_validate(signature.encode(), error._dbobj) != 0 my_error.raise_if_set() return \ result #end signature_validate def parse_signature(signature) : "convenience routine for parsing a signature string into a list of Type()" \ " instances." def process_subsig(sigelt) : elttype = sigelt.current_type if elttype in DBUS.basic_to_ctypes : result = BasicType(TYPE(elttype)) elif elttype == DBUS.TYPE_ARRAY : if sigelt.element_type == DBUS.TYPE_DICT_ENTRY : subsig = sigelt.recurse() subsubsig = subsig.recurse() keytype = process_subsig(next(subsubsig)) valuetype = process_subsig(next(subsubsig)) result = DictType(keytype, valuetype) else : subsig = sigelt.recurse() result = ArrayType(process_subsig(next(subsig))) #end if elif elttype == DBUS.TYPE_STRUCT : result = [] subsig = sigelt.recurse() for subelt in subsig : result.append(process_subsig(subelt)) #end for result = StructType(*result) elif elttype == DBUS.TYPE_VARIANT : result = VariantType() else : raise RuntimeError("unrecognized type %s" % bytes((elttype,))) #end if return \ result #end process_subsig #begin parse_signature if isinstance(signature, (tuple, list)) : if not all(isinstance(t, Type) for t in signature) : raise TypeError("signature is list containing non-Type objects") #end if result = signature elif isinstance(signature, Type) : result = [signature] elif isinstance(signature, str) : signature_validate(signature) result = [] if len(signature) != 0 : sigiter = SignatureIter.init(signature) for elt in sigiter : result.append(process_subsig(elt)) #end for #end if else : raise TypeError("signature must be list or str") #end if return \ result #end parse_signature def parse_single_signature(signature) : result = parse_signature(signature) if len(result) != 1 : raise ValueError("only single type expected") #end if return \ result[0] #end parse_single_signature def unparse_signature(signature) : "converts a signature from parsed form to string form." signature = parse_signature(signature) if not isinstance(signature, (tuple, list)) : signature = [signature] #end if return \ DBUS.Signature("".join(t.signature for t in signature)) #end unparse_signature def signature_validate_single(signature, error = None) : "is signature a single valid type." error, my_error = _get_error(error) result = dbus.dbus_signature_validate_single(signature.encode(), error._dbobj) != 0 my_error.raise_if_set() return \ result #end signature_validate_single def type_is_valid(typecode) : return \ dbus.dbus_type_is_valid(typecode) != 0 #end type_is_valid def type_is_basic(typecode) : return \ dbus.dbus_type_is_basic(typecode) != 0 #end type_is_basic def type_is_container(typecode) : return \ dbus.dbus_type_is_container(typecode) != 0 #end type_is_container def type_is_fixed(typecode) : return \ dbus.dbus_type_is_fixed(typecode) != 0 #end type_is_fixed def type_is_fixed_array_elttype(typecode) : "is typecode suitable as the element type of a fixed_array." return \ type_is_fixed(typecode) and typecode != DBUS.TYPE_UNIX_FD #end type_is_fixed_array_elttype # syntax validation def validate_path(path, error = None) : error, my_error = _get_error(error) result = dbus.dbus_validate_path(path.encode(), error._dbobj) != 0 my_error.raise_if_set() return \ result #end validate_path def valid_path(path) : "returns path if valid, raising appropriate exception if not." validate_path(path) return \ path #end valid_path def split_path(path) : "convenience routine for splitting a path into a list of components." if isinstance(path, (tuple, list)) : result = path # assume already split elif path == "/" : result = [] else : if not path.startswith("/") or path.endswith("/") : raise DBusError(DBUS.ERROR_INVALID_ARGS, "invalid path %s" % repr(path)) #end if result = path.split("/")[1:] #end if return \ result #end split_path def unsplit_path(path) : path = split_path(path) if len(path) != 0 : result = DBUS.ObjectPath("".join("/" + component for component in path)) else : result = "/" #end if return \ result #end unsplit_path def validate_interface(name, error = None) : error, my_error = _get_error(error) result = dbus.dbus_validate_interface(name.encode(), error._dbobj) != 0 my_error.raise_if_set() return \ result #end validate_interface def valid_interface(name) : "returns name if it is a valid interface name, raising appropriate exception if not." validate_interface(name) return \ name #end valid_interface def validate_member(name, error = None) : error, my_error = _get_error(error) result = dbus.dbus_validate_member(name.encode(), error._dbobj) != 0 my_error.raise_if_set() return \ result #end validate_member def valid_member(name) : "returns name if it is a valid member name, raising appropriate exception if not." validate_member(name) return \ name #end valid_member def validate_error_name(name, error = None) : error, my_error = _get_error(error) result = dbus.dbus_validate_error_name(name.encode(), error._dbobj) != 0 my_error.raise_if_set() return \ result #end validate_error_name def valid_error_name(name) : "returns name if it is a valid error name, raising appropriate exception if not." validate_error_name(name) return \ name #end valid_error_name def validate_bus_name(name, error = None) : error, my_error = _get_error(error) result = dbus.dbus_validate_bus_name(name.encode(), error._dbobj) != 0 my_error.raise_if_set() return \ result #end validate_bus_name def valid_bus_name(name) : "returns name if it is a valid bus name, raising appropriate exception if not." validate_bus_name(name) return \ name #end valid_bus_name def validate_utf8(alleged_utf8, error = None) : "alleged_utf8 must be null-terminated bytes." error, my_error = _get_error(error) result = dbus.dbus_validate_utf8(alleged_utf8, error._dbobj) != 0 my_error.raise_if_set() return \ result #end validate_utf8 def valid_utf8(alleged_utf8) : "returns alleged_utf8 if it is a valid utf-8 bytes value, raising" \ " appropriate exception if not." validate_utf8(alleged_utf8) return \ alleged_utf8 #end valid_utf8 #+ # Introspection representation #- class _TagCommon : def get_annotation(self, name) : "returns the value of the annotation with the specified name, or None" \ " if none could be found" annots = iter(self.annotations) while True : annot = next(annots, None) if annot == None : result = None break #end if if annot.name == name : result = annot.value break #end if #end while return \ result #end get_annotation @property def is_deprecated(self) : "is this interface/method/signal etc deprecated." return \ self.get_annotation("org.freedesktop.DBus.Deprecated") == "true" #end is_deprecated def __repr__(self) : celf = type(self) return \ ( "%s(%s)" % ( celf.__name__, ", ".join ( "%s = %s" % (name, repr(getattr(self, name))) for name in celf.__slots__ ), ) ) #end __repr__ #end _TagCommon class Introspection(_TagCommon) : "high-level wrapper for the DBUS.INTERFACE_INTROSPECTABLE interface." __slots__ = ("name", "interfaces", "nodes", "annotations") tag_name = "node" tag_attrs = ("name",) tag_attrs_optional = {"name"} class DIRECTION(enum.Enum) : "argument direction." IN = "in" # client to server OUT = "out" # server to client #end DIRECTION class ACCESS(enum.Enum) : "property access." READ = "read" WRITE = "write" READWRITE = "readwrite" #end ACCESS class PROP_CHANGE_NOTIFICATION(enum.Enum) : "how/if a changed property emits a notification signal." NEW_VALUE = "true" # notification includes new value INVALIDATES = "invalidates" # notification does not include new value CONST = "const" # property shouldn’t change NONE = "false" # does not notify changes #end PROP_CHANGE_NOTIFICATION class Annotation(_TagCommon) : __slots__ = ("name", "value") tag_name = "annotation" tag_attrs = ("name", "value") tag_elts = {} def __init__(self, name, value) : self.name = name self.value = value #end __init__ #end Annotation def _get_annotations(annotations) : # common validation of annotations arguments. if not all(isinstance(a, Introspection.Annotation) for a in annotations) : raise TypeError("annotations must be Annotation instances") #end if return \ annotations #end _get_annotations class Interface(_TagCommon) : __slots__ = ("name", "methods", "signals", "properties", "annotations") tag_name = "interface" tag_attrs = ("name",) class Method(_TagCommon) : __slots__ = ("name", "args", "annotations") tag_name = "method" tag_attrs = ("name",) class Arg(_TagCommon) : __slots__ = ("name", "type", "direction", "annotations") tag_name = "arg" tag_attrs = ("name", "type", "direction") tag_attrs_optional = {"name"} tag_elts = {} attr_convert = {} # {"direction" : Introspection.DIRECTION} assigned below def __init__(self, *, name = None, type, direction, annotations = ()) : if not isinstance(direction, Introspection.DIRECTION) : raise TypeError("direction must be an Introspection.DIRECTION.xxx enum") #end if self.name = name self.type = parse_single_signature(type) self.direction = direction self.annotations = Introspection._get_annotations(annotations) #end __init__ #end Arg tag_elts = {"args" : Arg} def __init__(self, name, args = (), annotations = ()) : if not all(isinstance(a, self.Arg) for a in args) : raise TypeError("args must be Arg instances") #end if self.name = name self.args = list(args) self.annotations = Introspection._get_annotations(annotations) #end __init__ @property def in_signature(self) : return \ list(a.type for a in self.args if a.direction == Introspection.DIRECTION.IN) #end in_signature @property def out_signature(self) : return \ list \ (a.type for a in self.args if a.direction == Introspection.DIRECTION.OUT) #end out_signature @property def expect_reply(self) : "will there be replies to this request method." return \ self.get_annotation("org.freedesktop.DBus.Method.NoReply") != "true" #end expect_reply #end Method class Signal(_TagCommon) : __slots__ = ("name", "args", "annotations") tag_name = "signal" tag_attrs = ("name",) class Arg(_TagCommon) : __slots__ = ("name", "type", "direction", "annotations") tag_name = "arg" tag_attrs = ("name", "type", "direction") tag_attrs_optional = {"name", "direction"} tag_elts = {} attr_convert = {} # {"direction" : Introspection.DIRECTION} assigned below def __init__(self, *, name = None, type, direction = None, annotations = ()) : if direction != None and direction != Introspection.DIRECTION.OUT : raise ValueError("direction can only be Introspection.DIRECTION.OUT") #end if self.name = name self.type = parse_single_signature(type) self.direction = direction self.annotations = Introspection._get_annotations(annotations) #end __init__ #end Arg tag_elts = {"args" : Arg} def __init__(self, name, args = (), annotations = ()) : if not all(isinstance(a, self.Arg) for a in args) : raise TypeError("args must be Arg instances") #end if self.name = name self.args = list(args) self.annotations = Introspection._get_annotations(annotations) #end __init__ @property def in_signature(self) : return \ list(a.type for a in self.args) #end in_signature #end Signal class Property(_TagCommon) : __slots__ = ("name", "type", "access", "annotations") tag_name = "property" tag_attrs = ("name", "type", "access") tag_elts = {} attr_convert = {} # {"access" : Introspection.ACCESS} assigned below def __init__(self, name, type, access, annotations = ()) : if not isinstance(access, Introspection.ACCESS) : raise TypeError("access must be an Introspection.ACCESS.xxx enum") #end if self.name = name self.type = parse_single_signature(type) self.access = access self.annotations = Introspection._get_annotations(annotations) #end __init__ #end Property tag_elts = {"methods" : Method, "signals" : Signal, "properties" : Property} def __init__(self, name, methods = (), signals = (), properties = (), annotations = ()) : if not all(isinstance(m, self.Method) for m in methods) : raise TypeError("methods must be Method instances") #end if if not all(isinstance(s, self.Signal) for s in signals) : raise TypeError("signals must be Signal instances") #end if if not all(isinstance(p, self.Property) for p in properties) : raise TypeError("properties must be Property instances") #end if self.name = name self.methods = list(methods) self.signals = list(signals) self.properties = list(properties) self.annotations = Introspection._get_annotations(annotations) #end __init__ @property def methods_by_name(self) : "returns a dict associating all the methods with their names." return \ dict((method.name, method) for method in self.methods) #end methods_by_name @property def signals_by_name(self) : "returns a dict associating all the signals with their names." return \ dict((signal.name, signal) for signal in self.signals) #end signals_by_name @property def properties_by_name(self) : "returns a dict associating all the properties with their names." return \ dict((prop.name, prop) for prop in self.properties) #end properties_by_name #end Interface Interface.Method.Arg.attr_convert["direction"] = DIRECTION Interface.Signal.Arg.attr_convert["direction"] = lambda x : (lambda : None, lambda : Introspection.DIRECTION(x))[x != None]() Interface.Property.attr_convert["access"] = ACCESS class StubInterface(_TagCommon) : "use this as a replacement for an Interface that you don’t want" \ " to see expanded, e.g. if it has already been seen." __slots__ = ("name", "annotations") tag_name = "interface" tag_attrs = ("name",) tag_elts = {} def __init__(self, name) : self.name = name self.annotations = () #end __init__ #end StubInterface class Node(_TagCommon) : __slots__ = ("name", "interfaces", "nodes", "annotations") tag_name = "node" tag_attrs = ("name",) def __init__(self, name, interfaces = (), nodes = (), annotations = ()) : if not all(isinstance(i, (Introspection.Interface, Introspection.StubInterface)) for i in interfaces) : raise TypeError("interfaces must be Interface or StubInterface instances") #end if if not all(isinstance(n, Introspection.Node) for n in nodes) : raise TypeError("nodes must be Node instances") #end if self.name = name self.interfaces = interfaces self.nodes = nodes self.annotations = Introspection._get_annotations(annotations) #end __init__ @property def interfaces_by_name(self) : "returns a dict associating all the interfaces with their names." return \ dict((iface.name, iface) for iface in self.interfaces) #end interfaces_by_name @property def nodes_by_name(self) : "returns a dict associating all the child nodes with their names." return \ dict((node.name, node) for node in self.nodes) #end nodes_by_name #end Node Node.tag_elts = {"interfaces" : Interface, "nodes" : Node} tag_elts = {"interfaces" : Interface, "nodes" : Node} def __init__(self, name = None, interfaces = (), nodes = (), annotations = ()) : if not all(isinstance(i, self.Interface) for i in interfaces) : raise TypeError("interfaces must be Interface instances") #end if if not all(isinstance(n, self.Node) for n in nodes) : raise TypeError("nodes must be Node instances") #end if self.name = name self.interfaces = list(interfaces) self.nodes = list(nodes) self.annotations = Introspection._get_annotations(annotations) #end __init__ @property def interfaces_by_name(self) : "returns a dict associating all the interfaces with their names." return \ dict((iface.name, iface) for iface in self.interfaces) #end interfaces_by_name @property def nodes_by_name(self) : "returns a dict associating all the nodes with their names." return \ dict((node.name, node) for node in self.nodes) #end nodes_by_name @classmethod def parse(celf, s) : "generates an Introspection tree from the given XML string description." def from_string_elts(celf, attrs, tree) : elts = dict((k, attrs[k]) for k in attrs) child_tags = dict \ ( (childclass.tag_name, childclass) for childclass in tuple(celf.tag_elts.values()) + (Introspection.Annotation,) ) children = [] for child in tree : if child.tag not in child_tags : raise KeyError("unrecognized tag %s" % child.tag) #end if childclass = child_tags[child.tag] childattrs = {} for attrname in childclass.tag_attrs : if hasattr(childclass, "tag_attrs_optional") and attrname in childclass.tag_attrs_optional : childattrs[attrname] = child.attrib.get(attrname, None) else : if attrname not in child.attrib : raise ValueError("missing %s attribute for %s tag" % (attrname, child.tag)) #end if childattrs[attrname] = child.attrib[attrname] #end if #end for if hasattr(childclass, "attr_convert") : for attr in childclass.attr_convert : if attr in childattrs : childattrs[attr] = childclass.attr_convert[attr](childattrs[attr]) #end if #end for #end if children.append(from_string_elts(childclass, childattrs, child)) #end for for child_tag, childclass in tuple(celf.tag_elts.items()) + ((), (("annotations", Introspection.Annotation),))[tree.tag != "annotation"] : for child in children : if isinstance(child, childclass) : if child_tag not in elts : elts[child_tag] = [] #end if elts[child_tag].append(child) #end if #end for #end for return \ celf(**elts) #end from_string_elts #begin parse tree = XMLElementTree.fromstring(s) assert tree.tag == "node", "root of introspection tree must be tag" return \ from_string_elts(Introspection, {}, tree) #end parse def unparse(self, indent_step = 4, max_linelen = 72) : "returns an XML string description of this Introspection tree." out = io.StringIO() def to_string(obj, indent) : tag_name = obj.tag_name attrs = [] for attrname in obj.tag_attrs : attr = getattr(obj, attrname) if attr != None : if isinstance(attr, enum.Enum) : attr = attr.value elif isinstance(attr, Type) : attr = unparse_signature(attr) elif not isinstance(attr, str) : raise TypeError("unexpected attribute type %s for %s" % (type(attr).__name__, repr(attr))) #end if attrs.append("%s=%s" % (attrname, quote_xml_attr(attr))) #end if #end for has_elts = \ ( sum ( len(getattr(obj, attrname)) for attrname in tuple(obj.tag_elts.keys()) + ((), ("annotations",)) [not isinstance(obj, Introspection.Annotation)] ) != 0 ) out.write(" " * indent + "<" + tag_name) if ( max_linelen != None and indent + len(tag_name) + sum((len(s) + 1) for s in attrs) + 2 + int(has_elts) > max_linelen ) : out.write("\n") for attr in attrs : out.write(" " * (indent + indent_step)) out.write(attr) out.write("\n") #end for out.write(" " * indent) else : for attr in attrs : out.write(" ") out.write(attr) #end for #end if if not has_elts : out.write("/") #end if out.write(">\n") if has_elts : for attrname in sorted(obj.tag_elts.keys()) + ["annotations"] : for elt in getattr(obj, attrname) : to_string(elt, indent + indent_step) #end for #end for out.write(" " * indent + "\n") #end if #end to_string #begin unparse out.write(DBUS.INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE) out.write("\n") for elt in self.interfaces : to_string(elt, indent_step) #end for for elt in self.nodes : to_string(elt, indent_step) #end for out.write("\n") return \ out.getvalue() #end unparse #end Introspection del _TagCommon #+ # Standard interfaces #- standard_interfaces = \ { DBUS.INTERFACE_PEER : # note implementation of this is hard-coded inside libdbus Introspection.Interface ( name = DBUS.INTERFACE_PEER, methods = [ Introspection.Interface.Method(name = "Ping"), Introspection.Interface.Method ( name = "GetMachineId", args = [ Introspection.Interface.Method.Arg ( name = "machine_uuid", type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.OUT, ), ] ), ], ), DBUS.INTERFACE_LOCAL : # note implementation of this is hard-coded inside, and specific to, libdbus Introspection.Interface ( name = DBUS.INTERFACE_LOCAL, signals = [ Introspection.Interface.Signal(name = "Disconnected"), # auto-generated by libdbus with path = DBUS.PATH_LOCAL # when connection is closed; cannot be explicitly sent by # clients. Documented here: # ], ), DBUS.INTERFACE_DBUS : Introspection.Interface ( name = DBUS.INTERFACE_DBUS, methods = [ Introspection.Interface.Method ( name = "Hello", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.OUT, ), # returned unique name ] ), Introspection.Interface.Method ( name = "RequestName", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), # name Introspection.Interface.Method.Arg ( type = BasicType(TYPE.UINT32), direction = Introspection.DIRECTION.IN, ), # flags DBUS.NAME_FLAG_xxx Introspection.Interface.Method.Arg ( type = BasicType(TYPE.UINT32), direction = Introspection.DIRECTION.OUT, ), # result DBUS.REQUEST_NAME_REPLY_xxx ] ), Introspection.Interface.Method ( name = "ReleaseName", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( type = BasicType(TYPE.UINT32), direction = Introspection.DIRECTION.OUT, ), # result DBUS.RELEASE_NAME_REPLY_xxx ] ), Introspection.Interface.Method ( name = "StartServiceByName", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), # name Introspection.Interface.Method.Arg ( type = BasicType(TYPE.UINT32), direction = Introspection.DIRECTION.IN, ), # flags (currently unused) Introspection.Interface.Method.Arg ( type = BasicType(TYPE.UINT32), direction = Introspection.DIRECTION.OUT, ), # result DBUS.START_REPLY_xxx ] ), Introspection.Interface.Method ( name = "UpdateActivationEnvironment", args = [ Introspection.Interface.Method.Arg ( type = DictType ( keytype = BasicType(TYPE.STRING), valuetype = BasicType(TYPE.STRING) ), direction = Introspection.DIRECTION.IN, ), # environment ] ), Introspection.Interface.Method ( name = "NameHasOwner", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), # name Introspection.Interface.Method.Arg ( type = BasicType(TYPE.BOOLEAN), direction = Introspection.DIRECTION.OUT, ), ] ), Introspection.Interface.Method ( name = "ListNames", args = [ Introspection.Interface.Method.Arg ( type = ArrayType(BasicType(TYPE.STRING)), direction = Introspection.DIRECTION.OUT, ), ] ), Introspection.Interface.Method ( name = "ListActivatableNames", args = [ Introspection.Interface.Method.Arg ( type = ArrayType(BasicType(TYPE.STRING)), direction = Introspection.DIRECTION.OUT, ), ] ), Introspection.Interface.Method ( name = "AddMatch", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), ] ), Introspection.Interface.Method ( name = "RemoveMatch", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), ] ), Introspection.Interface.Method ( name = "GetNameOwner", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.OUT, ), ] ), Introspection.Interface.Method ( name = "ListQueuedOwners", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( type = ArrayType(BasicType(TYPE.STRING)), direction = Introspection.DIRECTION.OUT, ), ] ), Introspection.Interface.Method ( name = "GetConnectionUnixUser", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( type = BasicType(TYPE.UINT32), direction = Introspection.DIRECTION.OUT, ), ] ), Introspection.Interface.Method ( name = "GetConnectionUnixProcessID", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( type = BasicType(TYPE.UINT32), direction = Introspection.DIRECTION.OUT, ), ] ), Introspection.Interface.Method ( name = "GetAdtAuditSessionData", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( type = ArrayType(BasicType(TYPE.BYTE)), direction = Introspection.DIRECTION.OUT, ), ] ), Introspection.Interface.Method ( name = "GetConnectionSELinuxSecurityContext", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( type = ArrayType(BasicType(TYPE.BYTE)), direction = Introspection.DIRECTION.OUT, ), ] ), Introspection.Interface.Method ( name = "ReloadConfig", ), Introspection.Interface.Method ( name = "GetId", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.OUT, ), ] ), Introspection.Interface.Method ( name = "GetConnectionCredentials", args = [ Introspection.Interface.Method.Arg ( type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( type = DictType(BasicType(TYPE.STRING), VariantType()), direction = Introspection.DIRECTION.OUT, ), ] ), ], signals = [ Introspection.Interface.Signal ( name = "NameOwnerChanged", args = [ Introspection.Interface.Signal.Arg ( type = BasicType(TYPE.STRING), ), # bus name Introspection.Interface.Signal.Arg ( type = BasicType(TYPE.STRING), ), # old owner, empty if none Introspection.Interface.Signal.Arg ( type = BasicType(TYPE.STRING), ), # new owner, empty if none ] ), Introspection.Interface.Signal ( name = "NameLost", # sent to previous owner of name args = [ Introspection.Interface.Signal.Arg ( type = BasicType(TYPE.STRING), ), ] ), Introspection.Interface.Signal ( name = "NameAcquired", # sent to new owner of name args = [ Introspection.Interface.Signal.Arg ( type = BasicType(TYPE.STRING), ), ] ), ], ), DBUS.INTERFACE_INTROSPECTABLE : Introspection.Interface ( name = DBUS.INTERFACE_INTROSPECTABLE, methods = [ Introspection.Interface.Method ( name = "Introspect", args = [ Introspection.Interface.Method.Arg ( name = "data", type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.OUT, ), ] ), ], ), DBUS.INTERFACE_PROPERTIES : Introspection.Interface ( name = DBUS.INTERFACE_PROPERTIES, methods = [ Introspection.Interface.Method ( name = "Get", args = [ Introspection.Interface.Method.Arg ( name = "interface_name", type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( name = "property_name", type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( name = "value", type = VariantType(), direction = Introspection.DIRECTION.OUT, ), ], ), Introspection.Interface.Method ( name = "Set", args = [ Introspection.Interface.Method.Arg ( name = "interface_name", type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( name = "property_name", type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( name = "value", type = VariantType(), direction = Introspection.DIRECTION.IN, ), ], ), Introspection.Interface.Method ( name = "GetAll", args = [ Introspection.Interface.Method.Arg ( name = "interface_name", type = BasicType(TYPE.STRING), direction = Introspection.DIRECTION.IN, ), Introspection.Interface.Method.Arg ( name = "values", type = DictType(BasicType(TYPE.STRING), VariantType()), direction = Introspection.DIRECTION.OUT, ), ], ), ], signals = [ Introspection.Interface.Signal ( name = "PropertiesChanged", args = [ Introspection.Interface.Signal.Arg ( name = "interface_name", type = BasicType(TYPE.STRING), ), Introspection.Interface.Signal.Arg ( name = "changed_properties", type = DictType(BasicType(TYPE.STRING), VariantType()), ), Introspection.Interface.Signal.Arg ( name = "invalidated_properties", type = ArrayType(BasicType(TYPE.STRING)), ), ], ), ], ), DBUS.INTERFACE_MONITORING : Introspection.Interface ( name = DBUS.INTERFACE_MONITORING, methods = [ Introspection.Interface.Method ( name = "BecomeMonitor", args = [ Introspection.Interface.Method.Arg ( type = ArrayType(BasicType(TYPE.STRING)), direction = Introspection.DIRECTION.IN, ), # match rules to add to the connection Introspection.Interface.Method.Arg ( type = BasicType(TYPE.UINT32), # match rules to add to the connection direction = Introspection.DIRECTION.IN, ), # flags (currently unused) ], ), ], ), DBUSX.INTERFACE_OBJECT_MANAGER : Introspection.Interface ( name = DBUSX.INTERFACE_OBJECT_MANAGER, methods = [ Introspection.Interface.Method ( name = "GetManagedObjects", args = [ Introspection.Interface.Method.Arg ( name = "objpath_interfaces_and_properties", type = DictType ( BasicType(TYPE.OBJECT_PATH), DictType ( BasicType(TYPE.STRING), # interface DictType(BasicType(TYPE.STRING), VariantType()) # properties and values ) ), direction = Introspection.DIRECTION.OUT, ), ], ), ], signals = [ Introspection.Interface.Signal ( name = "InterfacesAdded", args = [ Introspection.Interface.Signal.Arg ( name = "object_path", type = BasicType(TYPE.OBJECT_PATH), ), Introspection.Interface.Signal.Arg ( name = "interfaces_and_properties", type = DictType ( BasicType(TYPE.STRING), # interface added/changed DictType(BasicType(TYPE.STRING), VariantType()) # properties and values added ), ), ], ), Introspection.Interface.Signal ( name = "InterfacesRemoved", args = [ Introspection.Interface.Signal.Arg ( name = "object_path", type = BasicType(TYPE.OBJECT_PATH), ), Introspection.Interface.Signal.Arg ( name = "interfaces", type = ArrayType(BasicType(TYPE.STRING)), # interfaces removed ), ], ), ], ), } #+ # Cleanup #- def _atexit() : # disable all __del__ methods at process termination to avoid segfaults for cls in Connection, Server, PreallocatedSend, Message, PendingCall, Error, AddressEntries : delattr(cls, "__del__") #end for #end _atexit atexit.register(_atexit) del _atexit dbussy-1.3/ravel.py000066400000000000000000004156061366133523600143750ustar00rootroot00000000000000""" Simplified higher-level Python binding for D-Bus on top of dbussy. Provides a framework for dispatching method and signal calls, and also for on-the-fly invocation of method calls in the server from the client using proxy objects, all with the option of running via an asyncio event loop. """ #+ # Copyright 2017-2020 Lawrence D'Oliveiro . # Licensed under the GNU Lesser General Public License v2.1 or later. #- import enum from weakref import \ ref as weak_ref, \ WeakValueDictionary import asyncio import atexit import dbussy as dbus from dbussy import \ DBUS, \ DBUSX, \ Introspection #+ # High-level bus connection #- class ErrorReturn(Exception) : "Dispatch handlers can raise this to report an error that will be returned" \ " in a message back to the other end of the connection." def __init__(self, name, message) : self.args = (name, message) #end __init__ def as_error(self) : "fills in and returns an Error object that reports the specified error name and message." result = dbus.Error.init() result.set(self.args[0], self.args[1]) return \ result #end as_error #end ErrorReturn def _signal_key(fallback, interface, name) : # constructs a key for the signal-listener dictionary from the # given args. return \ (fallback, interface, name) #end _signal_key def _signal_rule(path, fallback, interface, name) : # constructs a D-Bus match rule from the given args. return \ dbus.format_rule \ ( { "type" : "signal", ("path", "path_namespace")[fallback] : dbus.unsplit_path(path), "interface" : interface, "member" : name, } ) #end _signal_rule class _DispatchNode : __slots__ = ("children", "signal_listeners") class _Interface : __slots__ = ("interface", "fallback", "listening") def __init__(self, interface, fallback) : self.interface = interface self.fallback = fallback self.listening = set() # of match rule strings #end __init #end _Interface def __init__(self) : self.children = {} # dict of path component => _DispatchNode self.signal_listeners = {} # dict of _signal_key() => list of functions #end __init__ @property def is_empty(self) : return \ ( len(self.children) == 0 and len(self.signal_listeners) == 0 ) #end _is_empty #end _DispatchNode class _ClientDispatchNode(_DispatchNode) : __slots__ = () def __init__(self, bus) : # bus arg ignored, only accepted for compatibility with _ServerDispatchNode super().__init__() #end __init__ #end _ClientDispatchNode class _ServerDispatchNode(_DispatchNode) : __slots__ = ("interfaces", "user_data") class _UserDataDict(dict) : # for holding user data, does automatic cleaning up of object # tree as items are removed. __slots__ = ("_ravel_bus",) def __init__(self, bus) : super().__init__() self._ravel_bus = weak_ref(bus) #end __init def __delitem__(self, key) : super().__delitem__(key) if len(self) == 0 : bus = self._ravel_bus() assert bus != None, "parent Connection has gone" bus._trim_dispatch(True) #end if #end __delitem__ #end _UserDataDict def __init__(self, bus) : super().__init__() self.interfaces = {} # dict of interface name => _Interface self.user_data = self._UserDataDict(bus) # for caller use #end __init__ @property def is_empty(self) : return \ ( super().is_empty and len(self.interfaces) == 0 and len(self.user_data) == 0 ) #end is_empty #end _ServerDispatchNode class _UserData : __slots__ = ("_w_conn",) def __init__(self, conn) : self._w_conn = weak_ref(conn) #end __init__ @property def conn(self) : result = self._w_conn() assert result != None return \ result #end conn def __getitem__(self, path) : node = self.conn._get_dispatch_node(path, True, True) return \ node.user_data #end __getitem__ #end _UserData class Connection(dbus.TaskKeeper) : "higher-level wrapper around dbussy.Connection. Do not instantiate directly: use" \ " the session_bus() and system_bus() calls in this module, or obtain from accepting" \ " connections on a Server().\n" \ "\n" \ "This class provides various functions, some more suited to client-side use and" \ " some more suitable to the server side. Allows for registering of @interface()" \ " classes for automatic dispatching of method calls at appropriate points in" \ " the object hierarchy." __slots__ = \ ( "connection", "notify_delay", "user_data", "bus_names_acquired", "bus_names_pending", "_client_dispatch", "_server_dispatch", "_managed_objects", "_registered_bus_names_listeners", "_bus_name_acquired_action", "_bus_name_acquired_action_arg", "_bus_name_lost_action", "_bus_name_lost_action_arg", "_props_changed", "_objects_added", "_objects_removed", ) # to forestall typos _instances = WeakValueDictionary() def __new__(celf, connection) : # always return the same Connection for the same dbus.Connection. if not isinstance(connection, dbus.Connection) : raise TypeError("connection must be a Connection") #end if self = celf._instances.get(connection) if self == None : self = super().__new__(celf) super()._init(self) self.connection = connection self.loop = connection.loop self.notify_delay = 0 self._client_dispatch = None # for signal listeners self._server_dispatch = None # for registered classes that field method calls self._managed_objects = None unique_name = connection.bus_unique_name assert unique_name != None, "connection not yet registered" self.bus_names_acquired = {unique_name} self.bus_names_pending = set() self._registered_bus_names_listeners = False self._props_changed = None self._objects_added = None self._objects_removed = None self._bus_name_acquired_action = None self._bus_name_acquired_action_arg = None self._bus_name_lost_action = None self._bus_name_lost_action_arg = None self.user_data = _UserData(self) celf._instances[connection] = self for interface in \ ( PeerStub, IntrospectionHandler, PropertyHandler, ) \ : self.register \ ( path = "/", interface = interface(), fallback = True ) #end for #end if return \ self #end __new__ def __del__(self) : def remove_listeners(level, path) : for node, child in level.children.items() : remove_listeners(child, path + [node]) #end for for interface in level.interfaces.values() : for rulestr in interface.listening : ignore = dbus.Error.init() self.connection.bus_remove_match(rulestr, ignore) #end for #end for for rulekey in level.signal_listeners : fallback, interface, name = rulekey ignore = dbus.Error.init() self.connection.bus_remove_match \ ( _signal_rule(path, fallback, interface, name), ignore ) #end for #end remove_listeners #begin __del__ if self._client_dispatch != None : remove_listeners(self._client_dispatch, []) #end if #end __del__ def attach_asyncio(self, loop = None) : "attaches this Connection object to an asyncio event loop. If none is" \ " specified, the default event loop (as returned from asyncio.get_event_loop()" \ " is used." self.connection.attach_asyncio(loop) self.loop = self.connection.loop return \ self #end attach_asyncio @staticmethod def _bus_name_acquired(conn, msg, w_self) : # internal callback which keeps track of bus names and dispatches # to user-specified action. self = w_self() assert self != None bus_name = msg.expect_objects("s")[0] self.bus_names_pending.discard(bus_name) if bus_name not in self.bus_names_acquired : self.bus_names_acquired.add(bus_name) if self._bus_name_acquired_action != None : result = self._bus_name_acquired_action(self, bus_name, self._bus_name_acquired_action_arg) if asyncio.iscoroutine(result) : self.create_task(result) #end if #end if #end if #end _bus_name_acquired @staticmethod def _bus_name_lost(conn, msg, w_self) : # internal callback which keeps track of bus names and dispatches # to user-specified action. self = w_self() assert self != None bus_name = msg.expect_objects("s")[0] self.bus_names_pending.discard(bus_name) if bus_name in self.bus_names_acquired : self.bus_names_acquired.remove(bus_name) if self._bus_name_lost_action != None : result = self._bus_name_lost_action(self, bus_name, self._bus_name_lost_action_arg) if asyncio.iscoroutine(result) : self.create_task(result) #end if #end if #end if #end _bus_name_lost def set_bus_name_acquired_action(self, action, action_arg) : "sets the action (if not None) to be called on receiving a bus-name-acquired" \ " signal. action is invoked as\n" \ "\n" \ " action(conn, bus_name, action_arg)\n" \ "\n" \ "where conn is the Connection object and bus_name is the name." self._bus_name_acquired_action = action self._bus_name_acquired_action_arg = action_arg #end set_bus_name_acquired_action def set_bus_name_lost_action(self, action, action_arg) : "sets the action (if not None) to be called on receiving a bus-name-lost" \ " signal. action is invoked as\n" \ "\n" \ " action(conn, bus_name, action_arg)\n" \ "\n" \ "where conn is the Connection object and bus_name is the name." self._bus_name_lost_action = action self._bus_name_lost_action_arg = action_arg #end set_bus_name_lost_action def request_name(self, bus_name, flags) : "registers a bus name." if not self._registered_bus_names_listeners : self.connection.bus_add_match_action \ ( rule = "type=signal,interface=org.freedesktop.DBus,member=NameAcquired", func = self._bus_name_acquired, user_data = weak_ref(self) ) self.connection.bus_add_match_action \ ( rule = "type=signal,interface=org.freedesktop.DBus,member=NameLost", func = self._bus_name_lost, user_data = weak_ref(self) ) self._registered_bus_names_listeners = True #end if return \ self.connection.bus_request_name(bus_name, flags) #end request_name async def request_name_async(self, bus_name, flags, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "registers a bus name." assert self.loop != None, "no event loop to attach coroutine to" if not self._registered_bus_names_listeners : self._registered_bus_names_listeners = True # do first in case of reentrant call await self.connection.bus_add_match_action_async \ ( rule = "type=signal,interface=org.freedesktop.DBus,member=NameAcquired", func = self._bus_name_acquired, user_data = weak_ref(self) ) await self.connection.bus_add_match_action_async \ ( rule = "type=signal,interface=org.freedesktop.DBus,member=NameLost", func = self._bus_name_lost, user_data = weak_ref(self) ) #end if is_acquired = bus_name in self.bus_names_acquired is_pending = bus_name in self.bus_names_pending if not (is_acquired or is_pending) : self.bus_names_pending.add(bus_name) result = await self.connection.bus_request_name_async(bus_name, flags, error = error, timeout = timeout) if error != None and error.is_set or result != DBUS.REQUEST_NAME_REPLY_IN_QUEUE : self.bus_names_pending.discard(bus_name) #end if elif is_pending : result = DBUS.REQUEST_NAME_REPLY_IN_QUEUE else : result = DBUS.REQUEST_NAME_REPLY_ALREADY_OWNER #end if return \ result #end request_name_async def release_name(self, bus_name) : "releases a registered bus name." return \ self.connection.bus_release_name(bus_name) #end release_name async def release_name_async(self, bus_name, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "releases a registered bus name." assert self.loop != None, "no event loop to attach coroutine to" return \ await self.connection.bus_release_name_async(bus_name, error = error, timeout = timeout) #end release_name_async def _trim_dispatch(self, is_server) : # removes empty subtrees from the object tree. def trim_dispatch_node(level) : to_delete = set() for node, child in level.children.items() : trim_dispatch_node(child) if child.is_empty : to_delete.add(node) #end if #end for for node in to_delete : del level.children[node] #end for #end trim_dispatch_node #begin _trim_dispatch dispatch = (self._client_dispatch, self._server_dispatch)[is_server] if dispatch != None : trim_dispatch_node(dispatch) if dispatch.is_empty : if is_server : self._server_dispatch = None else : self._client_dispatch = None #end if #end if #end if #end _trim_dispatch def _get_dispatch_node(self, path, is_server, create_if) : # returns the appropriate _DispatchNode entry in the # client or server dispatch tree (depending on is_server) for # the specified path, or None if no such and not create_if. if create_if : if is_server and self._server_dispatch == None : self._server_dispatch = _ServerDispatchNode(self) elif not is_server and self._client_dispatch == None : self._client_dispatch = _ClientDispatchNode(self) #end if #end if level = (self._client_dispatch, self._server_dispatch)[is_server] DispatchNode = (_ClientDispatchNode, _ServerDispatchNode)[is_server] if level != None : levels = iter(dbus.split_path(path)) while True : component = next(levels, None) if component == None : break # found if level != None if component not in level.children : if not create_if : level = None break #end if level.children[component] = DispatchNode(self) #end if level = level.children[component] # search another step down the path #end while #end if return \ level #end _get_dispatch_node def _remove_matches(self, dispatch) : for rulestr in dispatch.listening : ignore = dbus.Error.init() self.connection.bus_remove_match(rulestr, ignore) #end for #end _remove_matches def register_additional_standard(self, **kwargs) : "registers additional standard interfaces that are not automatically" \ " installed at Connection creation time. Currently the only one is" \ " the object-manager interface, registered with\n" \ "\n" \ " «conn».register_additional_standard(managed_objects = True)\n" for key in kwargs : if kwargs[key] : if key == "managed_objects" : if self._managed_objects != None : raise asyncio.InvalidStateError \ ( "object manager interface already registered" ) #end if self.register \ ( path = "/", interface = ManagedObjectsHandler(), fallback = True ) self._managed_objects = {} else : raise TypeError("unrecognized argument keyword “%s”" % key) #end if #end if #end for return \ self #end register_additional_standard def register(self, path, fallback, interface, replace = True) : "for server-side use; registers the specified instance of an @interface()" \ " class for handling method calls on the specified path, and also on subpaths" \ " if fallback." if is_interface_instance(interface) : iface_type = type(interface) elif is_interface(interface) : # assume can instantiate without arguments iface_type = interface interface = iface_type() else : raise TypeError("interface must be an @interface() class or instance thereof") #end if if self._server_dispatch == None : self._server_dispatch = _ServerDispatchNode(self) self.connection.add_filter(_message_interface_dispatch, weak_ref(self)) #end if level = self._server_dispatch for component in dbus.split_path(path) : if component not in level.children : level.children[component] = _ServerDispatchNode(self) #end if level = level.children[component] #end for interface_name = iface_type._interface_name if interface_name in level.interfaces : entry = level.interfaces[interface_name] existing_kind = type(entry.interface)._interface_kind if not replace or existing_kind != iface_type._interface_kind : raise KeyError \ ( "already registered an interface named “%s” of kind %s" % (interface_name, existing_kind) ) #end if self._remove_matches(entry) #end if entry = _ServerDispatchNode._Interface(interface, fallback) if iface_type._interface_kind != INTERFACE.SERVER : signals = iface_type._interface_signals for name in signals : if not iface_type._interface_signals[name]._signal_info["stub"] : rulestr = _signal_rule(path, fallback, interface_name, name) self.connection.bus_add_match(rulestr) entry.listening.add(rulestr) #end if #end for #end for level.interfaces[interface_name] = entry #end register def unregister(self, path, interface = None) : "for server-side use; unregisters the specified interface class (or all" \ " registered interface classes, if None) from handling method calls on path." if interface != None and not is_interface(interface) : raise TypeError("interface must be None or an @interface() class") #end if if self._server_dispatch != None : level = self._server_dispatch levels = iter(dbus.split_path(path)) while True : component = next(levels, None) if component == None : if interface != None : interfaces = [level.interfaces[interface._interface_name]] else : interfaces = set(level.interfaces.keys()) #end if for iface_name in interfaces : self._remove_matches(level.interfaces[iface_name]) del level.interfaces[iface_name] #end for break #end if if component not in level.children : break level = level.children[component] #end while self._trim_dispatch(True) #end if #end unregister def listen_signal(self, path, fallback, interface, name, func) : "for client-side use; registers a callback which will be invoked when a" \ " signal is received for the specified path, interface and name." if not hasattr(func, "_signal_info") : raise TypeError("callback must have @signal() decorator applied") #end if signal_info = func._signal_info entry = self._get_dispatch_node(path, False, True) # Should I bother to check it matches a registered interface and # defined signal therein? # Also, should I pay any attention to signal_info["name"]? Perhaps # default if name arg is None? listeners = entry.signal_listeners rulekey = _signal_key(fallback, interface, name) if rulekey not in listeners : self.connection.bus_add_match(_signal_rule(path, fallback, interface, name)) listeners[rulekey] = [] #end if listeners[rulekey].append(func) #end listen_signal def unlisten_signal(self, path, fallback, interface, name, func) : "for client-side use; unregisters a previously-registered callback" \ " which would have been invoked when a signal is received for the" \ " specified path, interface and name." entry = self._get_dispatch_node(path, False, False) if entry != None : signal_listeners = entry.signal_listeners rulekey = _signal_key(fallback, interface, name) if rulekey in signal_listeners : listeners = signal_listeners[rulekey] try : listeners.pop(listeners.index(func)) except ValueError : pass #end try if len(listeners) == 0 : ignore = dbus.Error.init() self.connection.bus_remove_match \ ( _signal_rule(path, fallback, interface, name), ignore ) del signal_listeners[rulekey] # as a note to myself that I will need to call bus_add_match # if a new listener is added #end if #end if self._trim_dispatch(False) #end if #end unlisten_signal def listen_propchanged(self, path, fallback, interface, func) : "special case of Connection.listen_signal specifically for listening" \ " for properties-changed signals. The interface is ignored for now;" \ " your listener will have to check for matches on this itself." self.listen_signal \ ( path = path, fallback = fallback, interface = DBUS.INTERFACE_PROPERTIES, name = "PropertiesChanged", func = func, ) #end listen_propchanged def unlisten_propchanged(self, path, fallback, interface, func) : "special case of Connection.unlisten_signal specifically for listening" \ " for properties-changed signals. The interface is ignored for now;" \ " your listener has to check for matches on this itself." self.unlisten_signal \ ( path = path, fallback = fallback, interface = DBUS.INTERFACE_PROPERTIES, name = "PropertiesChanged", func = func, ) #end unlisten_propchanged def listen_objects_added(self, func) : self.listen_signal \ ( path = "/", fallback = True, interface = DBUSX.INTERFACE_OBJECT_MANAGER, name = "InterfacesAdded", func = func, ) #end listen_objects_added def unlisten_objects_added(self, func) : self.unlisten_signal \ ( path = "/", fallback = True, interface = DBUSX.INTERFACE_OBJECT_MANAGER, name = "InterfacesAdded", func = func, ) #end unlisten_objects_added def listen_objects_removed(self, func) : self.listen_signal \ ( path = "/", fallback = True, interface = DBUSX.INTERFACE_OBJECT_MANAGER, name = "InterfacesRemoved", func = func, ) #end listen_objects_removed def unlisten_objects_removed(self, func) : self.unlisten_signal \ ( path = "/", fallback = True, interface = DBUSX.INTERFACE_OBJECT_MANAGER, name = "InterfacesRemoved", func = func, ) #end unlisten_objects_removed def get_dispatch_interface(self, path, interface_name) : "returns the appropriate instance of a previously-registered interface" \ " class for handling calls to the specified interface name for the" \ " specified object path, or None if no such." fallback = None # to begin with level = self._server_dispatch if level != None : levels = iter(dbus.split_path(path)) while True : component = next(levels, None) if ( interface_name in level.interfaces and (level.interfaces[interface_name].fallback or component == None) ) : iface = level.interfaces[interface_name].interface else : iface = fallback #end if if ( component == None # reached bottom of path or component not in level.children # no handlers to be found further down path ) : break #end if fallback = iface level = level.children[component] # search another step down the path #end while else : iface = None #end if return \ iface #end get_dispatch_interface def _get_iface_entry(self, path, interface, name, namekind) : iface = self.get_dispatch_interface(path, interface) if iface == None : raise TypeError \ ( "no suitable interface %s for object %s" % (interface, dbus.unsplit_path(path)) ) #end if iface_type = type(iface) if iface_type._interface_kind == (INTERFACE.SERVER, INTERFACE.CLIENT)[namekind == "signal"] : raise TypeError \ ( "cannot send %s call from %s side" % (("method", "server"), ("signal", "client"))[namekind == "signal"] ) #end if lookup = getattr \ ( iface_type, { "method" : "_interface_methods", "signal" : "_interface_signals", "property" : "_interface_props", }[namekind] ) if name not in lookup : raise KeyError \ ( "name “%s” is not a %s of interface “%s”" % (name, namekind, interface) ) #end if return \ lookup[name] #end _get_iface_entry def _notify_props_changed(self) : # callback that is queued on the event loop to actually send the # properties-changed notification signals. if self._props_changed != None : done = set() now = self.loop.time() for key in self._props_changed : entry = self._props_changed[key] path, interface = key if entry["at"] <= now : self.send_signal \ ( path = path, interface = DBUS.INTERFACE_PROPERTIES, name = "PropertiesChanged", args = (interface, entry["changed"], sorted(entry["invalidated"])) ) done.add(key) #end if #end for for key in done : del self._props_changed[key] #end for if len(self._props_changed) == 0 : # all done for now self._props_changed = None # indicates I am not pending to be called any more else : # another notification waiting to be sent later next_time = min(entry["at"] for entry in self._props_changed.values()) self.loop.call_at(next_time, self._notify_props_changed) #end if #end if #end _notify_props_changed def _get_all_my_props(self, message, path, interface_name) : # utility wrapper that retrieves all property values for the specified # object path defined by the specified interface. Returns two results: # property values, and list of (propname, coroutine) tuples for async # propgetters. Could raise ErrorReturn if a propgetter does so. dispatch = self.get_dispatch_interface(path, interface_name) props = type(dispatch)._interface_props propnames = iter(props.keys()) properror = None propvalues = {} to_await = [] while True : propname = next(propnames, None) if propname == None : break propentry = props[propname] if "getter" in propentry : getter = getattr(dispatch, propentry["getter"].__name__) kwargs = {} for keyword_keyword, value in \ ( ("name_keyword", lambda : propname), ("connection_keyword", lambda : self.connection), ("message_keyword", lambda : message), ("path_keyword", lambda : path), ("bus_keyword", lambda : bus), ) \ : if getter._propgetter_info[keyword_keyword] != None : value = value() if value == None : raise ValueError \ ( "getter for prop “%s” expects a %s arg but" " no value supplied for that" % (propname, keyword_keyword) ) #end if kwargs[getter._propgetter_info[keyword_keyword]] = value #end if #end for propvalue = getter(**kwargs) # could raise ErrorReturn if asyncio.iscoroutine(propvalue) : if self.loop == None : raise TypeError \ ( "not expecting getter for prop “%s” to be coroutine" % propname ) #end if to_await.append((propname, propvalue)) #end if propvalues[propname] = (propentry["type"], propvalue) #end if #end for return \ propvalues, to_await #end _get_all_my_props def prop_changed(self, path, interface, propname, proptype, propvalue) : "indicates that a signal should be sent notifying of a change to the specified" \ " property of the specified object path in the specified interface. propvalue" \ " is either the new value to be included in the signal, or None to indicate" \ " that the property has merely become invalidated, and its new value needs" \ " to be obtained explicitly.\n" \ "\n" \ "If there is an event loop attached, then multiple calls to this with different" \ " properties on the same path and interface can be batched up into a single" \ " signal notification." assert (proptype != None) == (propvalue != None), \ "either specify both of proptype and propvalue, or neither" if self.loop != None : queue_task = False if self._props_changed == None : self._props_changed = {} queue_task = True #end if key = (dbus.unsplit_path(path), interface) if key not in self._props_changed : self._props_changed[key] = \ { "at" : self.loop.time() + self.notify_delay, "changed" : {}, "invalidated" : set(), } #end if if propvalue != None : self._props_changed[key]["changed"][propname] = (proptype, propvalue) else : self._props_changed[key]["invalidated"].add(propname) #end if if queue_task : if self.notify_delay != 0 : self.loop.call_later(self.notify_delay, self._notify_props_changed) else : self.loop.call_soon(self._notify_props_changed) #end if #end if else : # cannot batch them up--send message immediately changed = {} invalidated = [] if propvalue != None : changed[propname] = (proptype, propvalue) else : invalidated.append(propname) #end if self.send_signal \ ( path = path, interface = DBUS.INTERFACE_PROPERTIES, name = "PropertiesChanged", args = (interface, changed, invalidated) ) #end if #end prop_changed def _notify_objects_added(self) : # callback that is queued on the event loop to actually send the # objects-added notification signals. if self._objects_added != None : notify_again = None if self.loop != None : now = self.loop.time() else : now = None #end if paths_to_delete = set() for path in sorted(self._objects_added.keys()) : entry = self._objects_added[path] added = {} for interface, iface_entry in entry.items() : when = iface_entry["at"] if when == None or when <= now : added[interface] = iface_entry["props"] else : if notify_again == None : notify_again = when else : notify_again = min(notify_again, when) #end if #end if #end for if len(added) != 0 : self.send_signal \ ( path = path, interface = DBUSX.INTERFACE_OBJECT_MANAGER, name = "InterfacesAdded", args = (path, added) ) for interface in added : del entry[interface] #end for #end if if len(entry) == 0 : paths_to_delete.add(path) #end if #end for for path in paths_to_delete : del self._objects_added[path] #end for if len(self._objects_added) == 0 : self._objects_added = None #end if if notify_again != None : self.loop.call_later(notify_again - now, self._notify_objects_added) #end if #end if #end _notify_objects_added def _notify_objects_removed(self) : # callback that is queued on the event loop to actually send the # objects-removed notification signals. if self._objects_removed != None : notify_again = None if self.loop != None : now = self.loop.time() else : now = None #end if paths_to_delete = set() for path in sorted(self._objects_removed.keys()) : entry = self._objects_removed[path] removed = set() for interface in entry : when = entry[interface]["at"] if when == None or when <= now : removed.add(interface) else : if notify_again == None : notify_again = when else : notify_again = min(notify_again, when) #end if #end if #end for if len(removed) != 0 : self.send_signal \ ( path = path, interface = DBUSX.INTERFACE_OBJECT_MANAGER, name = "InterfacesRemoved", args = (path, sorted(removed)) ) for interface in removed : del entry[interface] #end for #end if if len(entry) == 0 : paths_to_delete.add(path) #end if #end for for path in paths_to_delete : del self._objects_removed[path] #end for if len(self._objects_removed) == 0 : self._objects_removed = None #end if if notify_again != None : self.loop.call_later(notify_again - now, self._notify_objects_removed) #end if #end if #end _notify_objects_removed def find_interfaces_for_object(self, path) : "returns a dict of interfaces, keyed by name, applicable to the" \ " given object path." level = self._server_dispatch result = {} if level != None : levels = iter(dbus.split_path(path)) while True : component = next(levels, None) for interface_name, interface in level.interfaces.items() : if component == None or interface.fallback : result[interface_name] = interface.interface # Note that a fallback entry might be replaced # by a more specific one further down the path. #end if #end for if ( component == None # reached bottom of path or component not in level.children # no handlers to be found further down path ) : break #end if level = level.children[component] # search another step down the path #end while #end if return \ result #end find_interfaces_for_object def object_added(self, path, interfaces_and_props = None) : "Call this to send an ObjectManager notification about the addition of" \ " the specified interfaces and property values to the specified object" \ " path. The ObjectManager interface must already have been registered on" \ " this Connection, by calling" \ " «conn».register_additional_standard(managed_objects = True)." added_entry = None to_await = [] queue_task = False notify_when = None def queue_notify(deferred) : if queue_task : if deferred : delay = notify_when - self.loop.time() else : delay = self.notify_delay #end if if delay > 0 : self.loop.call_later(delay, self._notify_objects_added) else : self.loop.call_soon(self._notify_objects_added) #end if #end if #end queue_notify async def await_propvalues() : nonlocal queue_task for path, interface_name, propname, fute in to_await : propvalue = await fute # don’t trap ErrorReturn propvalues = added_entry[interface_name]["props"] propvalues[propname] = (propvalues[propname][0], propvalue) #end for if self._objects_added == None : # might have happened in meantime self._objects_added = {} queue_task = True #end if self._objects_added[path] = added_entry # all prop values now complete queue_notify(True) #end await_propvalues #begin object_added path = dbus.unsplit_path(path) if self._managed_objects == None : raise RuntimeError("ObjectManager interface needs to be registered on this Connection") #end if if interfaces_and_props == None : # get all applicable interface names, props will be retrieved below intfs = self.find_interfaces_for_object(path) interfaces_and_props = dict \ ( (iface_name, None) for iface_name in intfs if len(intfs[iface_name]._interface_props) != 0 ) #end if if path in self._managed_objects : obj_entry = self._managed_objects[path] else : obj_entry = set() self._managed_objects[path] = obj_entry #end if if self._objects_added == None : self._objects_added = {} queue_task = True #end if if path in self._objects_added : added_entry = self._objects_added[path] else : added_entry = {} #end if if self.loop != None : notify_when = self.loop.time() + self.notify_delay else : notify_when = None #end if for interface, props in interfaces_and_props.items() : if props != None : added_props = {} for propname, propvalue in props.items() : if not isinstance(propvalue, (list, tuple)) or len(propvalue) != 2 : raise TypeError \ ( "value for property “%s” must be (type, value) pair" % propname ) #end if proptype = dbus.parse_single_signature(propvalue[0]) propvalue = proptype.validate(propvalue[1]) proptype = dbus.unparse_signature(proptype) added_props[propname] = (proptype, propvalue) #end for else : added_props, await_props = self._get_all_my_props(None, path, interface) # propgetters should not expect a message arg for propname, propvalue in await_props : to_await.append((path, interface, propname, propvalue)) #end for #end if added_entry[interface] = \ { "at" : notify_when, "props" : added_props, } obj_entry.add(interface) #end for if len(to_await) == 0 : self._objects_added[path] = added_entry # all prop values complete #end if if self.loop != None : if len(to_await) != 0 : self.create_task(await_propvalues()) else : queue_notify(False) #end if else : # cannot queue, notify immediately self._notify_objects_added() #end if #end object_added def object_removed(self, path, interfaces = None) : "Call this to send an ObjectManager notification about the removal of the" \ " specified set/sequence of interfaces from the specified object path. The" \ " ObjectManager interface must already have been registered on this Connection," \ " by calling «conn».register_additional_standard(managed_objects = True)." path = dbus.unsplit_path(path) if self._managed_objects == None : raise RuntimeError("ObjectManager interface not registered on this Connection") #end if queue_task = False if self._objects_removed == None : self._objects_removed = {} queue_task = True #end if if self._objects_added != None : added_entry = self._objects_added.get(path) else : added_entry = None #end if obj_entry = self._managed_objects[path] if path in self._objects_removed : removed_entry = self._objects_removed[path] else : removed_entry = {} self._objects_removed[path] = removed_entry #end if if self.loop != None : when = self.loop.time() + self.notify_delay else : when = None #end if if interfaces == None : intfs = self.find_interfaces_for_object(path) interfaces = set \ ( iface_name for iface_name in intfs if len(intfs[iface_name]._interface_props) != 0 ) #end if for interface in interfaces : if added_entry != None and interface in added_entry : # object-added notification was never sent, just cancel # it and don’t send object-removed notification del added_entry[interface] else : removed_entry[interface] = {"at" : when} #end if if self._props_changed != None : props_key = (path, interface) if self._props_changed != None and props_key in self._props_changed : # cancel pending properties-changed notification del self._props_changed[key] if len(self._props_changed) == 0 : self._props_changed = None #end if #end if #end if obj_entry.remove(interface) #end for if len(obj_entry) == 0 : del self._managed_objects[path] #end if if added_entry != None and len(added_entry) == 0 : del self._objects_added[path] if len(self._objects_added) == 0 : self._objects_added = None #end if #end if if len(removed_entry) != 0 : if self.loop != None : if queue_task : if self.notify_delay != 0 : self.loop.call_later(self.notify_delay, self._notify_objects_removed) else : self.loop.call_soon(self._notify_objects_removed) #end if #end if else : # cannot queue, notify immediately self._notify_objects_removed() #end if #end if #end object_removed def send_signal(self, *, path, interface, name, args) : "intended for server-side use: sends a signal with the specified" \ " interface and name from the specified object path. There must" \ " already be a registered interface instance with that name which" \ " defines that signal for that path." call_info = self._get_iface_entry(path, interface, name, "signal")._signal_info message = dbus.Message.new_signal \ ( path = dbus.unsplit_path(path), iface = interface, name = name ) message.append_objects(call_info["in_signature"], *args) self.connection.send(message) #end send_signal def introspect(self, destination, path, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "sends an Introspect request to the specified bus name and object path," \ " and returns the resulting parsed Introspection structure." message = dbus.Message.new_method_call \ ( destination = destination, path = dbus.unsplit_path(path), iface = DBUS.INTERFACE_INTROSPECTABLE, method = "Introspect" ) reply = self.connection.send_with_reply_and_block(message, timeout) return \ Introspection.parse(reply.expect_return_objects("s")[0]) #end introspect async def introspect_async(self, destination, path, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "sends an Introspect request to the specified bus name and object path," \ " and returns the resulting parsed Introspection structure." assert self.loop != None, "no event loop to attach coroutine to" message = dbus.Message.new_method_call \ ( destination = destination, path = dbus.unsplit_path(path), iface = DBUS.INTERFACE_INTROSPECTABLE, method = "Introspect" ) reply = await self.connection.send_await_reply(message, timeout) return \ Introspection.parse(reply.expect_return_objects("s")[0]) #end introspect_async def __getitem__(self, bus_name) : "for client-side use; lets you obtain references to bus peers by" \ " looking up their names in this Connection object as though it" \ " were a mapping." return \ BusPeer(self, bus_name) #end __getitem__ def get_proxy_object(self, bus_name, path) : "for client-side use; returns a BusPeer.Object instance for communicating" \ " with a specified server object. You can then call get_interface" \ " on the result to create an interface object that can be used to" \ " call any method defined on the server by that interface." return \ BusPeer(self, bus_name)[path] #end get_proxy_object def get_proxy_interface(self, destination, path, interface, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "sends an Introspect request to the specified bus name and object path" \ " (if interface is not an Interface object or the name of one of the standard" \ " interfaces), and generates a client-side proxy interface for that interface." if isinstance(interface, Introspection.Interface) : definition = interface interface = definition.name elif isinstance(interface, str) : if interface in dbus.standard_interfaces : definition = dbus.standard_interfaces[interface] else : introspection = self.introspect(destination, path, timeout) interfaces = introspection.interfaces_by_name if interface not in interfaces : raise dbus.DBusError \ ( DBUS.ERROR_UNKNOWN_INTERFACE, "peer “%s” object “%s” does not understand interface “%s”" % (destination, path, interface) ) #end if definition = interfaces[interface] interface = definition.name #end if else : raise TypeError("interface must be an Interface or name of one") #end if return \ def_proxy_interface \ ( name = interface, kind = INTERFACE.CLIENT, introspected = definition, is_async = False ) #end get_proxy_interface async def get_proxy_interface_async(self, destination, path, interface, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "sends an Introspect request to the specified bus name and object path" \ " (if interface is not an Interface object or the name of one of the standard" \ " interfaces), and generates a client-side proxy interface for that interface." assert self.loop != None, "no event loop to attach coroutine to" if isinstance(interface, Introspection.Interface) : definition = interface interface = definition.name elif isinstance(interface, str) : if interface in dbus.standard_interfaces : definition = dbus.standard_interfaces[interface] else : introspection = await self.introspect_async(destination, path, timeout) interfaces = introspection.interfaces_by_name if interface not in interfaces : raise dbus.DBusError \ ( DBUS.ERROR_UNKNOWN_INTERFACE, "peer “%s” object “%s” does not understand interface “%s”" % (destination, path, interface) ) #end if definition = interfaces[interface] #end if else : raise TypeError("interface must be an Interface or name of one") #end if return \ def_proxy_interface \ ( name = interface, kind = INTERFACE.CLIENT, introspected = definition, is_async = True ) #end get_proxy_interface_async #end Connection def session_bus(**kwargs) : "returns a Connection object for the current D-Bus session bus." return \ Connection(dbus.Connection.bus_get(DBUS.BUS_SESSION, private = False)) \ .register_additional_standard(**kwargs) #end session_bus def system_bus(**kwargs) : "returns a Connection object for the D-Bus system bus." return \ Connection(dbus.Connection.bus_get(DBUS.BUS_SYSTEM, private = False)) \ .register_additional_standard(**kwargs) #end system_bus def starter_bus(**kwargs) : "returns a Connection object for the D-Bus starter bus." return \ Connection(dbus.Connection.bus_get(DBUS.BUS_STARTER, private = False)) \ .register_additional_standard(**kwargs) #end starter_bus async def session_bus_async(loop = None, **kwargs) : "returns a Connection object for the current D-Bus session bus." return \ Connection \ ( await dbus.Connection.bus_get_async(DBUS.BUS_SESSION, private = False, loop = loop) ) \ .register_additional_standard(**kwargs) #end session_bus_async async def system_bus_async(loop = None, **kwargs) : "returns a Connection object for the D-Bus system bus." return \ Connection \ ( await dbus.Connection.bus_get_async(DBUS.BUS_SYSTEM, private = False, loop = loop) ) \ .register_additional_standard(**kwargs) #end system_bus_async async def starter_bus_async(loop = None, **kwargs) : "returns a Connection object for the D-Bus starter bus." return \ Connection \ ( await dbus.Connection.bus_get_async(DBUS.BUS_STARTER, private = False, loop = loop) ) \ .register_additional_standard(**kwargs) #end starter_bus_async def connect_server(address, **kwargs) : "opens a connection to a server at the specified network address and" \ " returns a Connection object for the connection." return \ Connection(dbus.Connection.open(address, private = False)) \ .register_additional_standard(**kwargs) #end connect_server async def connect_server_async(address, loop = None, timeout = DBUS.TIMEOUT_INFINITE, **kwargs) : "opens a connection to a server at the specified network address and" \ " returns a Connection object for the connection." return \ Connection \ ( await dbus.Connection.open_async(address, private = False, loop = loop, timeout = timeout) ) \ .register_additional_standard(**kwargs) #end connect_server_async class Server : "listens for connections on a particular socket address, separate from" \ " the D-Bus daemon. Requires asyncio." __slots__ = ("server",) def __init__(self, address, loop = None) : self.server = dbus.Server.listen(address) self.server.attach_asyncio(loop) #end __init__ def __del__(self) : self.server.disconnect() #end __del__ async def await_new_connection(self, timeout = DBUS.TIMEOUT_INFINITE) : "waits for a new connection attempt and returns a wrapping Connection object." \ " If no connection appears within the specified timeout, returns None." conn = await self.server.await_new_connection(timeout) if conn != None : result = Connection(conn) else : result = None #end if return \ result #end await_new_connection #end Server #+ # Proxy interface objects -- for client-side use #- class BusPeer : "Intended for client-side use: a proxy for a D-Bus peer. These offer two" \ " different ways to traverse the bus-name/path/interface hierarchy. Start" \ " by obtaining a BusPeer object from a Connection:\n" \ "\n" \ " peer = conn[«bus_name»]\n" \ "\n" \ "Now you can either reference a proxy object by specifying its path:\n" \ "\n" \ " obj = peer[«object_path»]\n" \ "\n" \ "from which you can a proxy interface thus:\n" \ "\n" \ " iface = obj.get_interface(«iface_name»)\n" \ "\n" \ "Or you can get a root proxy interface from the bus peer by" \ " introspecting some arbitrary (but suitable) reference path:\n" \ "\n" \ " iface_root = peer.get_interface(«reference_path», «iface_name»)\n" \ "\n" \ "from which you can obtain a proxy interface for a specific object thus:\n" \ "\n" \ " iface = iface_root[«object_path»]\n" \ "\n" \ "Whichever way you do it, you can now make method calls on the iface object" \ " which will automatically be communicated as method calls to the peer via" \ " the D-Bus." __slots__ = ("conn", "bus_name") class RootProxy : "abstract base class for identifying root proxy classes." pass #end RootProxy class Object : "identifies a proxy object by a bus, a bus name and a path." __slots__ = ("conn", "peer", "bus_name", "path") class ProxyInterface : "abstract base class for identifying proxy interface classes." pass #end ProxyInterface def __init__(self, conn, bus_name, path) : if not isinstance(conn, Connection) : raise TypeError("conn must be a Connection") #end if dbus.validate_bus_name(bus_name) self.conn = conn self.bus_name = bus_name self.path = path #end __init__ def introspect(self, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "sends an Introspect request to the specified bus name and object path," \ " and returns the resulting parsed Introspection structure." return \ self.conn.introspect \ ( destination = self.bus_name, path = self.path, timeout = timeout, ) #end introspect async def introspect_async(self, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "sends an Introspect request to the specified bus name and object path," \ " and returns the resulting parsed Introspection structure." return await \ self.conn.introspect_async \ ( destination = self.bus_name, path = self.path, timeout = timeout, ) #end introspect_async def get_interface(self, interface, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "sends an Introspect request to the specified bus name and object path" \ " (if interface is not one of the standard interfaces), and generates" \ " a client-side proxy interface for the interface with the specified name." return \ self.conn.get_proxy_interface \ ( destination = self.bus_name, path = self.path, interface = interface, timeout = timeout )( connection = self.conn.connection, dest = self.bus_name, timeout = timeout, )[self.path] #end get_interface async def get_async_interface(self, interface, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "sends an Introspect request to the specified bus name and object path" \ " (if interface is not one of the standard interfaces), and generates" \ " a client-side proxy interface for the interface with the specified name." return \ (await self.conn.get_proxy_interface_async \ ( destination = self.bus_name, path = self.path, interface = interface, timeout = timeout ))( connection = self.conn.connection, dest = self.bus_name, timeout = timeout, )[self.path] #end get_async_interface #end Object def __init__(self, conn, bus_name) : self.conn = conn self.bus_name = bus_name #end __init__ def __getitem__(self, path) : "lets you obtain references to objects implemented by this bus peer" \ " by using their paths as lookup keys in a mapping. Of course, there" \ " is no guarantee such an object actually exists within the peer." return \ type(self).Object(self.conn, self.bus_name, path) #end __getitem__ def introspect(self, path, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "sends an Introspect request to the specified bus name and object path," \ " and returns the resulting parsed Introspection structure." return \ self.conn.introspect \ ( destination = self.bus_name, path = path, timeout = timeout, ) #end introspect async def introspect_async(self, path, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "sends an Introspect request to the specified bus name and object path," \ " and returns the resulting parsed Introspection structure." return await \ self.conn.introspect_async \ ( destination = self.bus_name, path = path, timeout = timeout, ) #end introspect_async def get_interface(self, path, interface, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "sends an Introspect request to the specified bus name and object path" \ " (if interface is not one of the standard interfaces), and generates" \ " a client-side proxy interface for the interface with the specified name." return \ self.conn.get_proxy_interface \ ( destination = self.bus_name, path = path, interface = interface, timeout = timeout, )( connection = self.conn.connection, dest = self.bus_name, timeout = timeout, ) #end get_interface async def get_interface_async(self, path, interface, timeout = DBUS.TIMEOUT_USE_DEFAULT) : "sends an Introspect request to the specified bus name and object path" \ " (if interface is not one of the standard interfaces), and generates" \ " a client-side proxy interface for the interface with the specified name." return \ (await self.conn.get_proxy_interface_async \ ( destination = self.bus_name, path = path, interface = interface, timeout = timeout, ))( connection = self.conn.connection, dest = self.bus_name, timeout = timeout, ) #end get_interface_async #end BusPeer #+ # Interface-dispatch mechanism #- class INTERFACE(enum.Enum) : "what kind of @interface() is this:\n" \ " * CLIENT -- client-side, for sending method calls to server and" \ " receiving signals from server\n" \ " * SERVER -- server-side, for receiving method calls from clients and" \ " sending signals to clients\n" \ " * CLIENT_AND_SERVER -- this side is both client and server." CLIENT = 1 SERVER = 2 CLIENT_AND_SERVER = 3 # CLIENT | SERVER #end INTERFACE def _send_method_return(connection, message, sig, args) : reply = message.new_method_return() reply.append_objects(sig, *args) connection.send(reply) #end _send_method_return def _message_interface_dispatch(connection, message, w_bus) : # installed as message filter on a connection to handle dispatch # to registered @interface() classes. bus = w_bus() assert bus != None, "parent Connection has gone" def dispatch_signal(level, path) : # Note I ignore handled/not handled status and pass the signal # to all registered handlers. if len(path) != 0 and path[0] in level.children : # call lower-level (more-specific) signal handlers first, # not that it’s important dispatch_signal(level.children[path[0]], path[1:]) #end if signal_listeners = level.signal_listeners name = message.member for fallback in (False, True) : # again, call more-specific (fallback = False) handlers first, # not that it’s important rulekey = _signal_key(fallback, message.interface, name) if rulekey in signal_listeners and (len(path) == 0 or fallback) : funcs = signal_listeners[rulekey] for func in funcs : try : call_info = func._signal_info try : args = message.expect_objects(call_info["in_signature"]) except (TypeError, ValueError) : raise ErrorReturn \ ( name = DBUS.ERROR_INVALID_ARGS, message = "message arguments do not match expected signature" ) #end try kwargs = {} for keyword_keyword, value in \ ( ("connection_keyword", lambda : connection), ("message_keyword", lambda : message), ("path_keyword", lambda : message.path), ("bus_keyword", lambda : bus), ) \ : if call_info[keyword_keyword] != None : kwargs[call_info[keyword_keyword]] = value() #end if #end for if call_info["args_keyword"] != None : if call_info["arg_keys"] != None : args = dict \ ( (key, value) for key, value in zip(call_info["arg_keys"], args) ) if "args_constructor" in call_info : args = call_info["args_constructor"](**args) #end if #end if kwargs[call_info["args_keyword"]] = args args = () else : if call_info["arg_keys"] != None : for key, value in zip(call_info["arg_keys"], args) : kwargs[key] = value #end for args = () #end if #end if result = func(*args, **kwargs) if asyncio.iscoroutine(result) : async def await_result(coro) : # just to gobble any ErrorReturn try : await coro except ErrorReturn : pass #end try #end await_result bus.create_task(await_result(result)) elif result != None : raise ValueError \ ( "invalid result from signal handler: %s" % repr(result) ) #end if except ErrorReturn : pass #end try #end for #end if #end for #end dispatch_signal def return_result_common(call_info, result) : # handles list, tuple, dict or Error returned from method handler; # packs into reply message and sends it off. if isinstance(result, dbus.Error) : assert result.is_set, "unset Error object returned from handler" reply = message.new_error(result.name, result.message) connection.send(reply) else : sig = dbus.parse_signature(call_info["out_signature"]) if isinstance(result, dict) and call_info["result_keys"] != None : result = list(result[key] for key in call_info["result_keys"]) # convert result items to list in right order elif ( "result_constructor" in call_info and isinstance(result, call_info["result_constructor"]) ) : result = list(result) elif len(sig) == 0 and result == None : # might as well allow method call to not bother returning an empty result result = [] elif not isinstance(result, (tuple, list)) : raise ValueError("invalid handler result %s" % repr(result)) #end if _send_method_return \ ( connection = connection, message = message, sig = sig, args = result ) #end if #end return_result_common #begin _message_interface_dispatch result = DBUS.HANDLER_RESULT_NOT_YET_HANDLED # to begin with if message.type in (DBUS.MESSAGE_TYPE_METHOD_CALL, DBUS.MESSAGE_TYPE_SIGNAL) : is_method = message.type == DBUS.MESSAGE_TYPE_METHOD_CALL if not is_method and bus._client_dispatch != None : dispatch_signal(bus._client_dispatch, dbus.split_path(message.path)) #end if iface = bus.get_dispatch_interface(message.path, message.interface) if iface != None : method_name = message.member methods = (iface._interface_signals, iface._interface_methods)[is_method] if ( iface._interface_kind != (INTERFACE.SERVER, INTERFACE.CLIENT)[is_method] and method_name in methods ) : method = methods[method_name] call_info = getattr(method, ("_signal_info", "_method_info")[is_method]) if is_method or not call_info["stub"] : to_return_result = None try : try : args = message.expect_objects(call_info["in_signature"]) except (TypeError, ValueError) : raise ErrorReturn \ ( name = DBUS.ERROR_INVALID_ARGS, message = "message arguments do not match expected signature" ) #end try kwargs = {} for keyword_keyword, value in \ ( ("connection_keyword", lambda : connection), ("message_keyword", lambda : message), ("path_keyword", lambda : message.path), ("bus_keyword", lambda : bus), ) \ : if call_info[keyword_keyword] != None : kwargs[call_info[keyword_keyword]] = value() #end if #end for if call_info["args_keyword"] != None : if call_info["arg_keys"] != None : args = dict \ ( (key, value) for key, value in zip(call_info["arg_keys"], args) ) if "args_constructor" in call_info : args = call_info["args_constructor"](**args) #end if #end if kwargs[call_info["args_keyword"]] = args args = () else : if call_info["arg_keys"] != None : for key, value in zip(call_info["arg_keys"], args) : kwargs[key] = value #end for args = () #end if #end if if is_method : # additional ways of returning method result if call_info["result_keyword"] != None : # construct a mutable result object that handler will update in place to_return_result = [None] * len(call_info["out_signature"]) if call_info["result_keys"] != None : to_return_result = dict \ ( (key, val) for key, val in zip(call_info["result_keys"], to_return_result) ) if "result_constructor" in call_info : to_return_result = call_info["result_constructor"](**to_return_result) #end if #end if kwargs[call_info["result_keyword"]] = to_return_result elif call_info["set_result_keyword"] != None : # caller wants to return result via callback def set_result(the_result) : "Call this to set the args for the reply message." nonlocal to_return_result to_return_result = the_result #end set_result kwargs[call_info["set_result_keyword"]] = set_result #end if #end if result = method(iface, *args, **kwargs) except ErrorReturn as err : result = err.as_error() #end try if result == None : if to_return_result != None or is_method : # method handler possibly used set_result mechanism return_result_common(call_info, to_return_result) #end if result = DBUS.HANDLER_RESULT_HANDLED elif asyncio.iscoroutine(result) : assert bus.loop != None, "no event loop to attach coroutine to" if is_method : # wait for result async def await_result(coro) : try : result = await coro except ErrorReturn as err : result = err.as_error() #end try if result == None and to_return_result != None : # method handler used set_result mechanism result = to_return_result #end if return_result_common(call_info, result) #end await_result bus.create_task(await_result(result)) else : bus.create_task(result) #end if result = DBUS.HANDLER_RESULT_HANDLED elif isinstance(result, bool) : # slightly tricky: interpret True as handled, False as not handled, # even though DBUS.HANDLER_RESULT_HANDLED is zero and # DBUS.HANDLER_RESULT_NOT_YET_HANDLED is nonzero. result = \ (DBUS.HANDLER_RESULT_NOT_YET_HANDLED, DBUS.HANDLER_RESULT_HANDLED)[result] elif ( result in ( DBUS.HANDLER_RESULT_HANDLED, DBUS.HANDLER_RESULT_NOT_YET_HANDLED, DBUS.HANDLER_RESULT_NEED_MEMORY, ) ) : pass else : return_result_common(call_info, result) result = DBUS.HANDLER_RESULT_HANDLED #end if #end if #end if #end if #end if dispatch_signal = None # remove reference circularity return \ result #end _message_interface_dispatch def def_attr_class(name, attrs) : "defines a class with read/write attributes with names from the sequence attrs." \ " Objects of this class can be coerced to lists or tuples, and attributes can" \ " also be accessed by index, like a list." class result : __slots__ = tuple(attrs) def __init__(self, **kwargs) : for name in type(self).__slots__ : setattr(self, name, None) #end for for name in kwargs : setattr(self, name, kwargs[name]) #end for #end __init__ def __repr__(self) : return \ ( "%s(%s)" % ( type(self).__name__, ", ".join ( "%s = %s" % (name, repr(getattr(self, name))) for name in type(self).__slots__ ), ) ) #end __repr__ def __len__(self) : return \ len(type(self).__slots__) #end __len__ def __getitem__(self, i) : return \ getattr(self, type(self).__slots__[i]) #end __getitem__ def __setitem__(self, i, val) : setattr(self, type(self).__slots__[i], val) #end __setitem__ #end class #begin def_attr_class result.__name__ = name return \ result #end def_attr_class def interface \ ( kind, *, name, property_change_notification = Introspection.PROP_CHANGE_NOTIFICATION.NEW_VALUE, deprecated = False ) : "class decorator creator for defining interface classes. “kind” is an" \ " INTERFACE.xxx value indicating whether this is for use on the client side" \ " (send methods, receive signals), server side (receive methods, send signals)" \ " or both. “name” (required) is the interface name that will be known to D-Bus." \ " Interface methods and signals should be invocable as\n" \ "\n" \ " method(self, ...)\n" \ "\n" \ " and definitions should be prefixed with calls to the “@method()” or “@signal()”" \ " decorator to identify them. The return result can be a DBUS.HANDLER_RESULT_xxx" \ " code, or None (equivalent to DBUS.HANDLER_RESULT_HANDLED), or a coroutine" \ " to queue for execution after indicating that the message has been handled. Note" \ " that if you declare the method with “async def”, then the return result seen" \ " will be such a coroutine." if not isinstance(kind, INTERFACE) : raise TypeError("kind must be an INTERFACE enum value") #end if if not isinstance(name, str) : raise ValueError("name is required") #end if dbus.validate_interface(name) def decorate(celf) : if not isinstance(celf, type) : raise TypeError("only apply decorator to classes.") #end if if not isinstance(property_change_notification, Introspection.PROP_CHANGE_NOTIFICATION) : raise TypeError \ ( "property_change_notification must be an Introspection." "PROP_CHANGE_NOTIFICATION value" ) #end if celf._interface_kind = kind celf._interface_name = name celf._interface_property_change_notification = property_change_notification celf._interface_deprecated = deprecated celf._interface_methods = \ dict \ ( (f._method_info["name"], f) for fname in dir(celf) for f in (getattr(celf, fname),) if hasattr(f, "_method_info") ) celf._interface_signals = \ dict \ ( (f._signal_info["name"], f) for fname in dir(celf) for f in (getattr(celf, fname),) if hasattr(f, "_signal_info") ) props = {} for info_type, meth_type in \ ( ("_propgetter_info", "getter"), # do first so setter can check change_notification ("_propsetter_info", "setter"), ) \ : for fname in dir(celf) : func = getattr(celf, fname) if hasattr(func, info_type) : propinfo = getattr(func, info_type) propname = propinfo["name"] if propname not in props : props[propname] = {"type" : None} #end if propentry = props[propname] if propinfo["type"] != None : if propentry["type"] != None : if propentry["type"] != propinfo["type"] : raise ValueError \ ( "disagreement on type for property “%s” between" " getter and setter: “%s” versus “%s”" % ( propname, dbus.unparse_signature(propentry["type"]), dbus.unparse_signature(propinfo["type"]), ) ) #end if else : propentry["type"] = propinfo["type"] #end if #end if if ( meth_type == "setter" and "getter" in propentry and propentry["change_notification"] == Introspection.PROP_CHANGE_NOTIFICATION.CONST ) : raise ValueError \ ( "mustn’t specify @propsetter() for a" " PROP_CHANGE_NOTIFICATION.CONST property" ) #end if if meth_type == "getter" : if propinfo["change_notification"] != None : propentry["change_notification"] = propinfo["change_notification"] else : propentry["change_notification"] = property_change_notification #end if #end if propentry[meth_type] = func #end if #end for #end for celf._interface_props = props return \ celf #end decorate #begin interface return \ decorate #end interface def is_interface(cłass) : "is cłass defined as an interface class." return \ isinstance(cłass, type) and hasattr(cłass, "_interface_name") #end is_interface def is_interface_instance(obj) : "is obj an instance of an interface class." return \ is_interface(type(obj)) #end is_interface_instance def method \ (*, name = None, in_signature, out_signature, args_keyword = None, arg_keys = None, arg_attrs = None, result_keyword = None, result_keys = None, result_attrs = None, connection_keyword = None, message_keyword = None, path_keyword = None, bus_keyword = None, set_result_keyword = None, reply = True, deprecated = False ) : "Put a call to this function as a decorator for each method of an @interface()" \ " class that is to be registered as a method of the interface." \ " “name” is the name of the method as specified in the D-Bus message; if omitted," \ " it defaults to the name of the function.\n" \ "\n" \ "This is really only useful on the server side. On the client side, omit the" \ " method definition, and even leave out the interface definition and registration" \ " altogether, unless you want to receive signals from the server; instead, use" \ " Connection.get_proxy_object() to send method calls to the server." in_signature = dbus.parse_signature(in_signature) out_signature = dbus.parse_signature(out_signature) for cond, msg in \ ( ( (result_keyword != None or result_keys != None or result_attrs != None) and not reply, "result_keyword, result_keys and result_attrs are" " meaningless if method does not reply", ), (arg_keys != None and arg_attrs != None, "specify arg_keys or arg_attrs, not both"), (arg_attrs != None and args_keyword == None, "need args_keyword with arg_attrs"), ( arg_keys != None and len(arg_keys) != len(in_signature), "number of arg_keys should match number of items in in_signature", ), ( arg_attrs != None and len(arg_attrs) != len(in_signature), "number of arg_attrs should match number of items in in_signature", ), ( set_result_keyword != None and result_keyword != None, "specify set_result_keyword or result_keyword, not both", ), ( result_keys != None and result_attrs != None, "specify result_keys or result_attrs, not both", ), ( result_attrs != None and result_keyword == None, "need result_keyword with result_attrs", ), ( result_keys != None and len(result_keys) != len(out_signature), "number of result_keys should match number of items in out_signature", ), ( result_attrs != None and len(result_attrs) != len(out_signature), "number of result_attrs should match number of items in out_signature", ), ) \ : if cond : raise ValueError(msg) #end if #end for if arg_keys == None : args_keys = arg_attrs #end if if result_keys == None : result_keys = result_attrs #end if def decorate(func) : if not callable(func) : raise TypeError("only apply decorator to callables.") #end if if name != None : func_name = name else : func_name = func.__name__ #end if dbus.validate_member(func_name) func._method_info = \ { "name" : func_name, "in_signature" : in_signature, "out_signature" : out_signature, "args_keyword" : args_keyword, "arg_keys" : arg_keys, "result_keyword" : result_keyword, "result_keys" : result_keys, "connection_keyword" : connection_keyword, "message_keyword" : message_keyword, "path_keyword" : path_keyword, "bus_keyword" : bus_keyword, "set_result_keyword" : set_result_keyword, "reply" : reply, "deprecated" : deprecated, } if arg_attrs != None : func._method_info["args_constructor"] = def_attr_class("%s_args" % func_name, arg_attrs) #end if if result_attrs != None : func._method_info["result_constructor"] = \ def_attr_class("%s_result" % func_name, result_attrs) #end if return \ func #end decorate #begin method return \ decorate #end method def signal \ (*, name = None, in_signature, args_keyword = None, arg_keys = None, arg_attrs = None, stub = False, connection_keyword = None, message_keyword = None, path_keyword = None, bus_keyword = None, deprecated = False # can signals be deprecated? ) : "Put a call to this function as a decorator for each method of an @interface()" \ " class that is to be registered as a signal of the interface." \ " “name” is the name of the signal as specified in the D-Bus message; if omitted," \ " it defaults to the name of the function.\n" \ "\n" \ "On the server side, the actual function need only be a dummy, since it is just" \ " a placeholder for storing the information used by Connection.send_signal()." in_signature = dbus.parse_signature(in_signature) if arg_attrs != None and args_keyword == None : raise ValueError("need args_keyword with arg_attrs") #end if if arg_keys != None and len(arg_keys) != len(in_signature) : raise ValueError("number of arg_keys should match number of items in in_signature") #end if if arg_attrs != None and len(arg_attrs) != len(in_signature) : raise ValueError("number of arg_attrs should match number of items in in_signature") #end if if arg_keys == None : args_keys = arg_attrs #end if def decorate(func) : if not callable(func) : raise TypeError("only apply decorator to callables.") #end if if name != None : func_name = name else : func_name = func.__name__ #end if dbus.validate_member(func_name) func._signal_info = \ { "name" : func_name, "in_signature" : in_signature, "args_keyword" : args_keyword, "arg_keys" : arg_keys, "stub" : stub, "connection_keyword" : connection_keyword, "message_keyword" : message_keyword, "path_keyword" : path_keyword, "bus_keyword" : bus_keyword, "deprecated" : deprecated, } if arg_attrs != None : func._signal_info["args_constructor"] = def_attr_class("%s_args" % func_name, arg_attrs) #end if return \ func #end decorate #begin signal return \ decorate #end signal def def_signal_stub(**kwargs) : "convenience routine for defining a signal stub function. Instead of\n" \ "\n" \ " @signal(«args»)\n" \ " def stubfunc() : pass\n" \ "\n" \ "you can do\n" \ "\n" \ " stubfunc = def_signal_stub(«args»)\n" \ "\n" \ "passing the same «args» as you would to the @signal() decorator. But note" \ " that the name arg is no longer optional." def stub() : "This is just a stub, standing in for a signal definition in a" \ " proxy interface class. It is not meant to be called." # Lack of formal args should also stymie attempts to invoke as a method. raise \ NotImplementedError("attempted call on signal stub") #end stub #begin def_signal_stub if "name" not in kwargs : raise KeyError("name arg is mandatory") #end if stub.__name__ = kwargs["name"] return \ signal(**kwargs)(stub) #end def_signal_stub def propgetter \ (*, name, type, name_keyword = None, connection_keyword = None, message_keyword = None, path_keyword = None, bus_keyword = None, change_notification = None ) : "Put a call to this function as a decorator for a method of an @interface()" \ " class that is to be the getter of the named property." def decorate(func) : if not callable(func) : raise TypeError("only apply decorator to callables.") #end if assert isinstance(name, str), "property name is mandatory" if ( change_notification != None and not isinstance(change_notification, Introspection.PROP_CHANGE_NOTIFICATION) ) : raise TypeError \ ( "change_notification must be None or an Introspection." "PROP_CHANGE_NOTIFICATION value" ) #end if func._propgetter_info = \ { "name" : name, "type" : dbus.parse_single_signature(type), "name_keyword" : name_keyword, "connection_keyword" : connection_keyword, "message_keyword" : message_keyword, "path_keyword" : path_keyword, "bus_keyword" : bus_keyword, "change_notification" : change_notification, } return \ func #end decorate #begin propgetter return \ decorate #end propgetter def propsetter \ (*, name, type, name_keyword = None, type_keyword = None, value_keyword, connection_keyword = None, message_keyword = None, path_keyword = None, bus_keyword = None ) : "Put a call to this function as a decorator for a method of an @interface()" \ " class that is to be the setter of the named property." def decorate(func) : if not callable(func) : raise TypeError("only apply decorator to callables.") #end if assert isinstance(name, str), "property name is mandatory" func._propsetter_info = \ { "name" : name, "type" : dbus.parse_single_signature(type), "name_keyword" : name_keyword, "type_keyword" : type_keyword, "value_keyword" : value_keyword, "connection_keyword" : connection_keyword, "message_keyword" : message_keyword, "path_keyword" : path_keyword, "bus_keyword" : bus_keyword, } return \ func #end decorate #begin propsetter return \ decorate #end propsetter #+ # Introspection #- def introspect(interface) : "returns an Introspection.Interface object that describes the specified" \ " @interface() class." if not is_interface(interface) : raise TypeError("interface must be an @interface()-type class") #end if def add_deprecated(annots, deprecated) : # common routine for generating “deprecated” annotations. if deprecated : annots.append \ ( Introspection.Annotation(name = "org.freedesktop.DBus.Deprecated", value = "true") ) #end if #end add_deprecated #begin introspect methods = [] for name in interface._interface_methods : method = interface._interface_methods[name] annots = [] add_deprecated(annots, method._method_info["deprecated"]) if not method._method_info["reply"] : annots.append \ ( Introspection.Annotation ( name = "org.freedesktop.DBus.Method.NoReply", value = "true" ) ) #end if args = [] for keys_keyword, sig_keyword, direction in \ ( ("arg_keys", "in_signature", Introspection.DIRECTION.IN), ("result_keys", "out_signature", Introspection.DIRECTION.OUT), ) \ : arg_sigs = dbus.parse_signature(method._method_info[sig_keyword]) arg_names = method._method_info[keys_keyword] if arg_names == None : arg_names = [None] * len(arg_sigs) #end if for arg_name, arg_sig in zip(arg_names, arg_sigs) : args.append \ ( Introspection.Interface.Method.Arg ( name = arg_name, type = arg_sig, direction = direction ) ) #end for #end for methods.append \ ( Introspection.Interface.Method ( name = name, args = args, annotations = annots ) ) #end for signals = [] for name in interface._interface_signals : signal = interface._interface_signals[name] annots = [] add_deprecated(annots, signal._signal_info["deprecated"]) args = [] arg_sigs = dbus.parse_signature(signal._signal_info["in_signature"]) arg_names = signal._signal_info["arg_keys"] if arg_names == None : arg_names = [None] * len(arg_sigs) #end if for arg_name, arg_sig in zip(arg_names, arg_sigs) : args.append \ ( Introspection.Interface.Signal.Arg(name = arg_name, type = arg_sig) ) #end for signals.append \ ( Introspection.Interface.Signal ( name = name, args = args, annotations = annots ) ) #end for properties = [] for name in interface._interface_props : prop = interface._interface_props[name] annots = [] if ( "getter" in prop and prop["change_notification"] != interface._interface_property_change_notification ) : annots.append \ ( Introspection.Annotation ( name = "org.freedesktop.DBus.Property.EmitsChangedSignal", value = prop["change_notification"].value ) ) #end if properties.append \ ( Introspection.Interface.Property ( name = name, type = dbus.parse_single_signature(prop["type"]), access = ( None, Introspection.ACCESS.READ, Introspection.ACCESS.WRITE, Introspection.ACCESS.READWRITE, )[ int("getter" in prop) | int("setter" in prop) << 1 ], annotations = annots ) ) #end for annots = [] if ( interface._interface_property_change_notification != Introspection.PROP_CHANGE_NOTIFICATION.NEW_VALUE ) : annots.append \ ( Introspection.Annotation ( name = "org.freedesktop.DBus.Property.EmitsChangedSignal", value = interface._interface_property_change_notification.value ) ) #end if add_deprecated(annots, interface._interface_deprecated) return \ Introspection.Interface \ ( name = interface._interface_name, methods = methods, signals = signals, properties = properties, annotations = annots ) #end introspect def _append_args(message, call_info, args, kwargs) : message_args = [None] * len(call_info.in_signature) if len(args) != 0 : if len(args) > len(message_args) : raise ValueError("too many args") #end if message_args[:len(args)] = args #end if if len(kwargs) != 0 : arg_positions = {} idx = 0 for arg in call_info.args : if ( isinstance(arg, Introspection.Interface.Signal.Arg) or arg.direction == Introspection.DIRECTION.IN ) : arg_positions[arg.name] = idx idx += 1 #end if #end for for arg_name in kwargs : if arg_name not in arg_positions : raise KeyError("no such arg name “%s”" % arg_name) #end if pos = arg_positions[arg_name] if message_args[pos] != None : raise ValueError("duplicate value for arg %d" % pos) #end if message_args[pos] = kwargs[arg_name] #end for #end if missing = set(pos for pos in range(len(message_args)) if message_args[pos] == None) if len(missing) != 0 : raise ValueError \ ( "too few args specified: missing %s" % ", ".join("%d" % pos for pos in sorted(missing)) ) #end if message.append_objects(call_info.in_signature, *message_args) #end _append_args def def_proxy_interface(kind, *, name, introspected, is_async) : "given an Introspection.Interface object, creates a proxy class that can be" \ " instantiated by a client to send method-call messages to a server," \ " or by a server to send signal messages to clients. is_async indicates" \ " whether method calls are done via coroutines as opposed to blocking" \ " the thread. The resulting class can be instantiated by\n" \ "\n" \ " instance = proxy_class(«connection», «dest»)\n" \ "\n" \ " where «connection» is a dbussy.Connection object to use for sending and receiving" \ " the messages, and «dest» is the bus name to which to send the messages. The" \ " resulting instance is a “root proxy”: a proxy for a particular object path" \ " is obtained from this by an indexing call like\n" \ "\n" \ " obj = instance[«object_path»]\n" \ "\n" \ "from which you can make proxy method calls like obj.«method»(«args») and so on." if not isinstance(kind, INTERFACE) : raise TypeError("kind must be an INTERFACE enum value") #end if if not isinstance(introspected, Introspection.Interface) : raise TypeError("introspected must be an Introspection.Interface") #end if class proxy(BusPeer.Object.ProxyInterface) : # class that will be constructed, to be instantiated for a given connection, # destination and path. # class field _iface_name contains interface name. __slots__ = ("_parent", "_conn", "_dest", "_path", "_timeout") def __init__(self, *, parent, connection, dest, path, timeout = DBUS.TIMEOUT_USE_DEFAULT) : if is_async : assert connection.loop != None, "no event loop to attach coroutines to" #end if self._parent = parent self._conn = connection self._dest = dest self._path = path self._timeout = timeout #end __init__ # rest filled in dynamically below. #end proxy def def_method(intr_method) : # constructs a method-call method. if is_async : async def call_method(self, *args, **kwargs) : message = dbus.Message.new_method_call \ ( destination = self._dest, path = dbus.unsplit_path(self._path), iface = self._iface_name, method = intr_method.name ) _append_args(message, intr_method, args, kwargs) if intr_method.expect_reply : reply = await self._conn.send_await_reply(message, self._timeout) result = reply.expect_return_objects(intr_method.out_signature) else : message.no_reply = True self._conn.send(message) result = None #end if return \ result #end call_method else : def call_method(self, *args, **kwargs) : message = dbus.Message.new_method_call \ ( destination = self._dest, path = dbus.unsplit_path(self._path), iface = self._iface_name, method = intr_method.name ) _append_args(message, intr_method, args, kwargs) if intr_method.expect_reply : reply = self._conn.send_with_reply_and_block(message, self._timeout) result = reply.expect_return_objects(intr_method.out_signature) else : message.no_reply = True self._conn.send(message) result = None #end if return \ result #end call_method #end if #begin def_method call_method.__name__ = intr_method.name call_method.__doc__ = \ ( "method, %(args)s, %(result)s" % { "args" : ( lambda : "no args", lambda : "args %s" % dbus.unparse_signature(intr_method.in_signature), )[len(intr_method.in_signature) != 0](), "result" : ( lambda : "no result", lambda : "result %s" % dbus.unparse_signature(intr_method.out_signature), )[len(intr_method.out_signature) != 0](), } ) setattr(proxy, intr_method.name, call_method) #end def_method def def_signal(intr_signal) : # constructs a signal method. These are never async, since messages # are queued and there is no reply. def send_signal(self, *args, **kwargs) : message = dbus.Message.new_signal \ ( path = dbus.unsplit_path(self._path), iface = self._iface_name, name = intr_signal.name ) _append_args(message, intr_signal, args, kwargs) self._conn.send(message) #end send_signal #begin def_signal send_signal.__name__ = intr_signal.name send_signal.__doc__ = \ ( "signal, %(args)s" % { "args" : ( lambda : "no args", lambda : "args %s" % dbus.unparse_signature(intr_signal.in_signature), )[len(intr_signal.in_signature) != 0](), } ) setattr(proxy, signal.name, send_signal) #end def_signal def def_prop(intr_prop) : # defines getter and/or setter methods as appropriate for a property. if is_async : async def get_prop(self) : message = dbus.Message.new_method_call \ ( destination = self._dest, path = dbus.unsplit_path(self._path), iface = DBUS.INTERFACE_PROPERTIES, method = "Get" ) message.append_objects("ss", self._iface_name, intr_prop.name) reply = await self._conn.send_await_reply(message, self._timeout) return \ reply.expect_return_objects("v")[0][1] #end get_prop def set_prop(self, value) : # Unfortunately, Python doesn’t (currently) allow “await” # on the LHS of an assignment. So to avoid holding up the # thread, I put a task on the event loop to watch over # the completion of the send. This means any error is # going to be reported asynchronously. C’est la vie. message = dbus.Message.new_method_call \ ( destination = self._dest, path = dbus.unsplit_path(self._path), iface = DBUS.INTERFACE_PROPERTIES, method = "Set" ) message.append_objects("ssv", self._iface_name, intr_prop.name, (intr_prop.type, value)) set_prop_pending = self._conn.loop.create_future() self._parent._set_prop_pending.append(set_prop_pending) pending = self._conn.send_with_reply(message, self._timeout) async def sendit() : reply = await pending.await_reply() set_prop_pending.set_result(None) self._parent._set_prop_pending.pop \ ( self._parent._set_prop_pending.index(set_prop_pending) ) if reply.type == DBUS.MESSAGE_TYPE_METHOD_RETURN : pass elif reply.type == DBUS.MESSAGE_TYPE_ERROR : raise dbus.DBusError(reply.error_name, reply.expect_objects("s")[0]) else : raise ValueError("unexpected reply type %d" % reply.type) #end if #end sendit self._conn.create_task(sendit()) #end set_prop else : def get_prop(self) : message = dbus.Message.new_method_call \ ( destination = self._dest, path = dbus.unsplit_path(self._path), iface = DBUS.INTERFACE_PROPERTIES, method = "Get" ) message.append_objects("ss", self._iface_name, intr_prop.name) reply = self._conn.send_with_reply_and_block(message, self._timeout) return \ reply.expect_return_objects("v")[0][1] #end get_prop def set_prop(self, value) : message = dbus.Message.new_method_call \ ( destination = self._dest, path = dbus.unsplit_path(self._path), iface = DBUS.INTERFACE_PROPERTIES, method = "Set" ) message.append_objects("ssv", self._iface_name, intr_prop.name, (intr_prop.type, value)) reply = self._conn.send_with_reply_and_block(message, self._timeout) if reply.type == DBUS.MESSAGE_TYPE_METHOD_RETURN : pass elif reply.type == DBUS.MESSAGE_TYPE_ERROR : raise dbus.DBusError(reply.error_name, reply.expect_objects("s")[0]) else : raise ValueError("unexpected reply type %d" % reply.type) #end if #end set_prop #end if def get_prop_noaccess(self) : raise dbus.DbusError \ ( name = DBUS.ERROR_ACCESS_DENIED, message = "property “%s” cannot be read" % intro_prop.name ) #end get_prop_noaccess #begin def_prop if intr_prop.access == Introspection.ACCESS.WRITE : get_prop = get_prop_noaccess #end if if intr_prop.access == Introspection.ACCESS.READ : set_prop = None #end if prop = property(fget = get_prop, fset = set_prop) setattr(proxy, intr_prop.name, prop) #end def_prop class proxy_factory(BusPeer.RootProxy) : # class that will be returned. __slots__ = ("connection", "dest", "timeout", "_set_prop_pending") def __init__(self, *, connection, dest, timeout = DBUS.TIMEOUT_USE_DEFAULT) : if is_async : assert connection.loop != None, "no event loop to attach coroutines to" #end if self.connection = connection self.dest = dest self.timeout = timeout if is_async : self._set_prop_pending = [] else : self._set_prop_pending = None #end if #end __init__ def __getitem__(self, path) : return \ self.template \ ( parent = self, connection = self.connection, dest = self.dest, path = path, timeout = self.timeout ) #end __getitem__ async def set_prop_flush(self) : "workaround for the fact that prop-setter has to queue a separate" \ " asynchronous task; caller can await this coroutine to ensure that" \ " all pending set-property calls have completed." if not is_async : raise RuntimeError("not without an event loop") #end if if len(self._set_prop_pending) != 0 : await asyncio.wait(self._set_prop_pending, loop = self.connection.loop) #end if #end set_prop_flush #end proxy_factory #begin def_proxy_interface if name != None : class_name = name else : class_name = introspected.name.replace(".", "_") #end if proxy.__name__ = class_name proxy._iface_name = introspected.name proxy.__doc__ = "for making method calls on the %s interface." % introspected.name if kind != INTERFACE.SERVER : for method in introspected.methods : def_method(method) #end for for prop in introspected.properties : def_prop(prop) #end for #end if if kind != INTERFACE.CLIENT : for signal in introspected.signals : def_signal(signal) #end for #end if proxy_factory.__name__ = "%s_factory" % proxy.__name__ proxy_factory.__doc__ = \ ( "proxy factory for a %(kind)s D-Bus interface named %(iname)s. Instantiate as\n" "\n" " %(cname)s(connection = «connection»[, dest = «dest»[, timeout = «timeout»]])\n" "\n" "where «connection» is the dbussy.Connection instance to use for sending" " messages and receiving replies, and «dest» is the destination" \ " bus name for sending method calls (not needed if only sending signals)." " The resulting «proxy» object can be indexed by object path, as follows:\n" "\n" " «proxy»[«path»]\n" "\n" "to obtain the actual proxy interface object that can be used to do method" " calls or signal calls." % { "cname" : class_name, "iname" : introspected.name, "kind" : { INTERFACE.CLIENT : "client-side", INTERFACE.SERVER : "server-side", INTERFACE.CLIENT_AND_SERVER : "client-and-server-side", }[kind] } ) proxy_factory.template = proxy return \ proxy_factory #end def_proxy_interface async def set_prop_flush(iface) : "iface must be either a BusPeer.RootProxy or BusPeer.Object.ProxyInterface" \ " instance; calls the set_prop_flush() method on the correct root proxy in" \ " either case." if isinstance(iface, BusPeer.RootProxy) : await iface.set_prop_flush() elif isinstance(iface, BusPeer.Object.ProxyInterface) : await iface._parent.set_prop_flush() else : raise TypeError("iface type %s is not a RootProxy or a ProxyInterface" % type(iface).__name__) #end if #end set_prop_flush #+ # Predefined interfaces #- @interface(INTERFACE.CLIENT_AND_SERVER, name = DBUS.INTERFACE_PEER) class PeerStub : "This is registered as a fallback at the root of your object tree to get" \ " automatic introspection of the DBUS.INTERFACE_PEER interface. The" \ " implementation is hard-coded inside libdbus itself, so the methods" \ " here will never be called." @method \ ( name = "Ping", in_signature = "", out_signature = "", ) def ping(self) : raise NotImplementedError("How did you get here?") #end ping @method \ ( name = "GetMachineId", in_signature = "", out_signature = "s", result_keys = ["machine_uuid"], ) def get_machine_id(self) : raise NotImplementedError("How did you get here?") #end get_machine_id #end PeerStub @interface(INTERFACE.CLIENT_AND_SERVER, name = DBUS.INTERFACE_INTROSPECTABLE) class IntrospectionHandler : "Register this as a fallback at the root of your object tree to obtain" \ " automatic introspection of any point in the tree." @method \ ( name = "Introspect", in_signature = "", out_signature = "s", path_keyword = "path", message_keyword = "message", bus_keyword = "bus", ) def introspect(self, message, bus, path) : interfaces = {} children = None # actually redundant level = bus._server_dispatch levels = iter(dbus.split_path(path)) while True : component = next(levels, None) for entry in level.interfaces.values() : if component == None or entry.fallback : interface = type(entry.interface) if interface._interface_kind != INTERFACE.CLIENT : interfaces[interface._interface_name] = interface # replace any higher-level entry for same name #end if #end if #end for if ( component == None # reached bottom of path or component not in level.children # no handlers to be found further down path ) : children = sorted(level.children.keys()) break #end if level = level.children[component] # search another step down the path #end while introspection = Introspection \ ( interfaces = list ( introspect(iface) for iface in sorted(interfaces.values(), key = lambda iface : iface._interface_name) ), nodes = list ( Introspection.Node(name = child) for child in children ) ) _send_method_return(bus.connection, message, "s", [introspection.unparse()]) return \ DBUS.HANDLER_RESULT_HANDLED #end introspect #end IntrospectionHandler @interface(INTERFACE.CLIENT_AND_SERVER, name = DBUS.INTERFACE_PROPERTIES) class PropertyHandler : "Register this as a fallback at the root of your object tree to provide" \ " automatic dispatching to any @propgetter() and @propsetter() methods" \ " defined for registered interfaces appropriate to an object path." @method \ ( name = "Get", in_signature = "ss", out_signature = "v", args_keyword = "args", path_keyword = "path", message_keyword = "message", bus_keyword = "bus" ) def getprop(self, bus, message, path, args) : interface_name, propname = args dispatch = bus.get_dispatch_interface(path, interface_name) props = type(dispatch)._interface_props if propname in props : propentry = props[propname] if "getter" in propentry : getter = getattr(dispatch, propentry["getter"].__name__) kwargs = {} for keyword_keyword, value in \ ( ("name_keyword", lambda : propname), ("connection_keyword", lambda : bus.connection), ("message_keyword", lambda : message), ("path_keyword", lambda : path), ("bus_keyword", lambda : bus), ) \ : if getter._propgetter_info[keyword_keyword] != None : kwargs[getter._propgetter_info[keyword_keyword]] = value() #end if #end for try : propvalue = getter(**kwargs) except ErrorReturn as err : propvalue = err.as_error() #end try if asyncio.iscoroutine(propvalue) : assert bus.loop != None, "no event loop to attach coroutine to" async def await_return_value(task) : propvalue = await task _send_method_return \ ( connection = bus.connection, message = message, sig = [dbus.VariantType()], args = [(propentry["type"], propvalue)] ) #end await_return_value bus.create_task(await_return_value(propvalue)) reply = None elif isinstance(propvalue, dbus.Error) : assert propvalue.is_set, "unset Error object returned from propgetter" reply = message.new_error(propvalue.name, propvalue.nessage) else : _send_method_return \ ( connection = bus.connection, message = message, sig = [dbus.VariantType()], args = [(propentry["type"], propvalue)] ) reply = None #end if else : reply = message.new_error \ ( name = DBUS.ERROR_ACCESS_DENIED, message = "property “%s” cannot be read" % propname ) #end if else : reply = message.new_error \ ( name = DBUS.ERROR_UNKNOWN_PROPERTY, message = "property “%s” cannot be found" % propname ) #end if if reply != None : bus.connection.send(reply) #end if return \ DBUS.HANDLER_RESULT_HANDLED #end getprop @method \ ( name = "Set", in_signature = "ssv", out_signature = "", args_keyword = "args", path_keyword = "path", message_keyword = "message", bus_keyword = "bus" ) def setprop(self, bus, message, path, args) : def notify_changed() : # sends property-changed signal if appropriate. if "getter" in propentry : notify = propentry["change_notification"] if notify == Introspection.PROP_CHANGE_NOTIFICATION.NEW_VALUE : bus.prop_changed(path, interface_name, propname, proptype, propvalue) elif notify == Introspection.PROP_CHANGE_NOTIFICATION.INVALIDATES : bus.prop_changed(path, interface_name, propname, None, None) #end if #end if #end notify_changed #begin setprop interface_name, propname, (proptype, propvalue) = args dispatch = bus.get_dispatch_interface(path, interface_name) props = type(dispatch)._interface_props if propname in props : propentry = props[propname] if "setter" in propentry : setter = getattr(dispatch, propentry["setter"].__name__) try : if propentry["type"] != None and propentry["type"] != dbus.parse_single_signature(proptype) : raise ErrorReturn \ ( name = DBUS.ERROR_INVALID_ARGS, message = "new property type %s does not match expected signature %s" % (proptype, dbus.unparse_signature(propentry["type"])) ) #end if kwargs = {} for keyword_keyword, value in \ ( ("name_keyword", lambda : propname), ("type_keyword", lambda : proptype), ("value_keyword", lambda : propvalue), ("connection_keyword", lambda : bus.connection), ("message_keyword", lambda : message), ("path_keyword", lambda : path), ("bus_keyword", lambda : bus), ) \ : if setter._propsetter_info[keyword_keyword] != None : kwargs[setter._propsetter_info[keyword_keyword]] = value() #end if #end for setresult = setter(**kwargs) except ErrorReturn as err : setresult = err.as_error() #end try if asyncio.iscoroutine(setresult) : assert bus.loop != None, "no event loop to attach coroutine to" async def wait_set_done() : await setresult reply = message.new_method_return() bus.connection.send(reply) notify_changed() #end wait_set_done bus.create_task(wait_set_done()) reply = None # for now elif isinstance(setresult, dbus.Error) : assert setresult.is_set, "unset Error object returned" reply = message.new_error(setresult.name, setresult.message) elif setresult == None : reply = message.new_method_return() notify_changed() else : raise ValueError("invalid propsetter result %s" % repr(setresult)) #end if else : reply = message.new_error \ ( name = DBUS.ERROR_PROPERTY_READ_ONLY, message = "property “%s” cannot be written" % propname ) #end if else : reply = message.new_error \ ( name = DBUS.ERROR_UNKNOWN_PROPERTY, message = "property “%s” cannot be found" % propname ) #end if if reply != None : bus.connection.send(reply) #end if return \ DBUS.HANDLER_RESULT_HANDLED #end setprop @method \ ( name = "GetAll", in_signature = "s", out_signature = "a{sv}", args_keyword = "args", path_keyword = "path", message_keyword = "message", bus_keyword = "bus" ) def get_all_props(self, bus, message, path, args) : properror = None propvalues = {} to_await = [] def return_result() : if properror != None : reply = message.new_error(properror.name, properror.message) bus.connection.send(reply) else : _send_method_return(bus.connection, message, "a{sv}", [propvalues]) #end if #end return_result async def await_propvalues() : nonlocal properror for propname, fute in to_await : try : propvalue = await fute except ErrorReturn as err : properror = err.as_error() break #end try propvalues[propname] = (propvalues[propname][0], propvalue) #end for return_result() #end await_propvalues #begin get_all_props interface_name, = args try : propvalues, to_await = bus._get_all_my_props(message, path, interface_name) except ErrorReturn as err : properror = err.as_error() #end try if len(to_await) != 0 : bus.create_task(await_propvalues()) else : return_result() #end if return \ DBUS.HANDLER_RESULT_HANDLED #end get_all_props @signal(name = "PropertiesChanged", in_signature = "sa{sv}as", stub = True) def properties_changed(self) : "for use with Connection.send_signal." pass #end properties_changed #end PropertyHandler @interface(INTERFACE.CLIENT_AND_SERVER, name = DBUSX.INTERFACE_OBJECT_MANAGER) class ManagedObjectsHandler : "Register this as a fallback at the root of your object tree to provide" \ " handling of the ObjectManager interface." @method \ ( name = "GetManagedObjects", in_signature = "", out_signature = "a{oa{sa{sv}}}", set_result_keyword = "set_result", bus_keyword = "bus", message_keyword = "message", path_keyword = "base_path", ) def get_managed_objects(self, bus, message, base_path, set_result) : result = {} to_await = [] async def await_propvalues() : for path, interface_name, propname, fute in to_await : propvalue = await fute # any ErrorReturn raised will be automatically converted # to error reply by _message_interface_dispatch (above) propvalues = result[path][interface_name] propvalues[propname] = (propvalues[propname][0], propvalue) #end for set_result([result]) #end await_propvalues #begin get_managed_objects "returns supported interfaces and current property values for all" \ " currently-existent managed objects." for path, interfaces in bus._managed_objects.items() : if base_path == "/" and path != "/" or path.startswith(base_path + "/") : obj_entry = {} for interface_name in interfaces : obj_entry[interface_name], await_props = \ bus._get_all_my_props(message, path, interface_name) for propname, propvalue in await_props : to_await.append((path, interface_name, propname, propvalue)) #end for #end for result[path] = obj_entry #end if #end for if len(to_await) != 0 : return_result = await_propvalues() # result will be available when this is done else : set_result([result]) return_result = None #end if return \ return_result #end get_managed_objects @signal(name = "InterfacesAdded", in_signature = "oa{sa{sv}}", stub = True) def interfaces_added(self) : "for use with Connection.send_signal." pass #end interfaces_added @signal(name = "InterfacesRemoved", in_signature = "oas", stub = True) def interfaces_removed(self) : "for use with Connection.send_signal." pass #end interfaces_removed #end ManagedObjectsHandler #+ # Cleanup #- def _atexit() : # disable all __del__ methods at process termination to avoid unpredictable behaviour for cls in Connection, Server : delattr(cls, "__del__") #end for #end _atexit atexit.register(_atexit) del _atexit dbussy-1.3/setup.cfg000066400000000000000000000007461366133523600145260ustar00rootroot00000000000000[metadata] license_file = COPYING platform = Linux classifiers = Programming Language :: Python :: 3.5 Development Status :: 4 - Beta Intended Audience :: Developers License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Operating System :: POSIX :: Linux Topic :: Software Development :: Libraries Topic :: Desktop Environment Topic :: Software Development :: Object Brokering [bdist_wheel] python-tag = py35 #plat-name = linux dbussy-1.3/setup.py000066400000000000000000000024161366133523600144130ustar00rootroot00000000000000#+ # Distutils script to install DBussy. Invoke from the command line # in this directory as follows: # # python3 setup.py build # sudo python3 setup.py install # # Written by Lawrence D'Oliveiro . #- import sys import distutils.core from distutils.command.build import \ build as std_build class my_build(std_build) : "customization of build to perform additional validation." def run(self) : try : exec \ ( "async def dummy() :\n" " pass\n" "#end dummy\n" ) except SyntaxError : sys.stderr.write("This module requires Python 3.5 or later.\n") sys.exit(-1) #end try super().run() #end run #end my_build distutils.core.setup \ ( name = "DBussy", version = "1.3", description = "language bindings for libdbus, for Python 3.5 or later", long_description = "language bindings for libdbus, for Python 3.5 or later", author = "Lawrence D'Oliveiro", author_email = "ldo@geek-central.gen.nz", url = "https://github.com/ldo/dbussy", license = "LGPL v2.1+", py_modules = ["dbussy", "ravel"], cmdclass = { "build" : my_build, }, )