diff --git a/doc/misc/dbus.texi b/doc/misc/dbus.texi index f3e42f3060c..7fad406520c 100644 --- a/doc/misc/dbus.texi +++ b/doc/misc/dbus.texi @@ -1240,6 +1240,8 @@ running): "org.freedesktop.systemd1.Manager" "RestartUnit" :authorizable t "bluetooth.service" "replace") + +@result{} "/org/freedesktop/systemd1/job/17508" @end lisp The remaining arguments @var{args} are passed to @var{method} as @@ -1752,6 +1754,8 @@ arguments. They are converted into D-Bus types as described in :session nil dbus-path-emacs (concat dbus-interface-emacs ".FileManager") "FileModified" "/home/albinus/.emacs") + +@result{} nil @end lisp @end defun @@ -1779,7 +1783,10 @@ argument. @var{handler} is a Lisp function to be called when the @var{signal} is received. It must accept as arguments the output parameters -@var{signal} is sending. +@var{signal} is sending.@footnote{It is possible to register different +handlers for the same signal. All registered handlers will be called +when the signal arrives. This is useful for example if different Lisp +packages are interested in the same signal.} The remaining arguments @var{args} can be keywords or keyword string pairs.@footnote{For backward compatibility, the arguments @var{args} @@ -2178,12 +2185,16 @@ The following form shows all D-Bus events on the session bus in buffer @lisp (dbus-register-monitor :session) + +@result{} ((:monitor :session-private) (nil nil dbus-monitor-handler)) @end lisp And this form restricts the monitoring on D-Bus errors: @lisp (dbus-register-monitor :session nil :type "error") + +@result{} ((:monitor :session-private) (nil nil dbus-monitor-handler)) @end lisp @end defun diff --git a/src/dbusbind.c b/src/dbusbind.c index b79715232fb..a2936011610 100644 --- a/src/dbusbind.c +++ b/src/dbusbind.c @@ -1617,14 +1617,32 @@ usage: (dbus-message-internal &rest REST) */) return result; } +/* Construct a D-Bus event, and store it into the input event queue. */ +static void +xd_store_event (Lisp_Object handler, Lisp_Object handler_args, + Lisp_Object event_args) +{ + struct input_event event; + EVENT_INIT (event); + event.kind = DBUS_EVENT; + event.frame_or_window = Qnil; + /* Handler and handler args. */ + event.arg = Fcons (handler, handler_args); + /* Event args. */ + event.arg = CALLN (Fappend, event_args, event.arg); + /* Store it into the input event queue. */ + kbd_buffer_store_event (&event); + + XD_DEBUG_MESSAGE ("Event stored: %s", XD_OBJECT_TO_STRING (event.arg)); +} + /* Read one queued incoming message of the D-Bus BUS. BUS is either a Lisp symbol, :system, :session, :system-private or :session-private, or a string denoting the bus address. */ static void xd_read_message_1 (DBusConnection *connection, Lisp_Object bus) { - Lisp_Object args, key, value; - struct input_event event; + Lisp_Object args, event_args, key, value; DBusMessage *dmessage; DBusMessageIter iter; int dtype; @@ -1676,6 +1694,27 @@ xd_read_message_1 (DBusConnection *connection, Lisp_Object bus) mtype == DBUS_MESSAGE_TYPE_ERROR ? error_name : member, XD_OBJECT_TO_STRING (args)); + /* Add type, serial, uname, destination, path, interface and member + or error_name to the event_args. */ + event_args + = Fcons (mtype == DBUS_MESSAGE_TYPE_ERROR + ? error_name == NULL ? Qnil : build_string (error_name) + : member == NULL ? Qnil : build_string (member), + Qnil); + event_args = Fcons ((interface == NULL ? Qnil : build_string (interface)), + event_args); + event_args = Fcons ((path == NULL ? Qnil : build_string (path)), + event_args); + event_args = Fcons ((destination == NULL ? Qnil : build_string (destination)), + event_args); + event_args = Fcons ((uname == NULL ? Qnil : build_string (uname)), + event_args); + event_args = Fcons (INT_TO_INTEGER (serial), event_args); + event_args = Fcons (make_fixnum (mtype), event_args); + + /* Add the bus symbol to the event. */ + event_args = Fcons (bus, event_args); + if (mtype == DBUS_MESSAGE_TYPE_INVALID) goto cleanup; @@ -1693,12 +1732,8 @@ xd_read_message_1 (DBusConnection *connection, Lisp_Object bus) /* Remove the entry. */ Fremhash (key, Vdbus_registered_objects_table); - /* Construct an event. */ - EVENT_INIT (event); - event.kind = DBUS_EVENT; - event.frame_or_window = Qnil; - /* Handler. */ - event.arg = Fcons (value, args); + /* Store the event. */ + xd_store_event (value, args, event_args); } else /* DBUS_MESSAGE_TYPE_METHOD_CALL, DBUS_MESSAGE_TYPE_SIGNAL. */ @@ -1729,6 +1764,7 @@ xd_read_message_1 (DBusConnection *connection, Lisp_Object bus) Fgethash (key, Vdbus_registered_objects_table, Qnil)); } + Lisp_Object called_handlers = Qnil; /* Loop over the registered functions. Construct an event. */ for (; !NILP (value); value = CDR_SAFE (value)) { @@ -1747,45 +1783,15 @@ xd_read_message_1 (DBusConnection *connection, Lisp_Object bus) Lisp_Object handler = CAR_SAFE (CDR_SAFE (key_path_etc)); if (NILP (handler)) continue; + if (!NILP (memq_no_quit (handler, called_handlers))) + continue; + called_handlers = Fcons (handler, called_handlers); - /* Construct an event and exit the loop. */ - EVENT_INIT (event); - event.kind = DBUS_EVENT; - event.frame_or_window = Qnil; - event.arg = Fcons (handler, args); - break; + /* Store the event. */ + xd_store_event (handler, args, event_args); } - - if (NILP (value)) - goto monitor; } - /* Add type, serial, uname, destination, path, interface and member - or error_name to the event. */ - event.arg - = Fcons (mtype == DBUS_MESSAGE_TYPE_ERROR - ? error_name == NULL ? Qnil : build_string (error_name) - : member == NULL ? Qnil : build_string (member), - event.arg); - event.arg = Fcons ((interface == NULL ? Qnil : build_string (interface)), - event.arg); - event.arg = Fcons ((path == NULL ? Qnil : build_string (path)), - event.arg); - event.arg = Fcons ((destination == NULL ? Qnil : build_string (destination)), - event.arg); - event.arg = Fcons ((uname == NULL ? Qnil : build_string (uname)), - event.arg); - event.arg = Fcons (INT_TO_INTEGER (serial), event.arg); - event.arg = Fcons (make_fixnum (mtype), event.arg); - - /* Add the bus symbol to the event. */ - event.arg = Fcons (bus, event.arg); - - /* Store it into the input event queue. */ - kbd_buffer_store_event (&event); - - XD_DEBUG_MESSAGE ("Event stored: %s", XD_OBJECT_TO_STRING (event.arg)); - /* Monitor. */ monitor: /* Search for a registered function of the message. */ @@ -1796,39 +1802,9 @@ xd_read_message_1 (DBusConnection *connection, Lisp_Object bus) if (NILP (value)) goto cleanup; - /* Construct an event. */ - EVENT_INIT (event); - event.kind = DBUS_EVENT; - event.frame_or_window = Qnil; - - /* Add type, serial, uname, destination, path, interface, member - or error_name and handler to the event. */ - event.arg - = Fcons (CAR_SAFE (CDR_SAFE (CDR_SAFE (CDR_SAFE (CAR_SAFE (value))))), - args); - event.arg - = Fcons (mtype == DBUS_MESSAGE_TYPE_ERROR - ? error_name == NULL ? Qnil : build_string (error_name) - : member == NULL ? Qnil : build_string (member), - event.arg); - event.arg = Fcons ((interface == NULL ? Qnil : build_string (interface)), - event.arg); - event.arg = Fcons ((path == NULL ? Qnil : build_string (path)), - event.arg); - event.arg = Fcons ((destination == NULL ? Qnil : build_string (destination)), - event.arg); - event.arg = Fcons ((uname == NULL ? Qnil : build_string (uname)), - event.arg); - event.arg = Fcons (INT_TO_INTEGER (serial), event.arg); - event.arg = Fcons (make_fixnum (mtype), event.arg); - - /* Add the bus symbol to the event. */ - event.arg = Fcons (bus, event.arg); - - /* Store it into the input event queue. */ - kbd_buffer_store_event (&event); - - XD_DEBUG_MESSAGE ("Monitor event stored: %s", XD_OBJECT_TO_STRING (event.arg)); + /* Store the event. */ + xd_store_event (CAR_SAFE (CDR_SAFE (CDR_SAFE (CDR_SAFE (CAR_SAFE (value))))), + args, event_args); /* Cleanup. */ cleanup: diff --git a/test/lisp/net/dbus-tests.el b/test/lisp/net/dbus-tests.el index 3490bebd8d6..e529e02ed9b 100644 --- a/test/lisp/net/dbus-tests.el +++ b/test/lisp/net/dbus-tests.el @@ -607,6 +607,7 @@ This includes initialization and closing the bus." (let ((method1 "Method1") (method2 "Method2") (handler #'dbus--test-method-handler) + dbus-debug ; There would be errors otherwise. registered) ;; The service is not registered yet. @@ -759,6 +760,7 @@ Returns the respective error." (unwind-protect (let ((method "Method") (handler #'dbus--test-method-authorizable-handler) + dbus-debug ; There would be errors otherwise. registered) ;; Register. @@ -850,7 +852,7 @@ Returns the respective error." (dbus-event-path-name dbus--test-event-expected)) (equal (dbus-event-member-name last-input-event) (dbus-event-member-name dbus--test-event-expected)))) - (setq dbus--test-signal-received args))))) + (push args dbus--test-signal-received))))) (defun dbus--test-timeout-handler (&rest _ignore) "Timeout handler, reporting a failed test." @@ -885,7 +887,7 @@ Returns the respective error." (with-timeout (1 (dbus--test-timeout-handler)) (while (null dbus--test-signal-received) (read-event nil nil 0.1))) - (should (equal dbus--test-signal-received '("foo"))) + (should (equal dbus--test-signal-received '(("foo")))) ;; Send two arguments, compound types. (setq dbus--test-signal-received nil) @@ -896,7 +898,7 @@ Returns the respective error." (with-timeout (1 (dbus--test-timeout-handler)) (while (null dbus--test-signal-received) (read-event nil nil 0.1))) - (should (equal dbus--test-signal-received '((1 2 3) ("bar")))) + (should (equal dbus--test-signal-received '(((1 2 3) ("bar"))))) ;; Unregister signal. (should (dbus-unregister-object registered)) @@ -905,6 +907,86 @@ Returns the respective error." ;; Cleanup. (dbus-unregister-service :session dbus--test-service))) +(defun dbus--test-signal-handler1 (&rest args) + "Signal handler for `dbus-test05-register-signal-several-handlers'." + ;; (message "dbus--test-signal-handler1 %S" last-input-event) + (dbus--test-signal-handler (cons "dbus--test-signal-handler1" args))) + +(defun dbus--test-signal-handler2 (&rest args) + "Signal handler for `dbus-test05-register-signal-several-handlers'." + ;; (message "dbus--test-signal-handler2 %S" last-input-event) + (dbus--test-signal-handler (cons "dbus--test-signal-handler2" args))) + +(ert-deftest dbus-test05-register-signal-several-handlers () + "Check signal registration for an own service. +It shall call several handlers per received signal." + (skip-unless dbus--test-enabled-session-bus) + (dbus-ignore-errors (dbus-unregister-service :session dbus--test-service)) + + (unwind-protect + (let ((member "Member") + (handler1 #'dbus--test-signal-handler1) + (handler2 #'dbus--test-signal-handler2) + registered1 registered2) + + ;; Register signal handlers. + (should + (equal + (setq + registered1 + (dbus-register-signal + :session dbus--test-service dbus--test-path + dbus--test-interface member handler1)) + `((:signal :session ,dbus--test-interface ,member) + (,dbus--test-service ,dbus--test-path ,handler1)))) + (should + (equal + (setq + registered2 + (dbus-register-signal + :session dbus--test-service dbus--test-path + dbus--test-interface member handler2)) + `((:signal :session ,dbus--test-interface ,member) + (,dbus--test-service ,dbus--test-path ,handler2)))) + + ;; Send one argument, basic type. + (setq dbus--test-signal-received nil) + (dbus-send-signal + :session dbus--test-service dbus--test-path + dbus--test-interface member "foo") + (with-timeout (1 (dbus--test-timeout-handler)) + (while (length< dbus--test-signal-received 2) + (read-event nil nil 0.1))) + (should + (member + '(("dbus--test-signal-handler1" "foo")) dbus--test-signal-received)) + (should + (member + '(("dbus--test-signal-handler2" "foo")) dbus--test-signal-received)) + + ;; Unregister one signal. + (should (dbus-unregister-object registered1)) + (should-not (dbus-unregister-object registered1)) + + ;; Send one argument, basic type. + (setq dbus--test-signal-received nil) + (dbus-send-signal + :session dbus--test-service dbus--test-path + dbus--test-interface member "foo") + (with-timeout (1 (dbus--test-timeout-handler)) + (while (null dbus--test-signal-received) + (read-event nil nil 0.1))) + (should + (equal + dbus--test-signal-received '((("dbus--test-signal-handler2" "foo"))))) + + ;; Unregister the other signal. + (should (dbus-unregister-object registered2)) + (should-not (dbus-unregister-object registered2))) + + ;; Cleanup. + (dbus-unregister-service :session dbus--test-service))) + (ert-deftest dbus-test05-register-signal-with-nils () "Check signal registration for an own service. SERVICE, PATH, INTERFACE and SIGNAL are ‘nil’. This is interpreted as a @@ -956,7 +1038,7 @@ wildcard for the respective argument." (with-timeout (1 (dbus--test-timeout-handler)) (while (null dbus--test-signal-received) (read-event nil nil 0.1))) - (should (equal dbus--test-signal-received '("foo"))) + (should (equal dbus--test-signal-received '(("foo")))) ;; Unregister signal. (should (dbus-unregister-object registered)) @@ -1317,7 +1399,7 @@ wildcard for the respective argument." ;; "invalidated_properties" (an array of strings). (should (equal dbus--test-signal-received - `(,dbus--test-interface ((,property ("foo"))) ()))) + `((,dbus--test-interface ((,property ("foo"))) ())))) (should (equal @@ -1341,7 +1423,7 @@ wildcard for the respective argument." (should (equal dbus--test-signal-received - `(,dbus--test-interface ((,property ((1 2 3)))) ()))) + `((,dbus--test-interface ((,property ((1 2 3)))) ())))) (should (equal