emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] Support automatic D-Bus proxy generation


From: Daiki Ueno
Subject: [PATCH] Support automatic D-Bus proxy generation
Date: Wed, 25 Feb 2015 17:23:53 +0900
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.50 (gnu/linux)

For what it's worth, I've turned it into a patch (still work in
progress).  It ended up with a new module dbus-codegen.el, with two
different interfaces: one is a static version (`define-dbus-proxy'),
which takes an interface definition as an argument and expands at
compile-time.  The other is a dynamic version (`make-dbus-proxy'), which
retrieves the interface through introspection.

I initially thought that it might fit in dbus.el, but it would be better
to keep it essential and not to bother with the boring code-generating
code.

address@hidden writes:

> I use Jan Moringens dbus-proxy in my Inkmacs project, which is an Emacs
> interface for Inkscape. 

Nice.  I'm playing with it as an example:
https://github.com/ueno/inkmacs/commit/d5835d2b
It seems partly working (I got 'dbus-call-method: D-Bus error: "Object
'inkmacs-flow-layer' not found in document."', maybe my programming
error somewhere).

Regards,
--
Daiki Ueno
>From 2a01d1fc73017cb2550d1ec47207fd1f0427e8b5 Mon Sep 17 00:00:00 2001
From: Daiki Ueno <address@hidden>
Date: Wed, 25 Feb 2015 16:25:30 +0900
Subject: [PATCH] Support automatic D-Bus proxy generation

* lisp/net/dbus-codegen.el: New file.
---
 lisp/net/dbus-codegen.el | 329 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 329 insertions(+)
 create mode 100644 lisp/net/dbus-codegen.el

diff --git a/lisp/net/dbus-codegen.el b/lisp/net/dbus-codegen.el
new file mode 100644
index 0000000..e2550f9
--- /dev/null
+++ b/lisp/net/dbus-codegen.el
@@ -0,0 +1,329 @@
+;;; dbus-codegen.el --- Lisp code generation for D-Bus. -*- lexical-biding: t; 
-*-
+
+;; Copyright (C) 2015 Free Software Foundation, Inc.
+
+;; Author: Daiki Ueno <address@hidden>
+;; Keywords: comm, hardware
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs 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 General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package provides two interfaces to make D-Bus proxy
+;; implementation easy.  One is `define-dbus-proxy', which takes a
+;; static definition of a D-Bus service and generates code at
+;; byte-compilation time.  The following code defines
+;; `search-provider-make' and
+;; `search-provider-get-initial-result-set'.
+;;
+;;   (define-dbus-proxy search-provider "\
+;;   <node>
+;;     <interface name=\"org.gnome.Shell.SearchProvider2\">
+;;       <method name=\"GetInitialResultSet\">
+;;         <arg type=\"as\" name=\"terms\" direction=\"in\" />
+;;         <arg type=\"as\" name=\"results\" direction=\"out\" />
+;;       </method>
+;;     </interface>
+;;   </node>"
+;;     "org.gnome.Shell.SearchProvider2"
+;;     :transform-name #'dbus-codegen-transform-name)
+;;
+;; This is good for stable D-Bus services.
+
+;; The other is `make-dbus-proxy', which retrieves the D-Bus service
+;; definition from the running service itself through D-Bus
+;; introspection.  This is good for unstable D-Bus services.
+
+;;; Code:
+
+(require 'dbus)
+(require 'xml)
+(require 'cl-lib)
+(require 'subword)
+
+;; Base type of a D-Bus proxy.
+(cl-defstruct (dbus-proxy
+              (:constructor nil))
+  (bus :read-only t)
+  (service :read-only t)
+  (path :read-only t))
+
+;; Return a list of elements in the form: (LISP-NAME ORIG-NAME MEMBER).
+(defun dbus-codegen--apply-transform-name (elements transform-name)
+  (mapcar (lambda (elements)
+           (let ((name (xml-get-attribute-or-nil elements 'name)))
+               (unless name
+                 (error "missing \"name\" attribute of %s"
+                        (xml-node-name elements)))
+               (list (funcall transform-name name)
+                     name
+                     elements)))
+         elements))
+
+;; Return a list of symbols.
+(defun dbus-codegen--collect-arglist (args transform-name)
+  (delq nil
+       (mapcar
+        (lambda (arg)
+          (let ((direction
+                 (xml-get-attribute-or-nil (nth 2 arg) 'direction)))
+            (if (or (null direction)
+                    (equal direction "in"))
+                (intern (car arg)))))
+        (dbus-codegen--apply-transform-name args transform-name))))
+
+(defun dbus-codegen-transform-name (name)
+  "Transform NAME into suitable Lisp function name."
+  (with-temp-buffer
+    (let (words)
+      (insert name)
+      (goto-char (point-min))
+      (while (not (eobp))
+       ;; Skip characters not recognized by subword-mode.
+       (if (looking-at "[^[:lower:][:upper:][:digit:]]+")
+           (goto-char (match-end 0)))
+       (push (downcase (buffer-substring (point) (progn (subword-forward 1)
+                                                        (point))))
+             words))
+      (mapconcat #'identity (nreverse words) "-"))))
+
+;;;###autoload
+(defmacro define-dbus-proxy (name xml interface &rest args)
+  "Define a new D-Bus proxy NAME.
+This defines a new struct type for the proxy and convenient
+functions for D-Bus method calls and signal registration.
+
+XML is either a string which defines the interface of the D-Bus
+proxy, or a tree already parsed with `xml-parse-file'.  It must
+comply with the standard D-Bus introspection XML format, and can
+contain only a single \"interface\" element under the root
+\"node\" element.
+
+INTERFACE is an interface which is represented by this proxy.
+
+ARGS are keyword-value pair.  Currently only one keyword is
+supported:
+
+:transform-name FUNCTION -- FUNCTION is a function which converts
+D-Bus method/signal/property names, into another representation.
+Use `dbus-codegen-transform-name' to convert all
+camel-cased names to suitable Lisp function names."
+  (unless (symbolp name)
+    (signal 'wrong-type-argument (list 'symbolp name)))
+  (unless (stringp xml)
+    (signal 'wrong-type-argument (list 'stringp xml)))
+  (let ((node (if (stringp xml)
+                 (car (with-temp-buffer
+                        (insert xml)
+                        (xml-parse-region (point-min) (point-max))))
+               xml))
+       (transform-name (or (plist-get args :transform-name)
+                           #'identity)))
+    (unless (eq (xml-node-name node) 'node)
+      (error "Root is not \"node\""))
+    (unless (functionp transform-name)
+      (setq transform-name (eval transform-name)))
+    (let ((interface-node
+          (cl-find-if (lambda (element)
+                        (equal (xml-get-attribute-or-nil element 'name)
+                               interface))
+                      (xml-get-children node 'interface))))
+      (unless interface-node
+       (error "Interface %s is missing" interface))
+      (let ((methods (dbus-codegen--apply-transform-name
+                     (xml-get-children interface-node 'method)
+                     transform-name))
+           (properties (dbus-codegen--apply-transform-name
+                        (xml-get-children interface-node 'properties)
+                        transform-name))
+           (signals (dbus-codegen--apply-transform-name
+                     (xml-get-children interface-node 'signals)
+                     transform-name)))
+       `(progn
+          ;; Define a new struct.
+          (cl-defstruct (,name (:include dbus-proxy)
+                               (:constructor nil)
+                               (:constructor ,(intern (format "%s--make" name))
+                                             (bus service path)))
+            ;; Slots for cached property values.
+            ,@(mapcar
+               (lambda (property)
+                 (intern (car property)))
+               properties))
+
+          (defun ,(intern (format "%s-make" name)) (bus service path)
+            ,(format "Create a new D-Bus proxy for %s.
+
+BUS is either a Lisp symbol, `:system' or `:session', or a string
+denoting the bus address.
+
+SERVICE is the D-Bus service name to be used.  PATH is the D-Bus
+object path SERVICE is registered at.  INTERFACE is an interface
+offered by SERVICE."
+                     interface)
+            (let ((proxy (,(intern (format "%s--make" name))
+                          bus service path)))
+              ,(when (and properties
+                          ;; FIXME: See the handler definition below.
+                          lexical-binding)
+                 ;; Initialize slots.
+                 `(let ((properties (dbus-get-all-properties bus service path
+                                                             ,interface)))
+                    ,@(mapcar
+                       (lambda (property)
+                         `(setf (,(intern (format "%s-%s" name (car property)))
+                                 proxy)
+                                (cdr (assoc ,(nth 1 property) properties))))
+                       properties)
+                    (dbus-register-signal
+                     bus service path dbus-interface-properties
+                     "PropertiesChanged"
+                     (lambda (interface changed invalidated)
+                       (funcall
+                        ,(intern (format "%s--handle-properties-changed"
+                                         name))
+                        proxy
+                        interface changed invalidated)))))
+              proxy))
+
+          ;; Define a handler of PropertiesChanged signal.
+          (defun ,(intern (format "%s--handle-properties-changed" name))
+              (proxy interface changed invalidated)
+            (when (equal interface ,interface)
+              ,@(mapcar
+                 (lambda (property)
+                   `(setf (,(intern (format "%s-%s" name (car property)))
+                           proxy)
+                          (cdr (assoc ,(nth 1 property) changed))))
+                 properties)))
+
+          ;; Define wrappers around `dbus-call-method'.
+          ,@(mapcar
+             (lambda (method)
+               (let ((arglist (dbus-codegen--collect-arglist
+                               (xml-get-children method 'arg)
+                               transform-name)))
+                 `(cl-defmethod
+                      ,(intern (format "%s-%s" name (car method)))
+                      ((proxy ,name) ,@arglist &rest args)
+                    (apply #'dbus-call-method
+                           (dbus-proxy-bus proxy)
+                           (dbus-proxy-service proxy)
+                           (dbus-proxy-path proxy)
+                           ,interface
+                           ,(nth 1 method)
+                           ,@arglist
+                           args))))
+             methods)
+
+          ;; Define wrappers around `dbus-call-method-asynchronously'.
+          ,@(mapcar
+             (lambda (method)
+               (let ((arglist (dbus-codegen--collect-arglist
+                               (xml-get-children method 'arg)
+                               transform-name)))
+                 `(cl-defmethod
+                      ,(intern (format "%s-%s-asynchronously"
+                                       name (car method)))
+                      ((proxy ,name) ,@arglist handler &rest args)
+                    (apply #'dbus-call-method-asynchronously
+                           (dbus-proxy-bus proxy)
+                           (dbus-proxy-service proxy)
+                           (dbus-proxy-path proxy)
+                           ,interface
+                           ,(nth 1 method)
+                           handler
+                           ,@arglist
+                           args))))
+             methods)
+
+          ;; Define wrappers around `dbus-register-signal'.
+          ,@(mapcar
+             (lambda (signal)
+               `(cl-defmethod
+                    ,(intern (format "%s-register-%s-signal"
+                                     name (car signal)))
+                    ((proxy ,name) handler &rest args)
+                  (apply #'dbus-register-signal
+                         (dbus-proxy-bus proxy)
+                         (dbus-proxy-service proxy)
+                         (dbus-proxy-path proxy)
+                         ,interface
+                         ,(nth 1 signal)
+                         handler
+                         args)))
+             signals)
+
+          ;; Define wrappers around `dbus-send-signal'.
+          ,@(mapcar
+             (lambda (signal)
+               (let ((arglist (dbus-codegen--collect-arglist
+                               (xml-get-children signal 'arg)
+                               transform-name)))
+                 `(cl-defmethod
+                      ,(intern (format "%s-send-%s-signal"
+                                       name (car signal)))
+                      ((proxy ,name) ,@arglist &rest args)
+                    (apply #'dbus-register-signal
+                           (dbus-proxy-bus proxy)
+                           (dbus-proxy-service proxy)
+                           (dbus-proxy-path proxy)
+                           ,interface
+                           ,(nth 1 signal)
+                           ,@arglist
+                           args))))
+             signals))))))
+
+;;;###autoload
+(defun make-dbus-proxy (name bus service path interface &rest args)
+  "Create a new D-Bus proxy based on the introspection data.
+
+If the data type of the D-Bus proxy is not yet defined, this will
+define it with `define-dbus-proxy', under a type name NAME.
+
+BUS is either a Lisp symbol, `:system' or `:session', or a string
+denoting the bus address.
+
+SERVICE is the D-Bus service name to be used.  PATH is the D-Bus
+object path SERVICE is registered at.  INTERFACE is an interface
+offered by SERVICE.
+
+INTERFACE is an interface which is represented by this proxy.
+
+ARGS are keyword-value pair.  Currently only one keyword is
+supported:
+
+:redefine FLAG -- if FLAG is non-nil, redefine the data type and
+associated functions.
+
+Other keywords are same as `define-dbus-proxy'."
+  (let ((constructor (intern (format "%s-make" name))))
+    (if (or (plist-get args :redefine)
+           (not (fboundp constructor)))
+       (eval `(define-dbus-proxy ,(intern name)
+                ,(dbus-introspect bus service path)
+                ,interface
+                ,@args)))
+    (funcall constructor bus service path)))
+
+(provide 'dbus-codegen)
+
+;;; TODO
+
+;; * Property setters
+;; * Server-side code generation
+
+;;; dbus-codegen.el ends here
-- 
2.1.0


reply via email to

[Prev in Thread] Current Thread [Next in Thread]