Merge tag 'dbus-pull-request' of https://gitlab.com/marcandre.lureau/qemu into staging

Add D-Bus display backend

# gpg: Signature made Mon 20 Dec 2021 10:57:18 PM PST
# gpg:                using RSA key 87A9BD933F87C606D276F62DDAE8E10975969CE5
# gpg:                issuer "marcandre.lureau@redhat.com"
# gpg: Good signature from "Marc-André Lureau <marcandre.lureau@redhat.com>" [unknown]
# gpg:                 aka "Marc-André Lureau <marcandre.lureau@gmail.com>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: 87A9 BD93 3F87 C606 D276  F62D DAE8 E109 7596 9CE5

* tag 'dbus-pull-request' of https://gitlab.com/marcandre.lureau/qemu: (36 commits)
  MAINTAINERS: update D-Bus section
  ui/dbus: register D-Bus VC handler
  ui/dbus: add chardev backend & interface
  option: add g_auto for QemuOpts
  chardev: make socket derivable
  chardev: teach socket to accept no addresses
  ui/dbus: add clipboard interface
  audio: add "dbus" audio backend
  tests: start dbus-display-test
  tests/qtests: add qtest_qmp_add_client()
  ui/dbus: add p2p=on/off option
  ui: add a D-Bus display backend
  build-sys: set glib dependency version
  docs: add dbus-display documentation
  docs: move D-Bus VMState documentation to source XML
  backends: move dbus-vmstate1.xml to backends/
  docs/sphinx: add sphinx modules to include D-Bus documentation
  scripts: teach modinfo to skip non-C sources
  console: save current scanout details
  ui: move qemu_spice_fill_device_address to ui/util.c
  ...

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson
2021-12-21 08:00:26 -08:00
79 changed files with 6249 additions and 401 deletions
+7 -3
View File
@@ -2873,11 +2873,15 @@ D-Bus
M: Marc-André Lureau <marcandre.lureau@redhat.com>
S: Maintained
F: backends/dbus-vmstate.c
F: tests/dbus-vmstate*
F: ui/dbus*
F: audio/dbus*
F: util/dbus.c
F: include/ui/dbus*
F: include/qemu/dbus.h
F: docs/interop/dbus.rst
F: docs/interop/dbus-vmstate.rst
F: docs/interop/dbus*
F: docs/sphinx/dbus*
F: docs/sphinx/fakedbusdoc.py
F: tests/qtest/dbus*
Seccomp
M: Eduardo Otubo <otubo@redhat.com>
+1
View File
@@ -2000,6 +2000,7 @@ void audio_create_pdos(Audiodev *dev)
CASE(NONE, none, );
CASE(ALSA, alsa, Alsa);
CASE(COREAUDIO, coreaudio, Coreaudio);
CASE(DBUS, dbus, );
CASE(DSOUND, dsound, );
CASE(JACK, jack, Jack);
CASE(OSS, oss, Oss);
+7
View File
@@ -31,6 +31,10 @@
#endif
#include "mixeng.h"
#ifdef CONFIG_GIO
#include <gio/gio.h>
#endif
struct audio_pcm_ops;
struct audio_callback {
@@ -140,6 +144,9 @@ struct audio_driver {
const char *descr;
void *(*init) (Audiodev *);
void (*fini) (void *);
#ifdef CONFIG_GIO
void (*set_dbus_server) (AudioState *s, GDBusObjectManagerServer *manager);
#endif
struct audio_pcm_ops *pcm_ops;
int can_be_default;
int max_voices_out;
+2
View File
@@ -327,6 +327,8 @@ AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev)
case AUDIODEV_DRIVER_COREAUDIO:
return qapi_AudiodevCoreaudioPerDirectionOptions_base(
dev->u.coreaudio.TYPE);
case AUDIODEV_DRIVER_DBUS:
return dev->u.dbus.TYPE;
case AUDIODEV_DRIVER_DSOUND:
return dev->u.dsound.TYPE;
case AUDIODEV_DRIVER_JACK:
+654
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -26,4 +26,10 @@ foreach m : [
endif
endforeach
if dbus_display
module_ss = ss.source_set()
module_ss.add(when: gio, if_true: files('dbusaudio.c'))
audio_modules += {'dbus': module_ss}
endif
modules += {'audio': audio_modules}
+5
View File
@@ -13,6 +13,11 @@ alsa_resume_out(void) "Resuming suspended output stream"
# ossaudio.c
oss_version(int version) "OSS version = 0x%x"
# dbusaudio.c
dbus_audio_register(const char *s, const char *dir) "sender = %s, dir = %s"
dbus_audio_put_buffer_out(size_t len) "len = %zu"
dbus_audio_read(size_t len) "len = %zu"
# audio.c
audio_timer_start(int interval) "interval %d ms"
audio_timer_stop(void) ""
+52
View File
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<!--
org.qemu.VMState1:
This interface must be implemented at the object path
``/org/qemu/VMState1`` to support helper migration.
-->
<interface name="org.qemu.VMState1">
<!--
Id:
A string that identifies the helper uniquely. (maximum 256 bytes
including terminating NUL byte)
.. note::
The VMState helper ID namespace is its own namespace. In particular,
it is not related to QEMU "id" used in -object/-device objects.
-->
<property name="Id" type="s" access="read"/>
<!--
Load:
@data: data to restore the state.
The method called on destination with the state to restore.
The helper may be initially started in a waiting state (with an
``-incoming`` argument for example), and it may resume on success.
An error may be returned to the caller.
-->
<method name="Load">
<arg type="ay" name="data" direction="in"/>
</method>
<!--
Save:
@data: state data to save for later resume.
The method called on the source to get the current state to be
migrated. The helper should continue to run normally.
An error may be returned to the caller.
-->
<method name="Save">
<arg type="ay" name="data" direction="out"/>
</method>
</interface>
</node>
+10 -62
View File
@@ -25,9 +25,7 @@
#include "qemu/osdep.h"
#include "chardev/char.h"
#include "io/channel-socket.h"
#include "io/channel-tls.h"
#include "io/channel-websock.h"
#include "io/net-listener.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "qemu/option.h"
@@ -37,61 +35,7 @@
#include "qemu/yank.h"
#include "chardev/char-io.h"
#include "qom/object.h"
/***********************************************************/
/* TCP Net console */
#define TCP_MAX_FDS 16
typedef struct {
char buf[21];
size_t buflen;
} TCPChardevTelnetInit;
typedef enum {
TCP_CHARDEV_STATE_DISCONNECTED,
TCP_CHARDEV_STATE_CONNECTING,
TCP_CHARDEV_STATE_CONNECTED,
} TCPChardevState;
struct SocketChardev {
Chardev parent;
QIOChannel *ioc; /* Client I/O channel */
QIOChannelSocket *sioc; /* Client master channel */
QIONetListener *listener;
GSource *hup_source;
QCryptoTLSCreds *tls_creds;
char *tls_authz;
TCPChardevState state;
int max_size;
int do_telnetopt;
int do_nodelay;
int *read_msgfds;
size_t read_msgfds_num;
int *write_msgfds;
size_t write_msgfds_num;
bool registered_yank;
SocketAddress *addr;
bool is_listen;
bool is_telnet;
bool is_tn3270;
GSource *telnet_source;
TCPChardevTelnetInit *telnet_init;
bool is_websock;
GSource *reconnect_timer;
int64_t reconnect_time;
bool connect_err_reported;
QIOTask *connect_task;
};
typedef struct SocketChardev SocketChardev;
DECLARE_INSTANCE_CHECKER(SocketChardev, SOCKET_CHARDEV,
TYPE_CHARDEV_SOCKET)
#include "chardev/char-socket.h"
static gboolean socket_reconnect_timeout(gpointer opaque);
static void tcp_chr_telnet_init(Chardev *chr);
@@ -1248,6 +1192,10 @@ static int qmp_chardev_open_socket_server(Chardev *chr,
qio_net_listener_set_name(s->listener, name);
g_free(name);
if (s->addr->type == SOCKET_ADDRESS_TYPE_FD && !*s->addr->u.fd.str) {
goto skip_listen;
}
if (qio_net_listener_open_sync(s->listener, s->addr, 1, errp) < 0) {
object_unref(OBJECT(s->listener));
s->listener = NULL;
@@ -1256,6 +1204,8 @@ static int qmp_chardev_open_socket_server(Chardev *chr,
qapi_free_SocketAddress(s->addr);
s->addr = socket_local_address(s->listener->sioc[0]->fd, errp);
skip_listen:
update_disconnected_filename(s);
if (is_waitconnect) {
@@ -1466,9 +1416,9 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
SocketAddressLegacy *addr;
ChardevSocket *sock;
if ((!!path + !!fd + !!host) != 1) {
if ((!!path + !!fd + !!host) > 1) {
error_setg(errp,
"Exactly one of 'path', 'fd' or 'host' required");
"None or one of 'path', 'fd' or 'host' option required.");
return;
}
@@ -1542,12 +1492,10 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
.has_ipv6 = qemu_opt_get(opts, "ipv6"),
.ipv6 = qemu_opt_get_bool(opts, "ipv6", 0),
};
} else if (fd) {
} else {
addr->type = SOCKET_ADDRESS_TYPE_FD;
addr->u.fd.data = g_new(String, 1);
addr->u.fd.data->str = g_strdup(fd);
} else {
g_assert_not_reached();
}
sock->addr = addr;
}
Vendored
+1
View File
@@ -3694,6 +3694,7 @@ echo "QEMU_CFLAGS=$QEMU_CFLAGS" >> $config_host_mak
echo "QEMU_CXXFLAGS=$QEMU_CXXFLAGS" >> $config_host_mak
echo "GLIB_CFLAGS=$glib_cflags" >> $config_host_mak
echo "GLIB_LIBS=$glib_libs" >> $config_host_mak
echo "GLIB_VERSION=$(pkg-config --modversion glib-2.0)" >> $config_host_mak
echo "QEMU_LDFLAGS=$QEMU_LDFLAGS" >> $config_host_mak
echo "LD_I386_EMULATION=$ld_i386_emulation" >> $config_host_mak
echo "EXESUF=$EXESUF" >> $config_host_mak
+8
View File
@@ -73,6 +73,12 @@ needs_sphinx = '1.6'
# ones.
extensions = ['kerneldoc', 'qmp_lexer', 'hxtool', 'depfile', 'qapidoc']
if sphinx.version_info[:3] > (4, 0, 0):
tags.add('sphinx4')
extensions += ['dbusdoc']
else:
extensions += ['fakedbusdoc']
# Add any paths that contain templates here, relative to this directory.
templates_path = [os.path.join(qemu_docdir, '_templates')]
@@ -311,3 +317,5 @@ kerneldoc_bin = ['perl', os.path.join(qemu_docdir, '../scripts/kernel-doc')]
kerneldoc_srctree = os.path.join(qemu_docdir, '..')
hxtool_srctree = os.path.join(qemu_docdir, '..')
qapidoc_srctree = os.path.join(qemu_docdir, '..')
dbusdoc_srctree = os.path.join(qemu_docdir, '..')
dbus_index_common_prefix = ["org.qemu."]
+31
View File
@@ -0,0 +1,31 @@
D-Bus display
=============
QEMU can export the VM display through D-Bus (when started with ``-display
dbus``), to allow out-of-process UIs, remote protocol servers or other
interactive display usages.
Various specialized D-Bus interfaces are available on different object paths
under ``/org/qemu/Display1/``, depending on the VM configuration.
QEMU also implements the standard interfaces, such as
`org.freedesktop.DBus.Introspectable
<https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces>`_.
.. contents::
:local:
:depth: 1
.. only:: sphinx4
.. dbus-doc:: ui/dbus-display1.xml
.. only:: not sphinx4
.. warning::
Sphinx 4 is required to build D-Bus documentation.
This is the content of ``ui/dbus-display1.xml``:
.. literalinclude:: ../../ui/dbus-display1.xml
:language: xml
+8 -44
View File
@@ -2,9 +2,6 @@
D-Bus VMState
=============
Introduction
============
The QEMU dbus-vmstate object's aim is to migrate helpers' data running
on a QEMU D-Bus bus. (refer to the :doc:`dbus` document for
some recommendations on D-Bus usage)
@@ -26,49 +23,16 @@ dbus-vmstate object can be configured with the expected list of
helpers by setting its ``id-list`` property, with a comma-separated
``Id`` list.
Interface
=========
.. only:: sphinx4
On object path ``/org/qemu/VMState1``, the following
``org.qemu.VMState1`` interface should be implemented:
.. dbus-doc:: backends/dbus-vmstate1.xml
.. code:: xml
.. only:: not sphinx4
<interface name="org.qemu.VMState1">
<property name="Id" type="s" access="read"/>
<method name="Load">
<arg type="ay" name="data" direction="in"/>
</method>
<method name="Save">
<arg type="ay" name="data" direction="out"/>
</method>
</interface>
.. warning::
Sphinx 4 is required to build D-Bus documentation.
"Id" property
-------------
This is the content of ``backends/dbus-vmstate1.xml``:
A string that identifies the helper uniquely. (maximum 256 bytes
including terminating NUL byte)
.. note::
The helper ID namespace is a separate namespace. In particular, it is not
related to QEMU "id" used in -object/-device objects.
Load(in u8[] bytes) method
--------------------------
The method called on destination with the state to restore.
The helper may be initially started in a waiting state (with
an --incoming argument for example), and it may resume on success.
An error may be returned to the caller.
Save(out u8[] bytes) method
---------------------------
The method called on the source to get the current state to be
migrated. The helper should continue to run normally.
An error may be returned to the caller.
.. literalinclude:: ../../backends/dbus-vmstate1.xml
:language: xml
+2
View File
@@ -108,3 +108,5 @@ QEMU Interfaces
===============
:doc:`dbus-vmstate`
:doc:`dbus-display`
+1
View File
@@ -12,6 +12,7 @@ are useful for making QEMU interoperate with other software.
bitmaps
dbus
dbus-vmstate
dbus-display
live-block-operations
pr-helper
qemu-ga
+166
View File
@@ -0,0 +1,166 @@
# D-Bus XML documentation extension
#
# Copyright (C) 2021, Red Hat Inc.
#
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
"""dbus-doc is a Sphinx extension that provides documentation from D-Bus XML."""
import os
import re
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Iterator,
List,
Optional,
Sequence,
Set,
Tuple,
Type,
TypeVar,
Union,
)
import sphinx
from docutils import nodes
from docutils.nodes import Element, Node
from docutils.parsers.rst import Directive, directives
from docutils.parsers.rst.states import RSTState
from docutils.statemachine import StringList, ViewList
from sphinx.application import Sphinx
from sphinx.errors import ExtensionError
from sphinx.util import logging
from sphinx.util.docstrings import prepare_docstring
from sphinx.util.docutils import SphinxDirective, switch_source_input
from sphinx.util.nodes import nested_parse_with_titles
import dbusdomain
from dbusparser import parse_dbus_xml
logger = logging.getLogger(__name__)
__version__ = "1.0"
class DBusDoc:
def __init__(self, sphinx_directive, dbusfile):
self._cur_doc = None
self._sphinx_directive = sphinx_directive
self._dbusfile = dbusfile
self._top_node = nodes.section()
self.result = StringList()
self.indent = ""
def add_line(self, line: str, *lineno: int) -> None:
"""Append one line of generated reST to the output."""
if line.strip(): # not a blank line
self.result.append(self.indent + line, self._dbusfile, *lineno)
else:
self.result.append("", self._dbusfile, *lineno)
def add_method(self, method):
self.add_line(f".. dbus:method:: {method.name}")
self.add_line("")
self.indent += " "
for arg in method.in_args:
self.add_line(f":arg {arg.signature} {arg.name}: {arg.doc_string}")
for arg in method.out_args:
self.add_line(f":ret {arg.signature} {arg.name}: {arg.doc_string}")
self.add_line("")
for line in prepare_docstring("\n" + method.doc_string):
self.add_line(line)
self.indent = self.indent[:-3]
def add_signal(self, signal):
self.add_line(f".. dbus:signal:: {signal.name}")
self.add_line("")
self.indent += " "
for arg in signal.args:
self.add_line(f":arg {arg.signature} {arg.name}: {arg.doc_string}")
self.add_line("")
for line in prepare_docstring("\n" + signal.doc_string):
self.add_line(line)
self.indent = self.indent[:-3]
def add_property(self, prop):
self.add_line(f".. dbus:property:: {prop.name}")
self.indent += " "
self.add_line(f":type: {prop.signature}")
access = {"read": "readonly", "write": "writeonly", "readwrite": "readwrite"}[
prop.access
]
self.add_line(f":{access}:")
if prop.emits_changed_signal:
self.add_line(f":emits-changed: yes")
self.add_line("")
for line in prepare_docstring("\n" + prop.doc_string):
self.add_line(line)
self.indent = self.indent[:-3]
def add_interface(self, iface):
self.add_line(f".. dbus:interface:: {iface.name}")
self.add_line("")
self.indent += " "
for line in prepare_docstring("\n" + iface.doc_string):
self.add_line(line)
for method in iface.methods:
self.add_method(method)
for sig in iface.signals:
self.add_signal(sig)
for prop in iface.properties:
self.add_property(prop)
self.indent = self.indent[:-3]
def parse_generated_content(state: RSTState, content: StringList) -> List[Node]:
"""Parse a generated content by Documenter."""
with switch_source_input(state, content):
node = nodes.paragraph()
node.document = state.document
state.nested_parse(content, 0, node)
return node.children
class DBusDocDirective(SphinxDirective):
"""Extract documentation from the specified D-Bus XML file"""
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
def run(self):
reporter = self.state.document.reporter
try:
source, lineno = reporter.get_source_and_line(self.lineno) # type: ignore
except AttributeError:
source, lineno = (None, None)
logger.debug("[dbusdoc] %s:%s: input:\n%s", source, lineno, self.block_text)
env = self.state.document.settings.env
dbusfile = env.config.qapidoc_srctree + "/" + self.arguments[0]
with open(dbusfile, "rb") as f:
xml_data = f.read()
xml = parse_dbus_xml(xml_data)
doc = DBusDoc(self, dbusfile)
for iface in xml:
doc.add_interface(iface)
result = parse_generated_content(self.state, doc.result)
return result
def setup(app: Sphinx) -> Dict[str, Any]:
"""Register dbus-doc directive with Sphinx"""
app.add_config_value("dbusdoc_srctree", None, "env")
app.add_directive("dbus-doc", DBusDocDirective)
dbusdomain.setup(app)
return dict(version=__version__, parallel_read_safe=True, parallel_write_safe=True)
+406
View File
@@ -0,0 +1,406 @@
# D-Bus sphinx domain extension
#
# Copyright (C) 2021, Red Hat Inc.
#
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
from typing import (
Any,
Dict,
Iterable,
Iterator,
List,
NamedTuple,
Optional,
Tuple,
cast,
)
from docutils import nodes
from docutils.nodes import Element, Node
from docutils.parsers.rst import directives
from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, Index, IndexEntry, ObjType
from sphinx.locale import _
from sphinx.roles import XRefRole
from sphinx.util import nodes as node_utils
from sphinx.util.docfields import Field, TypedField
from sphinx.util.typing import OptionSpec
class DBusDescription(ObjectDescription[str]):
"""Base class for DBus objects"""
option_spec: OptionSpec = ObjectDescription.option_spec.copy()
option_spec.update(
{
"deprecated": directives.flag,
}
)
def get_index_text(self, modname: str, name: str) -> str:
"""Return the text for the index entry of the object."""
raise NotImplementedError("must be implemented in subclasses")
def add_target_and_index(
self, name: str, sig: str, signode: desc_signature
) -> None:
ifacename = self.env.ref_context.get("dbus:interface")
node_id = name
if ifacename:
node_id = f"{ifacename}.{node_id}"
signode["names"].append(name)
signode["ids"].append(node_id)
if "noindexentry" not in self.options:
indextext = self.get_index_text(ifacename, name)
if indextext:
self.indexnode["entries"].append(
("single", indextext, node_id, "", None)
)
domain = cast(DBusDomain, self.env.get_domain("dbus"))
domain.note_object(name, self.objtype, node_id, location=signode)
class DBusInterface(DBusDescription):
"""
Implementation of ``dbus:interface``.
"""
def get_index_text(self, ifacename: str, name: str) -> str:
return ifacename
def before_content(self) -> None:
self.env.ref_context["dbus:interface"] = self.arguments[0]
def after_content(self) -> None:
self.env.ref_context.pop("dbus:interface")
def handle_signature(self, sig: str, signode: desc_signature) -> str:
signode += addnodes.desc_annotation("interface ", "interface ")
signode += addnodes.desc_name(sig, sig)
return sig
def run(self) -> List[Node]:
_, node = super().run()
name = self.arguments[0]
section = nodes.section(ids=[name + "-section"])
section += nodes.title(name, "%s interface" % name)
section += node
return [self.indexnode, section]
class DBusMember(DBusDescription):
signal = False
class DBusMethod(DBusMember):
"""
Implementation of ``dbus:method``.
"""
option_spec: OptionSpec = DBusMember.option_spec.copy()
option_spec.update(
{
"noreply": directives.flag,
}
)
doc_field_types: List[Field] = [
TypedField(
"arg",
label=_("Arguments"),
names=("arg",),
rolename="arg",
typerolename=None,
typenames=("argtype", "type"),
),
TypedField(
"ret",
label=_("Returns"),
names=("ret",),
rolename="ret",
typerolename=None,
typenames=("rettype", "type"),
),
]
def get_index_text(self, ifacename: str, name: str) -> str:
return _("%s() (%s method)") % (name, ifacename)
def handle_signature(self, sig: str, signode: desc_signature) -> str:
params = addnodes.desc_parameterlist()
returns = addnodes.desc_parameterlist()
contentnode = addnodes.desc_content()
self.state.nested_parse(self.content, self.content_offset, contentnode)
for child in contentnode:
if isinstance(child, nodes.field_list):
for field in child:
ty, sg, name = field[0].astext().split(None, 2)
param = addnodes.desc_parameter()
param += addnodes.desc_sig_keyword_type(sg, sg)
param += addnodes.desc_sig_space()
param += addnodes.desc_sig_name(name, name)
if ty == "arg":
params += param
elif ty == "ret":
returns += param
anno = "signal " if self.signal else "method "
signode += addnodes.desc_annotation(anno, anno)
signode += addnodes.desc_name(sig, sig)
signode += params
if not self.signal and "noreply" not in self.options:
ret = addnodes.desc_returns()
ret += returns
signode += ret
return sig
class DBusSignal(DBusMethod):
"""
Implementation of ``dbus:signal``.
"""
doc_field_types: List[Field] = [
TypedField(
"arg",
label=_("Arguments"),
names=("arg",),
rolename="arg",
typerolename=None,
typenames=("argtype", "type"),
),
]
signal = True
def get_index_text(self, ifacename: str, name: str) -> str:
return _("%s() (%s signal)") % (name, ifacename)
class DBusProperty(DBusMember):
"""
Implementation of ``dbus:property``.
"""
option_spec: OptionSpec = DBusMember.option_spec.copy()
option_spec.update(
{
"type": directives.unchanged,
"readonly": directives.flag,
"writeonly": directives.flag,
"readwrite": directives.flag,
"emits-changed": directives.unchanged,
}
)
doc_field_types: List[Field] = []
def get_index_text(self, ifacename: str, name: str) -> str:
return _("%s (%s property)") % (name, ifacename)
def transform_content(self, contentnode: addnodes.desc_content) -> None:
fieldlist = nodes.field_list()
access = None
if "readonly" in self.options:
access = _("read-only")
if "writeonly" in self.options:
access = _("write-only")
if "readwrite" in self.options:
access = _("read & write")
if access:
content = nodes.Text(access)
fieldname = nodes.field_name("", _("Access"))
fieldbody = nodes.field_body("", nodes.paragraph("", "", content))
field = nodes.field("", fieldname, fieldbody)
fieldlist += field
emits = self.options.get("emits-changed", None)
if emits:
content = nodes.Text(emits)
fieldname = nodes.field_name("", _("Emits Changed"))
fieldbody = nodes.field_body("", nodes.paragraph("", "", content))
field = nodes.field("", fieldname, fieldbody)
fieldlist += field
if len(fieldlist) > 0:
contentnode.insert(0, fieldlist)
def handle_signature(self, sig: str, signode: desc_signature) -> str:
contentnode = addnodes.desc_content()
self.state.nested_parse(self.content, self.content_offset, contentnode)
ty = self.options.get("type")
signode += addnodes.desc_annotation("property ", "property ")
signode += addnodes.desc_name(sig, sig)
signode += addnodes.desc_sig_punctuation("", ":")
signode += addnodes.desc_sig_keyword_type(ty, ty)
return sig
def run(self) -> List[Node]:
self.name = "dbus:member"
return super().run()
class DBusXRef(XRefRole):
def process_link(self, env, refnode, has_explicit_title, title, target):
refnode["dbus:interface"] = env.ref_context.get("dbus:interface")
if not has_explicit_title:
title = title.lstrip(".") # only has a meaning for the target
target = target.lstrip("~") # only has a meaning for the title
# if the first character is a tilde, don't display the module/class
# parts of the contents
if title[0:1] == "~":
title = title[1:]
dot = title.rfind(".")
if dot != -1:
title = title[dot + 1 :]
# if the first character is a dot, search more specific namespaces first
# else search builtins first
if target[0:1] == ".":
target = target[1:]
refnode["refspecific"] = True
return title, target
class DBusIndex(Index):
"""
Index subclass to provide a D-Bus interfaces index.
"""
name = "dbusindex"
localname = _("D-Bus Interfaces Index")
shortname = _("dbus")
def generate(
self, docnames: Iterable[str] = None
) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]:
content: Dict[str, List[IndexEntry]] = {}
# list of prefixes to ignore
ignores: List[str] = self.domain.env.config["dbus_index_common_prefix"]
ignores = sorted(ignores, key=len, reverse=True)
ifaces = sorted(
[
x
for x in self.domain.data["objects"].items()
if x[1].objtype == "interface"
],
key=lambda x: x[0].lower(),
)
for name, (docname, node_id, _) in ifaces:
if docnames and docname not in docnames:
continue
for ignore in ignores:
if name.startswith(ignore):
name = name[len(ignore) :]
stripped = ignore
break
else:
stripped = ""
entries = content.setdefault(name[0].lower(), [])
entries.append(IndexEntry(stripped + name, 0, docname, node_id, "", "", ""))
# sort by first letter
sorted_content = sorted(content.items())
return sorted_content, False
class ObjectEntry(NamedTuple):
docname: str
node_id: str
objtype: str
class DBusDomain(Domain):
"""
Implementation of the D-Bus domain.
"""
name = "dbus"
label = "D-Bus"
object_types: Dict[str, ObjType] = {
"interface": ObjType(_("interface"), "iface", "obj"),
"method": ObjType(_("method"), "meth", "obj"),
"signal": ObjType(_("signal"), "sig", "obj"),
"property": ObjType(_("property"), "attr", "_prop", "obj"),
}
directives = {
"interface": DBusInterface,
"method": DBusMethod,
"signal": DBusSignal,
"property": DBusProperty,
}
roles = {
"iface": DBusXRef(),
"meth": DBusXRef(),
"sig": DBusXRef(),
"prop": DBusXRef(),
}
initial_data: Dict[str, Dict[str, Tuple[Any]]] = {
"objects": {}, # fullname -> ObjectEntry
}
indices = [
DBusIndex,
]
@property
def objects(self) -> Dict[str, ObjectEntry]:
return self.data.setdefault("objects", {}) # fullname -> ObjectEntry
def note_object(
self, name: str, objtype: str, node_id: str, location: Any = None
) -> None:
self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype)
def clear_doc(self, docname: str) -> None:
for fullname, obj in list(self.objects.items()):
if obj.docname == docname:
del self.objects[fullname]
def find_obj(self, typ: str, name: str) -> Optional[Tuple[str, ObjectEntry]]:
# skip parens
if name[-2:] == "()":
name = name[:-2]
if typ in ("meth", "sig", "prop"):
try:
ifacename, name = name.rsplit(".", 1)
except ValueError:
pass
return self.objects.get(name)
def resolve_xref(
self,
env: "BuildEnvironment",
fromdocname: str,
builder: "Builder",
typ: str,
target: str,
node: pending_xref,
contnode: Element,
) -> Optional[Element]:
"""Resolve the pending_xref *node* with the given *typ* and *target*."""
objdef = self.find_obj(typ, target)
if objdef:
return node_utils.make_refnode(
builder, fromdocname, objdef.docname, objdef.node_id, contnode
)
def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
for refname, obj in self.objects.items():
yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
def setup(app):
app.add_domain(DBusDomain)
app.add_config_value("dbus_index_common_prefix", [], "env")
+373
View File
@@ -0,0 +1,373 @@
# Based from "GDBus - GLib D-Bus Library":
#
# Copyright (C) 2008-2011 Red Hat, Inc.
#
# 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, see <http://www.gnu.org/licenses/>.
#
# Author: David Zeuthen <davidz@redhat.com>
import xml.parsers.expat
class Annotation:
def __init__(self, key, value):
self.key = key
self.value = value
self.annotations = []
self.since = ""
class Arg:
def __init__(self, name, signature):
self.name = name
self.signature = signature
self.annotations = []
self.doc_string = ""
self.since = ""
class Method:
def __init__(self, name, h_type_implies_unix_fd=True):
self.name = name
self.h_type_implies_unix_fd = h_type_implies_unix_fd
self.in_args = []
self.out_args = []
self.annotations = []
self.doc_string = ""
self.since = ""
self.deprecated = False
self.unix_fd = False
class Signal:
def __init__(self, name):
self.name = name
self.args = []
self.annotations = []
self.doc_string = ""
self.since = ""
self.deprecated = False
class Property:
def __init__(self, name, signature, access):
self.name = name
self.signature = signature
self.access = access
self.annotations = []
self.arg = Arg("value", self.signature)
self.arg.annotations = self.annotations
self.readable = False
self.writable = False
if self.access == "readwrite":
self.readable = True
self.writable = True
elif self.access == "read":
self.readable = True
elif self.access == "write":
self.writable = True
else:
raise ValueError('Invalid access type "{}"'.format(self.access))
self.doc_string = ""
self.since = ""
self.deprecated = False
self.emits_changed_signal = True
class Interface:
def __init__(self, name):
self.name = name
self.methods = []
self.signals = []
self.properties = []
self.annotations = []
self.doc_string = ""
self.doc_string_brief = ""
self.since = ""
self.deprecated = False
class DBusXMLParser:
STATE_TOP = "top"
STATE_NODE = "node"
STATE_INTERFACE = "interface"
STATE_METHOD = "method"
STATE_SIGNAL = "signal"
STATE_PROPERTY = "property"
STATE_ARG = "arg"
STATE_ANNOTATION = "annotation"
STATE_IGNORED = "ignored"
def __init__(self, xml_data, h_type_implies_unix_fd=True):
self._parser = xml.parsers.expat.ParserCreate()
self._parser.CommentHandler = self.handle_comment
self._parser.CharacterDataHandler = self.handle_char_data
self._parser.StartElementHandler = self.handle_start_element
self._parser.EndElementHandler = self.handle_end_element
self.parsed_interfaces = []
self._cur_object = None
self.state = DBusXMLParser.STATE_TOP
self.state_stack = []
self._cur_object = None
self._cur_object_stack = []
self.doc_comment_last_symbol = ""
self._h_type_implies_unix_fd = h_type_implies_unix_fd
self._parser.Parse(xml_data)
COMMENT_STATE_BEGIN = "begin"
COMMENT_STATE_PARAMS = "params"
COMMENT_STATE_BODY = "body"
COMMENT_STATE_SKIP = "skip"
def handle_comment(self, data):
comment_state = DBusXMLParser.COMMENT_STATE_BEGIN
lines = data.split("\n")
symbol = ""
body = ""
in_para = False
params = {}
for line in lines:
orig_line = line
line = line.lstrip()
if comment_state == DBusXMLParser.COMMENT_STATE_BEGIN:
if len(line) > 0:
colon_index = line.find(": ")
if colon_index == -1:
if line.endswith(":"):
symbol = line[0 : len(line) - 1]
comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
else:
comment_state = DBusXMLParser.COMMENT_STATE_SKIP
else:
symbol = line[0:colon_index]
rest_of_line = line[colon_index + 2 :].strip()
if len(rest_of_line) > 0:
body += rest_of_line + "\n"
comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
elif comment_state == DBusXMLParser.COMMENT_STATE_PARAMS:
if line.startswith("@"):
colon_index = line.find(": ")
if colon_index == -1:
comment_state = DBusXMLParser.COMMENT_STATE_BODY
if not in_para:
in_para = True
body += orig_line + "\n"
else:
param = line[1:colon_index]
docs = line[colon_index + 2 :]
params[param] = docs
else:
comment_state = DBusXMLParser.COMMENT_STATE_BODY
if len(line) > 0:
if not in_para:
in_para = True
body += orig_line + "\n"
elif comment_state == DBusXMLParser.COMMENT_STATE_BODY:
if len(line) > 0:
if not in_para:
in_para = True
body += orig_line + "\n"
else:
if in_para:
body += "\n"
in_para = False
if in_para:
body += "\n"
if symbol != "":
self.doc_comment_last_symbol = symbol
self.doc_comment_params = params
self.doc_comment_body = body
def handle_char_data(self, data):
# print 'char_data=%s'%data
pass
def handle_start_element(self, name, attrs):
old_state = self.state
old_cur_object = self._cur_object
if self.state == DBusXMLParser.STATE_IGNORED:
self.state = DBusXMLParser.STATE_IGNORED
elif self.state == DBusXMLParser.STATE_TOP:
if name == DBusXMLParser.STATE_NODE:
self.state = DBusXMLParser.STATE_NODE
else:
self.state = DBusXMLParser.STATE_IGNORED
elif self.state == DBusXMLParser.STATE_NODE:
if name == DBusXMLParser.STATE_INTERFACE:
self.state = DBusXMLParser.STATE_INTERFACE
iface = Interface(attrs["name"])
self._cur_object = iface
self.parsed_interfaces.append(iface)
elif name == DBusXMLParser.STATE_ANNOTATION:
self.state = DBusXMLParser.STATE_ANNOTATION
anno = Annotation(attrs["name"], attrs["value"])
self._cur_object.annotations.append(anno)
self._cur_object = anno
else:
self.state = DBusXMLParser.STATE_IGNORED
# assign docs, if any
if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
self._cur_object.doc_string = self.doc_comment_body
if "short_description" in self.doc_comment_params:
short_description = self.doc_comment_params["short_description"]
self._cur_object.doc_string_brief = short_description
if "since" in self.doc_comment_params:
self._cur_object.since = self.doc_comment_params["since"].strip()
elif self.state == DBusXMLParser.STATE_INTERFACE:
if name == DBusXMLParser.STATE_METHOD:
self.state = DBusXMLParser.STATE_METHOD
method = Method(
attrs["name"], h_type_implies_unix_fd=self._h_type_implies_unix_fd
)
self._cur_object.methods.append(method)
self._cur_object = method
elif name == DBusXMLParser.STATE_SIGNAL:
self.state = DBusXMLParser.STATE_SIGNAL
signal = Signal(attrs["name"])
self._cur_object.signals.append(signal)
self._cur_object = signal
elif name == DBusXMLParser.STATE_PROPERTY:
self.state = DBusXMLParser.STATE_PROPERTY
prop = Property(attrs["name"], attrs["type"], attrs["access"])
self._cur_object.properties.append(prop)
self._cur_object = prop
elif name == DBusXMLParser.STATE_ANNOTATION:
self.state = DBusXMLParser.STATE_ANNOTATION
anno = Annotation(attrs["name"], attrs["value"])
self._cur_object.annotations.append(anno)
self._cur_object = anno
else:
self.state = DBusXMLParser.STATE_IGNORED
# assign docs, if any
if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
self._cur_object.doc_string = self.doc_comment_body
if "since" in self.doc_comment_params:
self._cur_object.since = self.doc_comment_params["since"].strip()
elif self.state == DBusXMLParser.STATE_METHOD:
if name == DBusXMLParser.STATE_ARG:
self.state = DBusXMLParser.STATE_ARG
arg_name = None
if "name" in attrs:
arg_name = attrs["name"]
arg = Arg(arg_name, attrs["type"])
direction = attrs.get("direction", "in")
if direction == "in":
self._cur_object.in_args.append(arg)
elif direction == "out":
self._cur_object.out_args.append(arg)
else:
raise ValueError('Invalid direction "{}"'.format(direction))
self._cur_object = arg
elif name == DBusXMLParser.STATE_ANNOTATION:
self.state = DBusXMLParser.STATE_ANNOTATION
anno = Annotation(attrs["name"], attrs["value"])
self._cur_object.annotations.append(anno)
self._cur_object = anno
else:
self.state = DBusXMLParser.STATE_IGNORED
# assign docs, if any
if self.doc_comment_last_symbol == old_cur_object.name:
if "name" in attrs and attrs["name"] in self.doc_comment_params:
doc_string = self.doc_comment_params[attrs["name"]]
if doc_string is not None:
self._cur_object.doc_string = doc_string
if "since" in self.doc_comment_params:
self._cur_object.since = self.doc_comment_params[
"since"
].strip()
elif self.state == DBusXMLParser.STATE_SIGNAL:
if name == DBusXMLParser.STATE_ARG:
self.state = DBusXMLParser.STATE_ARG
arg_name = None
if "name" in attrs:
arg_name = attrs["name"]
arg = Arg(arg_name, attrs["type"])
self._cur_object.args.append(arg)
self._cur_object = arg
elif name == DBusXMLParser.STATE_ANNOTATION:
self.state = DBusXMLParser.STATE_ANNOTATION
anno = Annotation(attrs["name"], attrs["value"])
self._cur_object.annotations.append(anno)
self._cur_object = anno
else:
self.state = DBusXMLParser.STATE_IGNORED
# assign docs, if any
if self.doc_comment_last_symbol == old_cur_object.name:
if "name" in attrs and attrs["name"] in self.doc_comment_params:
doc_string = self.doc_comment_params[attrs["name"]]
if doc_string is not None:
self._cur_object.doc_string = doc_string
if "since" in self.doc_comment_params:
self._cur_object.since = self.doc_comment_params[
"since"
].strip()
elif self.state == DBusXMLParser.STATE_PROPERTY:
if name == DBusXMLParser.STATE_ANNOTATION:
self.state = DBusXMLParser.STATE_ANNOTATION
anno = Annotation(attrs["name"], attrs["value"])
self._cur_object.annotations.append(anno)
self._cur_object = anno
else:
self.state = DBusXMLParser.STATE_IGNORED
elif self.state == DBusXMLParser.STATE_ARG:
if name == DBusXMLParser.STATE_ANNOTATION:
self.state = DBusXMLParser.STATE_ANNOTATION
anno = Annotation(attrs["name"], attrs["value"])
self._cur_object.annotations.append(anno)
self._cur_object = anno
else:
self.state = DBusXMLParser.STATE_IGNORED
elif self.state == DBusXMLParser.STATE_ANNOTATION:
if name == DBusXMLParser.STATE_ANNOTATION:
self.state = DBusXMLParser.STATE_ANNOTATION
anno = Annotation(attrs["name"], attrs["value"])
self._cur_object.annotations.append(anno)
self._cur_object = anno
else:
self.state = DBusXMLParser.STATE_IGNORED
else:
raise ValueError(
'Unhandled state "{}" while entering element with name "{}"'.format(
self.state, name
)
)
self.state_stack.append(old_state)
self._cur_object_stack.append(old_cur_object)
def handle_end_element(self, name):
self.state = self.state_stack.pop()
self._cur_object = self._cur_object_stack.pop()
def parse_dbus_xml(xml_data):
parser = DBusXMLParser(xml_data, True)
return parser.parsed_interfaces
+25
View File
@@ -0,0 +1,25 @@
# D-Bus XML documentation extension, compatibility gunk for <sphinx4
#
# Copyright (C) 2021, Red Hat Inc.
#
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
"""dbus-doc is a Sphinx extension that provides documentation from D-Bus XML."""
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective
from typing import Any, Dict
class FakeDBusDocDirective(SphinxDirective):
has_content = True
required_arguments = 1
def run(self):
return []
def setup(app: Sphinx) -> Dict[str, Any]:
"""Register a fake dbus-doc directive with Sphinx"""
app.add_directive("dbus-doc", FakeDBusDocDirective)
+6 -1
View File
@@ -2171,12 +2171,17 @@ static void qxl_realize_common(PCIQXLDevice *qxl, Error **errp)
}
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
Error *err = NULL;
char device_address[256] = "";
if (qemu_spice_fill_device_address(qxl->vga.con, device_address, 256)) {
if (qemu_console_fill_device_address(qxl->vga.con,
device_address, sizeof(device_address),
&err)) {
spice_qxl_set_device_info(&qxl->ssd.qxl,
device_address,
0,
qxl->max_outputs);
} else {
error_report_err(err);
}
#endif

Some files were not shown because too many files have changed in this diff Show More