summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrett Stottlemyer <bstottle@ford.com>2021-03-07 10:51:50 -0500
committerBrett Stottlemyer <bstottle@ford.com>2021-07-15 14:44:24 -0400
commit0ab10b008f4b713b7a58206b796668e05e80c2c6 (patch)
tree6795aadfd8e9d74d962ccd8709342dc414fd6065
parentd8b6dce0d2cf91e20905856aef06c77c8659da98 (diff)
Add example of reading CBOR data from pythonwip/serialization
This is currently a test harness that will expand as the CBOR functionality is prototyped. A start at changes to repc to autogenerate python code for Replica types is also included. Change-Id: I97c3ad4c62c7d747c1918f88be41fb7643bbb46d Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Michael Brasser <michael.brasser@live.com>
-rw-r--r--examples/remoteobjects/CMakeLists.txt1
-rw-r--r--examples/remoteobjects/cbor_python/CMakeLists.txt3
-rw-r--r--examples/remoteobjects/cbor_python/cbor_python.pro3
-rw-r--r--examples/remoteobjects/cbor_python/py_replica/py_replica.py653
-rw-r--r--examples/remoteobjects/cbor_python/qt_source/CMakeLists.txt58
-rw-r--r--examples/remoteobjects/cbor_python/qt_source/Simple.rep9
-rw-r--r--examples/remoteobjects/cbor_python/qt_source/main.cpp98
-rw-r--r--examples/remoteobjects/cbor_python/qt_source/qt_source.pro26
-rw-r--r--examples/remoteobjects/cbor_python/qt_source/simple.cpp70
-rw-r--r--examples/remoteobjects/cbor_python/qt_source/simple.h72
-rw-r--r--examples/remoteobjects/pyside/replica.py76
-rw-r--r--examples/remoteobjects/pyside/simple.py196
-rw-r--r--examples/remoteobjects/pyside/source.py80
-rw-r--r--examples/remoteobjects/remoteobjects.pro1
-rw-r--r--tools/repc/CMakeLists.txt1
-rw-r--r--tools/repc/main.cpp59
-rw-r--r--tools/repc/pythoncodegenerator.cpp575
-rw-r--r--tools/repc/pythoncodegenerator.h57
-rw-r--r--tools/repc/repc.pro2
-rw-r--r--tools/repc/repcodegenerator.cpp147
-rw-r--r--tools/repc/repcodegenerator.h14
-rw-r--r--tools/repc/utils.cpp141
-rw-r--r--tools/repc/utils.h52
23 files changed, 2258 insertions, 136 deletions
diff --git a/examples/remoteobjects/CMakeLists.txt b/examples/remoteobjects/CMakeLists.txt
index ec71f6f..652c64b 100644
--- a/examples/remoteobjects/CMakeLists.txt
+++ b/examples/remoteobjects/CMakeLists.txt
@@ -2,6 +2,7 @@
add_subdirectory(server)
add_subdirectory(cppclient)
+add_subdirectory(cbor_python)
add_subdirectory(simpleswitch)
add_subdirectory(websockets)
if(TARGET Qt::Widgets)
diff --git a/examples/remoteobjects/cbor_python/CMakeLists.txt b/examples/remoteobjects/cbor_python/CMakeLists.txt
new file mode 100644
index 0000000..609eb34
--- /dev/null
+++ b/examples/remoteobjects/cbor_python/CMakeLists.txt
@@ -0,0 +1,3 @@
+# Generated from cbor_python.pro.
+
+add_subdirectory(qt_source)
diff --git a/examples/remoteobjects/cbor_python/cbor_python.pro b/examples/remoteobjects/cbor_python/cbor_python.pro
new file mode 100644
index 0000000..b559903
--- /dev/null
+++ b/examples/remoteobjects/cbor_python/cbor_python.pro
@@ -0,0 +1,3 @@
+TEMPLATE = subdirs
+
+SUBDIRS += qt_source
diff --git a/examples/remoteobjects/cbor_python/py_replica/py_replica.py b/examples/remoteobjects/cbor_python/py_replica/py_replica.py
new file mode 100644
index 0000000..8d9509b
--- /dev/null
+++ b/examples/remoteobjects/cbor_python/py_replica/py_replica.py
@@ -0,0 +1,653 @@
+#!/usr/bin/env python3
+#############################################################################
+##
+## Copyright (C) 2021 Ford Motor Company
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the QtRemoteObjects module of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## Commercial License Usage
+## Licensees holding valid commercial Qt licenses may use this file in
+## accordance with the commercial license agreement provided with the
+## Software or, alternatively, in accordance with the terms contained in
+## a written agreement between you and The Qt Company. For licensing terms
+## and conditions see https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## BSD License Usage
+## Alternatively, you may use this file under the terms of the BSD license
+## as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import argparse
+import asyncio
+import collections
+import logging
+import signal
+import socket
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from enum import IntEnum
+from functools import partial
+from struct import pack, unpack, calcsize
+from urllib.parse import urlparse
+from weakref import WeakSet
+
+DEFAULT_URL = "tcp://127.0.0.1:5005"
+logging.basicConfig(format='%(asctime)s.%(msecs)06d %(levelname)-8s %(message)s',
+ datefmt='%Y-%m-%d %H:%M:%S')
+logger = logging.getLogger(__name__)
+
+
+class PacketType(IntEnum):
+ Invalid = 0
+ Handshake = 1
+ InitPacket = 2
+ InitDynamicPacket = 3
+ AddObject = 4
+ RemoveObject = 5
+ InvokePacket = 6
+ InvokeReplyPacket = 7
+ PropertyChangePacket = 8
+ ObjectList = 9
+ Ping = 10
+ Pong = 11
+
+
+class Decoder:
+ _QVariantDecoders = {
+ 1: lambda self: self.decodeBool(),
+ 2: lambda self: self.decodeS32(),
+ 3: lambda self: self.decodeU32(),
+ 6: lambda self: self.decodeDouble(),
+ 10: lambda self: self.decodeQString(),
+ 12: lambda self: self.decodeByteArray(),
+ 36: lambda self: self.decodeU16(),
+ 38: lambda self: self.decodeDouble(), # float is treated as double in QDataStream
+ }
+
+ def __init__(self, data):
+ self.data = data
+ self.index = 0
+
+ def decodeU16(self):
+ val = unpack(">H", self.data[self.index:self.index+2])[0]
+ logger.debug(f"decodeU16: val = {val} (at index = {self.index})")
+ self.index += 2
+ return val
+
+ def decodeU32(self):
+ val = unpack(">I", self.data[self.index:self.index+4])[0]
+ logger.debug(f"decodeU32: val = {val} (at index = {self.index})")
+ self.index += 4
+ return val
+
+ def decodeS32(self):
+ val = unpack(">i", self.data[self.index:self.index+4])[0]
+ logger.debug(f"decodeS32: val = {val} (at index = {self.index})")
+ self.index += 4
+ return val
+
+ def decodeDouble(self):
+ val = unpack(">d", self.data[self.index:self.index+8])[0]
+ logger.debug(f"decodeDouble: val = {val} (at index = {self.index})")
+ self.index += 8
+ return val
+
+ def decodeQString(self):
+ l = self.decodeU32()
+ s = self.data[self.index:self.index+l].decode('utf-16be')
+ logger.debug(f"decodeQString: len = {l/2}, s = {s}")
+ self.index += l
+ return s
+
+ def decodeByteArray(self):
+ l = self.decodeU32()
+ s = self.data[self.index:self.index+l]
+ logger.debug(f"decodeByteArray: len = {l}, s = {s}")
+ self.index += l
+ return s
+
+ def decodeBool(self):
+ b = bool(self.data[self.index])
+ logger.debug(f"decodeBool: result {b}")
+ self.index += 1
+ return b
+
+ def decodeQVariant(self):
+ typeId = self.decodeU32()
+ isNull = self.decodeBool()
+ decoder = Decoder._QVariantDecoders.get(typeId)
+ if decoder:
+ return decoder(self)
+ else:
+ raise RuntimeError(f"Tried to decode unrecognized QVariant type ({typeId}).")
+
+ def decodeArgs(self):
+ parameters = []
+ nArgs = self.decodeU32()
+ for i in range(nArgs):
+ parameters.append(self.decodeQVariant())
+ return parameters
+
+
+class Timer:
+ # Based on https://stackoverflow.com/a/45430833/169296
+
+ def __init__(self, timeout, callback):
+ self._timeout = timeout
+ self._callback = callback
+ self._task = asyncio.create_task(self._job())
+
+ async def _job(self):
+ await asyncio.sleep(self._timeout)
+ await self._callback()
+
+ async def restart(self):
+ if not self._task.cancelled():
+ self._task.cancel()
+ try:
+ await self._task
+ except asyncio.CancelledError:
+ pass
+ self._task = asyncio.create_task(self._job())
+
+
+class Pipe(ABC):
+ handlers = {}
+ schema = None
+
+ def __init__(self, url, queue, hbInterval):
+ self.url = url
+ self.queue = queue
+ self.hbInterval = hbInterval
+ self.handshake = False
+ self.error = self.reader = self.writer = self.timer = None
+
+ @abstractmethod
+ async def connect(self):
+ pass
+
+ async def run(self):
+ await self.connect()
+ while not self.error:
+ data = await self.reader.readexactly(4)
+ if not self.handshake: # Test Qt6 handshake
+ size, packetId, rev, firstChar = unpack(">BBBc", data)
+ logger.debug(f"Handshake size = {size} id = {packetId} rev = {rev} firstChar = "
+ f"{firstChar}")
+ if size < 3 or size > 252 or packetId != 1:
+ size = unpack(">I", data)[0]
+ firstChar = b'\x00'
+ else:
+ size -= 3 # size includes packetId, rev and firstChar which we've already read
+ else:
+ size = unpack(">I", data)[0]
+ logger.debug(f"Packet length {size}")
+ data = await self.reader.readexactly(size)
+ if not self.handshake:
+ if firstChar == b'\x00':
+ packetId = unpack(">H", data[:2])[0]
+ if packetId != PacketType.Handshake:
+ logger.error("Invalid pipe, unable to establish handshake")
+ self.error = True
+ continue
+ else:
+ # Make it look like the old style handshake packet
+ codec = (firstChar + data).decode('utf-8').encode('utf-16be')
+ data = b'\x00\x01' + pack('>I', len(codec)) + codec
+ logger.error(f"Data {data}")
+ self.handshake = True # The Node will delete us if the handshake isn't valid
+ if self.hbInterval > 0:
+ self.timer = Timer(self.hbInterval, self.sendHeartbeat)
+ elif self.timer:
+ await self.timer.restart()
+ await self.queue.put( (id(self), data) )
+ self.writer.close()
+ self.handshake = False
+ await self.queue.put( (id(self), None) )
+
+ async def sendHeartbeat(self):
+ logger.debug("Sending Heartbeat")
+ self.writer.write(pack(">IH", 2, PacketType.Ping))
+ await self.timer.restart()
+
+ @classmethod
+ def __init_subclass__(cls, **kwargs):
+ """
+ This creates a map of schema name ("tcp", "ws", etc) to the class (derived from Pipe)
+ that handles that type of pipe.
+
+ This classmethod is called whenever a class derived from Pipe is parsed, assuming each
+ class has a class variable (not an instance variable) named `schema`.
+ """
+ super().__init_subclass__(**kwargs)
+ if cls.schema is None:
+ raise RuntimeError(f"Invalid Pipe defined ({cls.__name__}). Pipes must declare a "
+ f"schema to support.")
+ if cls.schema in Pipe.handlers:
+ raise RuntimeError(f"Invalid Pipe defined ({cls.__name__}). Schema {cls.schema} "
+ f"already in use.")
+ Pipe.handlers[cls.schema] = cls
+
+
+class TcpPipe(Pipe):
+ schema = "tcp"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ urlmeta = urlparse(self.url)
+ self.hostname = urlmeta.hostname
+ self.port = urlmeta.port
+
+ async def connect(self):
+ # TODO error handling, reconnect, etc
+ self.reader, self.writer = await asyncio.open_connection(self.hostname, self.port)
+
+
+class Node:
+
+ def __init__(self, name=""):
+ self.Handlers = {
+ PacketType.Handshake: lambda decoder: self.onHandshake(decoder),
+ PacketType.InitPacket: lambda decoder: self.onInitPacket(decoder),
+ PacketType.InitDynamicPacket: lambda decoder: self.onInitDynamicPacket(decoder),
+ PacketType.RemoveObject: lambda decoder: self.onRemoveObject(decoder),
+ PacketType.InvokePacket: lambda decoder: self.onInvokePacket(decoder),
+ PacketType.InvokeReplyPacket: lambda decoder: self.onInvokeReplyPacket(decoder),
+ PacketType.PropertyChangePacket: lambda decoder: self.onPropertyChangePacket(decoder),
+ PacketType.ObjectList: lambda decoder: self.onObjectList(decoder),
+ PacketType.Pong: lambda decoder: self.onPong(decoder),
+ }
+ self.name = name
+ # We set write() and currentPipe to the pipe of the packet we are handling
+ self.write = self.currentPipe = None
+ self.hbInterval = 0
+ self.queue = asyncio.Queue()
+ self.pipes = {}
+ self.replicaPrivateInstances = {}
+
+ def acquire(self, cls, name=None):
+ name = name or cls.__name__
+ replicaPrivate = self.replicaPrivateInstances.get(name)
+ if replicaPrivate is None:
+ replicaPrivate = ReplicaPrivate(cls, name)
+ self.replicaPrivateInstances[name] = replicaPrivate
+ return replicaPrivate.create()
+
+ def setHeartbeatInterval(self, timeout):
+ self.hbInterval = timeout
+
+ def connect(self, url):
+ urlmeta = urlparse(url)
+ if urlmeta.scheme not in Pipe.handlers:
+ logger.error(f"Unrecognized schema ({urlmeta.scheme}) for url ({url})")
+ logger.error(f" Known schemas: {Pipe.handlers.keys()}")
+ return
+ pipe = Pipe.handlers[urlmeta.scheme](url, self.queue, self.hbInterval)
+ self.pipes[id(pipe)] = pipe
+ asyncio.get_event_loop().create_task(pipe.run())
+
+ def onPipeError(self, msg):
+ logger.error(msg)
+ del self.pipes[id(self.currentPipe)]
+
+ async def run(self):
+ while True:
+ pipeId, data = await self.queue.get()
+ if data is None:
+ del self.pipes[pipeId]
+ d = Decoder(data)
+ packetId = d.decodeU16()
+ handler = self.Handlers.get(packetId, None)
+ if handler:
+ self.currentPipe = self.pipes.get(pipeId)
+ if self.currentPipe is None:
+ continue # We deleted the pipe, but there were still packets in the queue
+ self.write = self.currentPipe.writer.write
+ logger.debug(f"{PacketType(packetId).name} packet received on node {self.name}")
+ handler(d)
+ self.write = self.currentPipe = None
+ logger.debug("")
+ else:
+ self.onPipeError(f"Invalid packet found with id = {packetId}")
+
+ def onHandshake(self, d):
+ handshake = d.decodeQString()
+ if handshake not in ["QtRO 1.3", "QDataStream"]:
+ self.onPipeError(f"Unrecognized handshake ({handshake}) received.")
+
+ def onObjectList(self, d):
+ l = d.decodeU32()
+ i = 0
+ while l > 0:
+ i += 1
+ l -= 1
+ name = d.decodeQString()
+ typename = d.decodeQString()
+ checksum = d.decodeByteArray()
+ logger.debug(f"Object #{i}: name: {name} type: {typename} checksum: {checksum}")
+ len2 = len(name) * 2
+ self.write(pack(f">IHI{len2}sc", 7 + len2, PacketType.AddObject, len2,
+ name.encode('utf-16be'), b'\x00'))
+
+ def onInitPacket(self, d):
+ name = d.decodeQString()
+ properties = d.decodeArgs()
+ logger.info(f"Properties for {name}: {properties}")
+ self.replicaPrivateInstances[name].initialize(self.currentPipe, properties)
+
+ def onInitDynamicPacket(self, d): pass
+ def onRemoveObject(self, d): pass
+ def onInvokePacket(self, d):
+ name = d.decodeQString()
+ call = d.decodeS32()
+ index = d.decodeS32()
+ parameters = d.decodeArgs()
+ serialId = d.decodeS32()
+ propertyIndex = d.decodeS32()
+ logger.info(f"Invoke: name: {name} call: {call} index: {index} serialId: {serialId} "
+ f"propertyIndex: {propertyIndex}")
+ self.replicaPrivateInstances[name].invoke(index, *parameters)
+
+ def onInvokeReplyPacket(self, d): pass
+ def onPropertyChangePacket(self, d):
+ name = d.decodeQString()
+ index = d.decodeS32()
+ val = d.decodeQVariant()
+ logger.debug(f"PropertyChange: name: {name} index: {index} value: {val}")
+
+ def onPong(self, d): pass
+
+
+class Signal:
+
+ def __init__(self):
+ self._slots = collections.deque()
+
+ def __repr__(self):
+ res = super().__repr__()
+ if self._slots:
+ extra = f'slots:{len(self._slots)}'
+ return f'<{res[1:-1]} [{extra}]>'
+
+ def emit(self, *args):
+ for slot in self._slots:
+ slot(*args)
+
+ def connect(self, func):
+ self._slots.append(func)
+
+
+class ReplicaPrivate:
+ """
+ There can be multiple instances of the same replica type. We don't want
+ each to each to talk to the source indepenently, so the ReplicaPrivate class
+ represents
+ * The pipe (if available) to the source
+ * The "cache" of property values locally, shared by instances
+ * The link to all instances
+ """
+
+ def __init__(self, cls, name):
+ self.instances = WeakSet()
+ self.pipe = None
+ self.cls = cls
+ self.name = name
+ self.typename = cls.__name__
+ self.properties = cls.defaults()
+
+ def create(self):
+ replica = self.cls(self)
+ self.instances.add(replica)
+ return replica
+
+ def write(self, buffer):
+ if self.pipe is None:
+ return
+ self.pipe.writer.write(buffer)
+
+ def initialize(self, pipe, values):
+ self.pipe = pipe
+ self.properties = values
+ for instance in self.instances:
+ instance.initialize()
+
+ def invoke(self, index, *parameters):
+ signal = self.cls.Signals[index]
+ for instance in self.instances:
+ print("invoke on instance", instance)
+ getattr(instance, signal).emit(*parameters)
+
+ def isValid(self):
+ return self.pipe is not None
+
+
+@dataclass(eq=False)
+class Replica:
+ _priv: ReplicaPrivate = field(repr=False)
+ initialized: Signal = field(init=False, repr=False, default_factory=Signal)
+
+ def __new__(cls, replicaPrivate=None):
+ if replicaPrivate is None:
+ raise RuntimeError("Replicas can only be created from a Node's acquire() method")
+ return super().__new__(cls)
+
+ def name(self):
+ return self._priv.name
+
+ def typename(self):
+ return self._priv.typename
+
+ def initialize(self, *args):
+ self.initialized.emit()
+
+ def callSlot(self, index, *args):
+ # 0 is Call.InvokeMetaMethod
+ self.sendInvokePacket(index, 0, self._priv.cls.slotTypes()[index], *args)
+
+ def callSetter(self, index, *args):
+ # 2 is Call.WriteProperty
+ self.sendInvokePacket(index, 2, self._priv.cls.propTypes()[index], *args)
+
+ def sendInvokePacket(self, index, callIndex, types, *args):
+ if not self._priv.isValid():
+ logger.warning(f"Replica instance (name: {self.name()} type: {self.typename()}) tried "
+ f"to write before pipe available.")
+ return
+ encodedLen = len(self.name())*2
+ prefixPart = [
+ ("I", 0), # Length of packet, filled later
+ ("H", PacketType.InvokePacket), # PacketId
+ ("I", encodedLen), # Length of name as a QString
+ (f"{encodedLen}s", self.name().encode('utf-16be')), # Replica name string
+ ("i", callIndex), # Call
+ ("i", index), # Index
+ ("I", len(args)), # Number of args
+ ]
+ argsPart = []
+ for ind, arg in enumerate(args):
+ f = types[ind]
+ if f == "i":
+ argsPart.append( ("i", 2) ) # Qt typeId for int
+ elif f == "f":
+ argsPart.append( ("i", 38) ) # Qt typeId for float
+ argsPart.append( ("c", b'\x00') ) # isNull
+ if f == "i":
+ argsPart.append( ("i", arg) )
+ elif f == "f":
+ argsPart.append( ("d", arg) )
+ suffixPart = [
+ ("i", -1), # SerialId
+ ("i", -1), # PropertyIndex
+ ]
+ f, newArgs = map(list, zip(*prefixPart, *argsPart, *suffixPart))
+ f = ">" + " ".join(f)
+ newArgs[0] = calcsize(f) - 4 # Exclude the 4 bytes for the packet length
+ buffer = pack(f, *newArgs)
+ self._priv.write(buffer)
+
+
+@dataclass(eq=False)
+class Simple(Replica):
+ Signals = ['iChanged', 'fChanged', 'random']
+ iChanged: Signal = field(init=False, repr=False, default_factory=Signal)
+ fChanged: Signal = field(init=False, repr=False, default_factory=Signal)
+ random: Signal = field(init=False, repr=False, default_factory=Signal)
+
+ @property
+ def i(self):
+ return self._priv.properties[0]
+
+ def pushI(self, i):
+ print('pushI', i)
+ self.callSlot(0, i)
+
+ @property
+ def f(self):
+ return self._priv.properties[1]
+
+ @f.setter
+ def f(self, f):
+ print('f setter', f)
+ self.callSetter(1, f)
+
+ def reset(self):
+ print('Calling reset slot')
+ self.callSlot(1)
+
+ @classmethod
+ def defaults(cls):
+ return [2, -1.]
+
+ @classmethod
+ def propTypes(cls):
+ return [
+ ('i',), # i is of type int -> i
+ ('f',), # f is of type float -> f
+ ]
+
+ @classmethod
+ def signalTypes(cls):
+ return [
+ ('i',),
+ ('f',),
+ ('i',),
+ ]
+
+ @classmethod
+ def slotTypes(cls):
+ return [
+ ('i',),
+ (),
+ ]
+
+ @classmethod
+ def signature(cls):
+ return b'c6f33edb0554ba4241aad1286a47c8189d65c845'
+
+ @classmethod
+ def name(cls):
+ return 'Simple'
+
+
+def handle_exception(loop, context):
+ msg = context.get("exception", context["message"])
+ logger.error(f"Caught exception: {msg}")
+ logger.error(f"{context}")
+ logger.info("Shutting down...")
+ asyncio.create_task(shutdown(loop))
+
+
+async def shutdown(loop, signal=None):
+ if signal:
+ logger.info(f"Received exit signal {signal.name}...")
+ logger.info("Cancelling outstanding tasks")
+ tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
+ [task.cancel() for task in tasks]
+
+ await asyncio.gather(*tasks, return_exceptions=True)
+ loop.stop()
+
+
+def handler(i, val):
+ print(f"handler #{i} fired {val}")
+
+
+def main(args):
+ loop = asyncio.get_event_loop()
+ signals = (signal.SIGHUP, signal.SIGTERM, signal.SIGINT)
+ for s in signals:
+ loop.add_signal_handler(
+ s, lambda s=s: asyncio.create_task(shutdown(loop, signal=s))
+ )
+ loop.set_exception_handler(handle_exception)
+ node = Node("A")
+ if args.heartbeat:
+ node.setHeartbeatInterval(args.heartbeat)
+ s = node.acquire(Simple, "DifferentName")
+ print(f"Initial Simple: i = {s.i} f = {s.f}")
+ s.iChanged.connect(partial(handler, 3))
+ s.iChanged.connect(partial(handler, 7))
+ # s.reset() # This gives a warning about the pipe not being available
+ s.initialized.connect(s.reset)
+ s.initialized.connect(lambda : print(f"Now s: i = {s.i} f = {s.f}"))
+ s2 = node.acquire(Simple, "DifferentName")
+ s2.iChanged.connect(partial(handler, 42))
+ async def change():
+ await asyncio.sleep(1.5)
+ s2.f = 99.5
+ await asyncio.sleep(.5)
+ s2.f = 93.9
+ s2.initialized.connect(lambda : loop.create_task(change()))
+ node.connect(DEFAULT_URL)
+ try:
+ loop.create_task(node.run())
+ loop.run_forever()
+ finally:
+ loop.close()
+ logger.info("Loop exited")
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-v", "--verbose", action="count", default=0,
+ help="provide verbose information about the run (may be repeated)")
+ parser.add_argument("--heartbeat", type=int, default=0,
+ help="set heartbeat timeout (in seconds)")
+ args = parser.parse_args()
+ if args.verbose == 1:
+ logger.setLevel(logging.INFO)
+ elif args.verbose > 1:
+ logger.setLevel(logging.DEBUG)
+ main(args)
diff --git a/examples/remoteobjects/cbor_python/qt_source/CMakeLists.txt b/examples/remoteobjects/cbor_python/qt_source/CMakeLists.txt
new file mode 100644
index 0000000..11c31c6
--- /dev/null
+++ b/examples/remoteobjects/cbor_python/qt_source/CMakeLists.txt
@@ -0,0 +1,58 @@
+# Generated from qt_source.pro.
+
+cmake_minimum_required(VERSION 3.14)
+project(QtSource LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/cbor_python/qt_source")
+
+find_package(Qt6 COMPONENTS Core)
+find_package(Qt6 COMPONENTS Gui)
+find_package(Qt6 COMPONENTS RemoteObjects)
+
+qt_add_executable(QtSource
+ main.cpp
+ simple.cpp simple.h
+)
+set_target_properties(QtSource PROPERTIES
+ WIN32_EXECUTABLE FALSE
+ MACOSX_BUNDLE FALSE
+)
+target_link_libraries(QtSource PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::RemoteObjects
+)
+
+qt6_add_repc_sources(QtSource
+ Simple.rep
+)
+
+install(TARGETS QtSource
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
+
+# special case begin
+add_custom_command(
+ TARGET QtSource POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ ${CMAKE_CURRENT_SOURCE_DIR}/../py_replica/py_replica.py
+ $<TARGET_FILE_DIR:QtSource>/../py_replica/py_replica.py
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ ${CMAKE_CURRENT_SOURCE_DIR}/Simple.rep
+ $<TARGET_FILE_DIR:QtSource>/Simple.rep
+ )
+install(FILES ../py_replica/py_replica.py DESTINATION "${INSTALL_EXAMPLEDIR}/../py_replica")
+install(FILES Simple.rep DESTINATION "${INSTALL_EXAMPLEDIR}")
+# special case end
diff --git a/examples/remoteobjects/cbor_python/qt_source/Simple.rep b/examples/remoteobjects/cbor_python/qt_source/Simple.rep
new file mode 100644
index 0000000..1757ae6
--- /dev/null
+++ b/examples/remoteobjects/cbor_python/qt_source/Simple.rep
@@ -0,0 +1,9 @@
+#include <QtCore>
+
+class Simple
+{
+ PROP(int i = 2);
+ PROP(float f = -1. READWRITE);
+ SIGNAL(random(int i));
+ SLOT(void reset());
+};
diff --git a/examples/remoteobjects/cbor_python/qt_source/main.cpp b/examples/remoteobjects/cbor_python/qt_source/main.cpp
new file mode 100644
index 0000000..db04ea4
--- /dev/null
+++ b/examples/remoteobjects/cbor_python/qt_source/main.cpp
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 Ford Motor Company
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtRemoteObjects module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QCoreApplication>
+#include <QDebug>
+#include <QRemoteObjectHost>
+
+#include "simple.h"
+
+void SigIntHandler()
+{
+ qDebug()<<"Ctrl-C received. Quitting.";
+ qApp->quit();
+}
+#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_QNX)
+ #include <signal.h>
+ void unix_handler(int s)
+ {
+ if (s==SIGINT)
+ SigIntHandler();
+ }
+#elif defined(Q_OS_WIN32)
+ #include <windows.h>
+ BOOL WINAPI WinHandler(DWORD CEvent)
+ {
+ switch (CEvent)
+ {
+ case CTRL_C_EVENT:
+ SigIntHandler();
+ break;
+ }
+ return TRUE;
+ }
+#endif
+
+int main(int argc, char *argv[])
+{
+ QLoggingCategory::setFilterRules("qt.remoteobjects*=true");
+ QCoreApplication app(argc, argv);
+#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_QNX)
+ signal(SIGINT, &unix_handler);
+#elif defined(Q_OS_WIN32)
+ SetConsoleCtrlHandler((PHANDLER_ROUTINE)WinHandler, TRUE);
+#endif
+ QRemoteObjectHost node(QUrl(QLatin1String("tcp://127.0.0.1:5005")));
+ Simple simple;
+ simple.setF(3.14);
+ node.enableRemoting(&simple, "DifferentName");
+ Q_UNUSED(simple);
+ return app.exec();
+}
diff --git a/examples/remoteobjects/cbor_python/qt_source/qt_source.pro b/examples/remoteobjects/cbor_python/qt_source/qt_source.pro
new file mode 100644
index 0000000..16895ee
--- /dev/null
+++ b/examples/remoteobjects/cbor_python/qt_source/qt_source.pro
@@ -0,0 +1,26 @@
+QT += remoteobjects
+
+TARGET = QtSource
+CONFIG += cmdline
+CONFIG -= app_bundle
+
+REPC_SOURCE += Simple.rep
+
+DISTFILES += \
+ Simple.rep
+
+SOURCES += main.cpp \
+ simple.cpp
+
+HEADERS += simple.h
+
+OTHER_FILES += $$REPC_SOURCE
+
+repfiles.files = $$REPC_SOURCE
+repfiles.path += $$[QT_INSTALL_EXAMPLES]/remoteobjects/cbor_python/qt_source
+pythonfiles.files = py_replica/py_replica.py
+pythonfiles.path += $$[QT_INSTALL_EXAMPLES]/remoteobjects/cbor_python/py_replica
+
+target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/cbor_python/qt_source
+
+INSTALLS += target repfiles pythonfiles
diff --git a/examples/remoteobjects/cbor_python/qt_source/simple.cpp b/examples/remoteobjects/cbor_python/qt_source/simple.cpp
new file mode 100644
index 0000000..78f7995
--- /dev/null
+++ b/examples/remoteobjects/cbor_python/qt_source/simple.cpp
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 Ford Motor Company
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtRemoteObjects module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "simple.h"
+
+Simple::Simple(QObject *parent) : SimpleSimpleSource(parent)
+{
+}
+
+void Simple::reset()
+{
+ setI(0);
+ m_timerId = startTimer(500);
+}
+
+void Simple::timerEvent(QTimerEvent *)
+{
+ auto count = i();
+ setI(count + 1);
+ if (count >= 3) {
+ killTimer(m_timerId);
+ }
+}
diff --git a/examples/remoteobjects/cbor_python/qt_source/simple.h b/examples/remoteobjects/cbor_python/qt_source/simple.h
new file mode 100644
index 0000000..c69934b
--- /dev/null
+++ b/examples/remoteobjects/cbor_python/qt_source/simple.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 Ford Motor Company
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtRemoteObjects module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SIMPLE_H
+#define SIMPLE_H
+
+#include "rep_Simple_source.h"
+
+class Simple : public SimpleSimpleSource
+{
+ Q_OBJECT
+public:
+ explicit Simple(QObject *parent = nullptr);
+
+signals:
+
+public slots:
+ void reset() override;
+ void timerEvent(QTimerEvent *) override;
+
+private:
+ int m_timerId;
+};
+
+#endif // SIMPLE_H
diff --git a/examples/remoteobjects/pyside/replica.py b/examples/remoteobjects/pyside/replica.py
new file mode 100644
index 0000000..a0c7843
--- /dev/null
+++ b/examples/remoteobjects/pyside/replica.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+#############################################################################
+##
+## Copyright (C) 2021 Ford Motor Company
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the QtRemoteObjects module of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## Commercial License Usage
+## Licensees holding valid commercial Qt licenses may use this file in
+## accordance with the commercial license agreement provided with the
+## Software or, alternatively, in accordance with the terms contained in
+## a written agreement between you and The Qt Company. For licensing terms
+## and conditions see https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## BSD License Usage
+## Alternatively, you may use this file under the terms of the BSD license
+## as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import signal
+import sys
+from PySide6.QtCore import QCoreApplication, QUrl
+from PySide6.QtRemoteObjects import QRemoteObjectNode
+from simple import SimpleReplica
+
+if __name__ == '__main__':
+ app = QCoreApplication(sys.argv)
+ node = QRemoteObjectNode()
+ node.connectToNode(QUrl("tcp://127.0.0.1:5005"))
+ # TODO: Fix PySide so we can use acquire() here
+ rep = SimpleReplica(node, "DifferentName")
+ def init():
+ print(f'after initialization i = {rep.i} f = {rep.f}')
+ rep.reset()
+ rep.pushI(42)
+ rep.iChanged.connect(lambda i: print(f'Updated i received: {i}'))
+ # Changing f doesn't work from C++ due to float being 4 bytes in C++ and 8 in python
+ rep.f = 4.5
+ rep.fChanged.connect(lambda f: print(f'Updated f received: {f}'))
+ print(f'start: i = {rep.i} f = {rep.f}')
+
+ rep.initialized.connect(lambda: init())
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+ sys.exit(app.exec())
diff --git a/examples/remoteobjects/pyside/simple.py b/examples/remoteobjects/pyside/simple.py
new file mode 100644
index 0000000..bd56f7c
--- /dev/null
+++ b/examples/remoteobjects/pyside/simple.py
@@ -0,0 +1,196 @@
+#!/usr/bin/env python3
+#############################################################################
+##
+## Copyright (C) 2021 Ford Motor Company
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the QtRemoteObjects module of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## Commercial License Usage
+## Licensees holding valid commercial Qt licenses may use this file in
+## accordance with the commercial license agreement provided with the
+## Software or, alternatively, in accordance with the terms contained in
+## a written agreement between you and The Qt Company. For licensing terms
+## and conditions see https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## BSD License Usage
+## Alternatively, you may use this file under the terms of the BSD license
+## as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+'''
+TODO: Remove before merging. This is an example generated by the updated
+ repc to show what the generated code would look like and simplify
+ trying the examples.
+'''
+
+from abc import abstractmethod, ABC, ABCMeta
+from typing import TypeVar, Generic, Iterator
+from PySide6.QtCore import (ClassInfo, Property, QMetaObject,
+ QObject, Signal, Slot)
+from PySide6.QtRemoteObjects import QRemoteObjectReplica
+
+QObjectType = type(QObject)
+T = TypeVar('T')
+
+class QABCMeta(QObjectType, ABCMeta):
+ pass
+
+@ClassInfo({'RemoteObject Type':'Simple', 'RemoteObject Signature':'c6f33edb0554ba4241aad1286a47c8189d65c845'})
+class SimpleSource(QObject, Generic[T], metaclass=QABCMeta):
+ iChanged = Signal(int)
+ fChanged = Signal(float)
+ random = Signal(int)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ def _getI(self):
+ return self.getI()
+
+ @abstractmethod
+ def getI(self):
+ pass
+
+ def _setI(self, i):
+ return self.setI(i)
+
+ @abstractmethod
+ def setI(self, i):
+ pass
+
+ @abstractmethod
+ def pushI(self, i):
+ pass
+
+ @Slot(int, name='pushI')
+ def _pushI(self, i):
+ return self.pushI(i)
+
+ i = Property(int, _getI, _setI, notify = iChanged)
+
+ def _getF(self):
+ return self.getF()
+
+ @abstractmethod
+ def getF(self):
+ pass
+
+ def _setF(self, f):
+ return self.setF(f)
+
+ @abstractmethod
+ def setF(self, f):
+ pass
+
+ f = Property(float, _getF, _setF, notify = fChanged)
+
+
+ @abstractmethod
+ def reset(self):
+ pass
+
+ @Slot(name='reset')
+ def _reset(self):
+ return self.reset()
+
+class SimpleSimpleSource(SimpleSource):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._i = int(2)
+ self._f = float(-1.)
+
+ def getI(self):
+ return self._i
+
+ def pushI(self, i):
+ self.setI(i)
+
+ def setI(self, i):
+ if i != self._i:
+ self._i = i
+ self.iChanged.emit(i)
+
+ def getF(self):
+ return self._f
+
+ def setF(self, f):
+ if f != self._f:
+ self._f = f
+ self.fChanged.emit(f)
+
+@ClassInfo({'RemoteObject Type':'Simple', 'RemoteObject Signature':'c6f33edb0554ba4241aad1286a47c8189d65c845'})
+class SimpleReplica(QRemoteObjectReplica):
+ iChanged = Signal(int)
+ fChanged = Signal(float)
+ random = Signal(int)
+ pushI_index = -1
+ setF_index = -1
+ reset_index = -1
+
+ def __init__(self, node=None, name=None):
+ super().__init__()
+ self.initialize()
+ if SimpleReplica.pushI_index == -1:
+ SimpleReplica.pushI_index = self.metaObject().indexOfSlot('pushI(int)')
+ SimpleReplica.setF_index = self.metaObject().indexOfProperty('f')
+ SimpleReplica.reset_index = self.metaObject().indexOfSlot('reset()')
+ if node:
+ self.initializeNode(node, name)
+
+ def initialize(self):
+ self.setProperties([int(2), float(-1.)])
+
+ def getI(self):
+ return self.propAsVariant(0)
+
+ @Slot(int)
+ def pushI(self, i):
+ self.send(QMetaObject.InvokeMetaMethod, SimpleReplica.pushI_index, [i])
+
+ i = Property(int, getI, notify = iChanged)
+
+ def getF(self):
+ return self.propAsVariant(1)
+
+ def setF(self, f):
+ self.send(QMetaObject.WriteProperty, SimpleReplica.setF_index, [f])
+
+ f = Property(float, getF, setF, notify = fChanged)
+
+ @Slot()
+ def reset(self):
+ self.send(QMetaObject.InvokeMetaMethod, SimpleReplica.reset_index, [])
+
diff --git a/examples/remoteobjects/pyside/source.py b/examples/remoteobjects/pyside/source.py
new file mode 100644
index 0000000..f470d2a
--- /dev/null
+++ b/examples/remoteobjects/pyside/source.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+#############################################################################
+##
+## Copyright (C) 2021 Ford Motor Company
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the QtRemoteObjects module of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## Commercial License Usage
+## Licensees holding valid commercial Qt licenses may use this file in
+## accordance with the commercial license agreement provided with the
+## Software or, alternatively, in accordance with the terms contained in
+## a written agreement between you and The Qt Company. For licensing terms
+## and conditions see https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## BSD License Usage
+## Alternatively, you may use this file under the terms of the BSD license
+## as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import sys
+import signal
+from PySide6.QtCore import QCoreApplication, QUrl, Slot
+from PySide6.QtRemoteObjects import QRemoteObjectHost
+from simple import SimpleSource, SimpleSimpleSource
+
+
+class Simple(SimpleSimpleSource):
+
+ def __init__(self, _i, _f):
+ super().__init__()
+ self.setI(_i)
+ self.setF(_f)
+
+ @Slot()
+ def reset(self):
+ print(f'Derived Reset() called')
+
+
+if __name__ == '__main__':
+ app = QCoreApplication(sys.argv)
+ node = QRemoteObjectHost(QUrl("tcp://127.0.0.1:5005"))
+ toosimple = SimpleSource() # This should fail when abc is working
+ simple = SimpleSimpleSource() # This should fail, too
+ working = Simple(3, 3.14)
+ print(f'initial: i = {working.i}, f = {working.f}')
+ node.enableRemoting(working, "DifferentName")
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+ sys.exit(app.exec())
diff --git a/examples/remoteobjects/remoteobjects.pro b/examples/remoteobjects/remoteobjects.pro
index 9b22d21..10a6e80 100644
--- a/examples/remoteobjects/remoteobjects.pro
+++ b/examples/remoteobjects/remoteobjects.pro
@@ -3,6 +3,7 @@ CONFIG += debug_and_release ordered
SUBDIRS = \
server \
cppclient \
+ cbor_python \
simpleswitch \
websockets
diff --git a/tools/repc/CMakeLists.txt b/tools/repc/CMakeLists.txt
index a0fb8a5..9e22e51 100644
--- a/tools/repc/CMakeLists.txt
+++ b/tools/repc/CMakeLists.txt
@@ -14,6 +14,7 @@ qt_internal_add_tool(${target_name}
cppcodegenerator.cpp cppcodegenerator.h
main.cpp
repcodegenerator.cpp repcodegenerator.h
+ pythoncodegenerator.cpp pythoncodegenerator.h
utils.cpp utils.h
DEFINES
QT_NO_CAST_FROM_ASCII
diff --git a/tools/repc/main.cpp b/tools/repc/main.cpp
index 6c402ea..3d57321 100644
--- a/tools/repc/main.cpp
+++ b/tools/repc/main.cpp
@@ -35,6 +35,7 @@
#include <qjsonarray.h>
#include "cppcodegenerator.h"
+#include "pythoncodegenerator.h"
#include "repcodegenerator.h"
#include "repparser.h"
#include "utils.h"
@@ -50,11 +51,18 @@ enum Mode {
OutRep = 4,
OutSource = 8,
OutReplica = 16,
- OutMerged = OutSource | OutReplica
+ OutPython = 32,
+ OutPyside = 64,
+ OutMerged = OutSource | OutReplica,
+ OutPyVariant = OutPython | OutPyside,
+ OutSelected = OutRep | OutMerged | OutPyVariant
};
static const QLatin1String REP("rep");
static const QLatin1String JSON("json");
+static const QLatin1String PY("py");
+static const QLatin1String PYTHON("python");
+static const QLatin1String PYSIDE("pyside");
static const QLatin1String REPLICA("replica");
static const QLatin1String SOURCE("source");
static const QLatin1String MERGED("merged");
@@ -140,6 +148,10 @@ int main(int argc, char **argv)
mode |= OutSource;
else if (outputType == MERGED)
mode |= OutMerged;
+ else if (outputType == PY || outputType == PYTHON)
+ mode |= OutPython;
+ else if (outputType == PYSIDE)
+ mode |= OutPyside;
else {
fprintf(stderr, PROGRAM_NAME ": Unknown output type\"%s\".\n", qPrintable(outputType));
parser.showHelp(1);
@@ -149,10 +161,12 @@ int main(int argc, char **argv)
switch (files.count()) {
case 2:
outputFile = files.last();
- if (!(mode & (OutRep | OutSource | OutReplica))) {
+ if (!(mode & (OutRep | OutSource | OutReplica | OutPython | OutPyside))) {
// try to figure out the Out mode from file extension
if (outputFile.endsWith(QLatin1String(".rep")))
mode |= OutRep;
+ if (outputFile.endsWith(QLatin1String(".py")))
+ mode |= OutPython;
}
Q_FALLTHROUGH();
case 1:
@@ -171,7 +185,7 @@ int main(int argc, char **argv)
fprintf(stderr, PROGRAM_NAME ": Unknown input type, please use -i option to specify one.\n");
parser.showHelp(1);
}
- if (!(mode & (OutRep | OutSource | OutReplica))) {
+ if (!(mode & (OutSelected))) {
fprintf(stderr, PROGRAM_NAME ": Unknown output type, please use -o option to specify one.\n");
parser.showHelp(1);
}
@@ -228,10 +242,15 @@ int main(int argc, char **argv)
if (mode & OutRep) {
CppCodeGenerator generator(&output);
generator.generate(classes, parser.isSet(alwaysClassOption));
- } else {
- Q_ASSERT(mode & OutReplica);
+ } else if (mode & OutReplica) {
RepCodeGenerator generator(&output);
- generator.generate(classList2AST(classes), RepCodeGenerator::REPLICA, outputFile);
+ generator.generate(classList2AST(classes), GeneratorBase::Mode::Replica, outputFile);
+ } else if (mode & OutPyVariant) {
+ PythonCodeGenerator generator(&output);
+ if (mode & OutPython)
+ generator.generate(classList2AST(classes), PythonCodeGenerator::OutputStyle::DataStream);
+ else
+ generator.generate(classList2AST(classes), PythonCodeGenerator::OutputStyle::PySide);
}
} else {
Q_ASSERT(!(mode & OutRep));
@@ -249,16 +268,24 @@ int main(int argc, char **argv)
input.close();
- RepCodeGenerator generator(&output);
- if ((mode & OutMerged) == OutMerged)
- generator.generate(repparser.ast(), RepCodeGenerator::MERGED, outputFile);
- else if (mode & OutReplica)
- generator.generate(repparser.ast(), RepCodeGenerator::REPLICA, outputFile);
- else if (mode & OutSource)
- generator.generate(repparser.ast(), RepCodeGenerator::SOURCE, outputFile);
- else {
- fprintf(stderr, PROGRAM_NAME ": Unknown mode.\n");
- return 1;
+ if (mode & OutPyVariant) {
+ PythonCodeGenerator generator(&output);
+ if (mode & OutPython)
+ generator.generate(repparser.ast(), PythonCodeGenerator::OutputStyle::DataStream);
+ else
+ generator.generate(repparser.ast(), PythonCodeGenerator::OutputStyle::PySide);
+ } else {
+ RepCodeGenerator generator(&output);
+ if ((mode & OutMerged) == OutMerged)
+ generator.generate(repparser.ast(), GeneratorBase::Mode::Merged, outputFile);
+ else if (mode & OutReplica)
+ generator.generate(repparser.ast(), GeneratorBase::Mode::Replica, outputFile);
+ else if (mode & OutSource)
+ generator.generate(repparser.ast(), GeneratorBase::Mode::Source, outputFile);
+ else {
+ fprintf(stderr, PROGRAM_NAME ": Unknown mode.\n");
+ return 1;
+ }
}
}
diff --git a/tools/repc/pythoncodegenerator.cpp b/tools/repc/pythoncodegenerator.cpp
new file mode 100644
index 0000000..bfd7bef
--- /dev/null
+++ b/tools/repc/pythoncodegenerator.cpp
@@ -0,0 +1,575 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 Ford Motor Company
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtRemoteObjects module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "pythoncodegenerator.h"
+#include "repparser.h"
+#include "utils.h"
+
+#include <QScopedPointer>
+#include <QStringList>
+#include <QTextStream>
+
+typedef QLatin1String l1;
+
+static QString cap(QString name)
+{
+ if (!name.isEmpty())
+ name[0] = name[0].toUpper();
+ return name;
+}
+
+class PythonGenerator : public GeneratorImplBase
+{
+public:
+ PythonGenerator(QTextStream &_stream) : GeneratorImplBase(_stream) {}
+ bool generateClass(const ASTClass &astClass, Mode mode) override;
+ void generateEnum(const ASTEnum &) override {}
+ void generatePod(const POD &) override {}
+ QString typeLookup(const QString &type) const;
+ QString paramString(const QStringList &params) const;
+ QString tupleString(const QStringList &packParams) const;
+};
+
+class PysideGenerator : public GeneratorImplBase
+{
+public:
+ PysideGenerator(QTextStream &_stream) : GeneratorImplBase(_stream) {}
+ bool generateClass(const ASTClass &astClass, Mode mode) override;
+ void generateEnum(const ASTEnum &) override {}
+ void generatePod(const POD &) override {}
+ void generatePrefix(const AST &) override;
+ QString typeLookup(const QString &type) const;
+ QString paramString(const QStringList &params) const;
+ QString tupleString(const QStringList &packParams) const;
+};
+
+bool PythonGenerator::generateClass(const ASTClass &astClass, Mode mode)
+{
+ if (mode != Mode::Replica) // For now support Replica only
+ return true;
+
+ bool validClass = true;
+ QStringList signalNames;
+ QStringList propertyPackTypes, propertyDefaults;
+ for (const auto &prop : astClass.properties) {
+ signalNames << l1("%1Changed").arg(prop.name);
+ auto packType = typeLookup(prop.type);
+ if (packType.isEmpty()) {
+ qWarning() << "Invalid property" << prop.name << "of type" << prop.type
+ << "as the type isn't convertible to/from python.";
+ validClass = false;
+ continue;
+ }
+
+ propertyPackTypes << packType;
+ propertyDefaults << (prop.defaultValue.isNull() ? l1("None") : prop.defaultValue);
+ }
+
+ QList<QStringList> signalPackParameters;
+ for (const auto &sig : astClass.signalsList) {
+ signalNames << sig.name;
+ QStringList packParams;
+ for (const auto &param : sig.params) {
+ auto paramType = typeLookup(param.type);
+ if (paramType.isEmpty()) {
+ qWarning() << "Invalid signal parameter of" << sig.name << "of type" << param.type
+ << "as the type isn't convertible to/from python.";
+ validClass = false;
+ continue;
+ }
+ packParams << paramType;
+ }
+ signalPackParameters << packParams;
+ }
+
+ QStringList slotPackReturnValues;
+ QList<QStringList> slotPackParameters;
+ for (const auto &slot : astClass.slotsList) {
+ QStringList packParams;
+ if (slot.returnType == QLatin1String("void")) {
+ slotPackReturnValues << QString();
+ } else {
+ auto returnType = typeLookup(slot.returnType);
+ if (returnType.isEmpty()) {
+ qWarning() << "Invalid slot return type of" << slot.name << "of type"
+ << slot.returnType << "as the type isn't convertible to/from python.";
+ validClass = false;
+ continue;
+ }
+ slotPackReturnValues << returnType;
+ }
+ for (const auto &param : slot.params) {
+ auto paramType = typeLookup(param.type);
+ if (paramType.isEmpty()) {
+ qWarning() << "Invalid slot parameter of" << slot.name << "of type" << param.type
+ << "as the type isn't convertible to/from python.";
+ validClass = false;
+ continue;
+ }
+ packParams << paramType;
+ }
+ slotPackParameters << packParams;
+ }
+
+ if (!validClass)
+ return false;
+
+ stream << "@dataclass(eq=False)\n";
+ stream << "class " << astClass.name << "(Replica):\n";
+ if (signalNames.count()) {
+ stream << " Signals = ['" << signalNames.join(l1("', '")) << "']\n";
+ for (const auto &name : signalNames)
+ stream << " " << name << ": Signal = field(init=False, repr=False, "
+ << "default_factory=Signal)\n";
+ }
+
+ int index = -1;
+ int methodIndex = 0;
+ for (const auto &prop : astClass.properties) {
+ index++;
+ stream << "\n";
+ stream << " @property\n";
+ stream << " def " << prop.name << "(self):\n";
+ stream << " return self._priv.properties[" << index << "]\n";
+ switch (prop.modifier) {
+ case ASTProperty::Constant:
+ case ASTProperty::ReadOnly:
+ case ASTProperty::SourceOnlySetter:
+ case ASTProperty::ReadPush:
+ stream << "\n";
+ stream << " def push" << cap(prop.name) << "(self, " << prop.name << "):\n";
+ stream << " print('push" << cap(prop.name) << "', " << prop.name << ")\n";
+ stream << " self.callSlot(" << methodIndex++ << ", " << prop.name << ")\n";
+ break;
+ case ASTProperty::ReadWrite:
+ stream << "\n";
+ stream << " @" << prop.name << ".setter\n";
+ stream << " def " << prop.name << "(self, " << prop.name << "):\n";
+ stream << " print('" << prop.name << " setter', " << prop.name << ")\n";
+ stream << " self.callSetter(" << index << ", " << prop.name << ")\n";
+ break;
+ }
+ }
+
+ for (const auto &slot : astClass.slotsList) {
+ static bool firstLine = true;
+ if (firstLine) {
+ stream << "\n";
+ firstLine = false;
+ }
+ QStringList paramNames;
+ for (const auto &param : slot.params)
+ paramNames << param.name;
+ auto parameters = paramString(paramNames);
+ stream << " def " << slot.name << "(self" << parameters << "):\n";
+ stream << " print('Calling " << slot.name << " slot')\n";
+ stream << " self.callSlot(" << methodIndex++ << parameters << ")\n";
+ }
+
+ stream << "\n";
+ stream << " @classmethod\n";
+ stream << " def defaults(cls):\n";
+ stream << " return [" << propertyDefaults.join(l1(", ")) << "]\n";
+
+ stream << "\n";
+ stream << " @classmethod\n";
+ stream << " def propTypes(cls):\n";
+ stream << " return [\n";
+ for (int i = 0; i < astClass.properties.count(); i++) {
+ const auto prop = astClass.properties.at(i);
+ stream << " ('" << propertyPackTypes.at(i) << "',), # " << prop.name
+ << " is of type " << prop.type << " -> " << propertyPackTypes.at(i) << "\n";
+ }
+ stream << " ]\n";
+
+ stream << "\n";
+ stream << " @classmethod\n";
+ stream << " def signalTypes(cls):\n";
+ stream << " return [\n";
+ for (int i = 0; i < astClass.properties.count(); i++)
+ stream << " ('" << propertyPackTypes.at(i) << "',),\n";
+ for (int i = 0; i < astClass.signalsList.count(); i++)
+ stream << " " << tupleString(signalPackParameters.at(i)) << ",\n";
+ stream << " ]\n";
+
+ stream << "\n";
+ stream << " @classmethod\n";
+ stream << " def slotTypes(cls):\n";
+ stream << " return [\n";
+ for (int i = 0; i < astClass.properties.count(); i++) {
+ auto prop = astClass.properties.at(i);
+ if (prop.modifier == ASTProperty::ReadPush)
+ stream << " ('" << propertyPackTypes.at(i) << "',),\n";
+ }
+ for (int i = 0; i < astClass.slotsList.count(); i++)
+ stream << " " << tupleString(slotPackParameters.at(i)) << ",\n";
+ stream << " ]\n";
+
+ stream << "\n";
+ stream << " @classmethod\n";
+ stream << " def signature(cls):\n";
+ stream << " return b'" << classSignature(astClass) << "'\n";
+
+ stream << "\n";
+ stream << " @classmethod\n";
+ stream << " def name(cls):\n";
+ stream << " return '" << astClass.name << "'\n";
+
+ return true;
+}
+
+QString PythonGenerator::typeLookup(const QString &type) const
+{
+ const static QMap<QString, QString> primitiveTypes{
+ {l1("char"), l1("c")},
+ {l1("bool"), l1("?")},
+ {l1("short"), l1("h")},
+ {l1("unsigned short"), l1("H")},
+ {l1("int"), l1("i")},
+ {l1("unsigned int"), l1("I")},
+ {l1("long"), l1("l")},
+ {l1("unsigned long"), l1("L")},
+ {l1("long long"), l1("q")},
+ {l1("unsigned long long"), l1("Q")},
+ {l1("float"), l1("f")},
+ {l1("double"), l1("d")},
+ };
+ if (primitiveTypes.contains(type))
+ return primitiveTypes[type];
+
+ return {};
+}
+
+QString PythonGenerator::paramString(const QStringList &params) const
+{
+ // Return a string to add to a function call of parameters
+ // This will either follow "self" or an index, so it should
+ // be an empty string or start with a ","
+ if (params.count() == 0)
+ return QString();
+
+ return l1(", %1").arg(params.join(l1(", ")));
+}
+
+QString PythonGenerator::tupleString(const QStringList &packParams) const
+{
+ // Return a string for a proper python tuple for the classmethods
+ if (packParams.count() == 0)
+ return l1("()");
+
+ if (packParams.count() > 1)
+ return l1("('%1')").arg(packParams.join(l1("', '")));
+
+ return l1("('%1',)").arg(packParams.at(0));
+}
+
+bool PysideGenerator::generateClass(const ASTClass &astClass, Mode mode)
+{
+ if (mode == Mode::SimpleSource) {
+ stream << "class " << astClass.name << "SimpleSource(" << astClass.name << "Source):\n\n";
+ stream << " def __init__(self, parent=None):\n";
+ stream << " super().__init__(parent)\n";
+ for (const auto &prop : astClass.properties) {
+ if (!prop.defaultValue.isNull())
+ stream << " self._" << prop.name << " = " << prop.type << "("
+ << prop.defaultValue << ")\n";
+ }
+
+ for (const auto &prop : astClass.properties) {
+ stream << "\n";
+ stream << " def get" << cap(prop.name) << "(self):\n";
+ stream << " return self._" << prop.name << "\n";
+ if (hasPush(prop, mode))
+ {
+ stream << "\n";
+ stream << " def push" << cap(prop.name) << "(self, " << prop.name << "):\n";
+ stream << " self.set" << cap(prop.name) << "(" << prop.name << ")\n";
+ }
+ if (hasSetter(prop, mode))
+ {
+ stream << "\n";
+ stream << " def set" << cap(prop.name) << "(self, " << prop.name << "):\n";
+ stream << " if " << prop.name << " != self._" << prop.name << ":\n";
+ stream << " self._" << prop.name << " = " << prop.name << "\n";
+ stream << " self." << prop.name << "Changed.emit("
+ << prop.name << ")\n";
+ }
+ }
+ stream << "\n";
+ return true;
+ }
+
+ stream << "@ClassInfo({'RemoteObject Type':'" << astClass.name
+ << "', 'RemoteObject Signature':'" << classSignature(astClass) << "'})\n";
+
+ if (mode == Mode::Source)
+ stream << "class " << astClass.name
+ << "Source(QObject, Generic[T], metaclass=QABCMeta):\n";
+ else
+ stream << "class " << astClass.name << "Replica(QRemoteObjectReplica):\n";
+ for (const auto &prop : astClass.properties) {
+ if (hasNotify(prop, mode))
+ stream << " " << l1("%1Changed").arg(prop.name) << " = Signal("
+ << prop.type << ")\n";
+ }
+ for (const auto &sig : astClass.signalsList) {
+ if (sig.params.count() == 0) {
+ stream << " " << sig.name << " = Signal()\n";
+ } else {
+ stream << " " << sig.name << " = Signal(" << sig.params.at(0).type;
+ for (int index = 1; index < sig.params.count(); index++)
+ stream << ", " << sig.params.at(index).type;
+ stream << ")\n";
+ }
+ }
+ int indexCount = 0;
+ if (mode == Mode::Replica) {
+ for (const auto &prop : astClass.properties) {
+ if (hasPush(prop, mode)) {
+ indexCount++;
+ stream << " push" << cap(prop.name) << "_index = -1\n";
+ } else if (hasSetter(prop, mode)) {
+ indexCount++;
+ stream << " set" << cap(prop.name) << "_index = -1\n";
+ }
+ }
+ for (const auto &slot : astClass.slotsList) {
+ stream << " " << slot.name << "_index = -1\n";
+ indexCount++;
+ }
+ }
+ stream << "\n";
+ if (mode == Mode::Source) {
+ stream << " def __init__(self, parent=None):\n";
+ stream << " super().__init__(parent)\n";
+ } else {
+ stream << " def __init__(self, node=None, name=None):\n";
+ stream << " super().__init__()\n";
+ stream << " self.initialize()\n";
+ if (indexCount > 0) {
+ bool first = true;
+ for (const auto &prop : astClass.properties) {
+ if (hasPush(prop, mode)) {
+ if (first) {
+ stream << " if " << astClass.name << "Replica.push"
+ << cap(prop.name) << "_index == -1:\n";
+ first = false;
+ }
+ stream << " " << astClass.name << "Replica.push" << cap(prop.name)
+ << "_index = self.metaObject().indexOfSlot('push" << cap(prop.name)
+ << "(" << prop.type << ")')\n";
+ } else if (hasSetter(prop, mode)) {
+ if (first) {
+ stream << " if " << astClass.name << "Replica.set"
+ << cap(prop.name) << "_index == -1:\n";
+ first = false;
+ }
+ stream << " " << astClass.name << "Replica.set" << cap(prop.name)
+ << "_index = self.metaObject().indexOfProperty('"
+ << prop.name << "')\n";
+ }
+ }
+ for (const auto &slot : astClass.slotsList) {
+ if (first) {
+ stream << " if " << astClass.name << "Replica." << slot.name
+ << "_index == -1:\n";
+ first = false;
+ }
+ stream << " " << astClass.name << "Replica." << slot.name
+ << "_index = self.metaObject().indexOfSlot('" << slot.name << "("
+ << slot.paramsAsString(ASTFunction::Normalized) << ")')\n";
+ }
+
+ }
+ stream << " if node:\n";
+ stream << " self.initializeNode(node, name)\n\n";
+ stream << " def initialize(self):\n";
+ stream << " self.setProperties([";
+ bool first = true;
+ for (const auto &prop : astClass.properties) {
+ if (!first)
+ stream << ", ";
+ stream << prop.type << "(" << prop.defaultValue << ")";
+ first = false;
+ }
+ stream << "])\n";
+ }
+
+ int index = -1;
+ for (const auto &prop : astClass.properties) {
+ index++;
+ stream << "\n";
+ if (mode == Mode::Source) {
+ stream << " def _get" << cap(prop.name) << "(self):\n";
+ stream << " return self.get" << cap(prop.name) << "()\n\n";
+ stream << " @abstractmethod\n";
+ }
+ stream << " def get" << cap(prop.name) << "(self):\n";
+ if (mode == Mode::Source)
+ stream << " pass\n";
+ else
+ stream << " return self.propAsVariant(" << index << ")\n";
+ if (hasSetter(prop, mode)) {
+ stream << "\n";
+ if (mode == Mode::Source) {
+ stream << " def _set" << cap(prop.name) << "(self, " << prop.name << "):\n";
+ stream << " return self.set" << cap(prop.name) << "(" << prop.name
+ << ")\n\n";
+ stream << " @abstractmethod\n";
+ }
+ stream << " def set" << cap(prop.name) << "(self, " << prop.name << "):\n";
+ if (mode == Mode::Source)
+ stream << " pass\n";
+ else
+ stream << " self.send(QMetaObject.WriteProperty, " << astClass.name
+ << "Replica.set" << cap(prop.name) << "_index, [" << prop.name << "])\n";
+ }
+ if (hasPush(prop, mode)) {
+ stream << "\n";
+ if (mode == Mode::Source)
+ stream << " @abstractmethod\n";
+ else
+ stream << " @Slot(" << prop.type << ")\n";
+ stream << " def push" << cap(prop.name) << "(self, " << prop.name << "):\n";
+ if (mode == Mode::Source) {
+ stream << " pass\n\n";
+ stream << " @Slot(" << prop.type << ", name='push" << cap(prop.name) << "')\n";
+ stream << " def _push" << cap(prop.name) << "(self, " << prop.name << "):\n";
+ stream << " return self.push" << cap(prop.name) << "(" << prop.name
+ << ")\n";
+ } else {
+ stream << " self.send(QMetaObject.InvokeMetaMethod, " << astClass.name
+ << "Replica.push" << cap(prop.name) << "_index, [" << prop.name << "])\n";
+ }
+ }
+ stream << "\n";
+ if (mode == Mode::Source)
+ stream << " " << prop.name << " = Property(" << prop.type << ", _get"
+ << cap(prop.name);
+ else
+ stream << " " << prop.name << " = Property(" << prop.type << ", get"
+ << cap(prop.name);
+ if (hasSetter(prop, mode)) {
+ if (mode == Mode::Source)
+ stream << ", _set" << cap(prop.name);
+ else
+ stream << ", set" << cap(prop.name);
+ }
+ if (hasNotify(prop, mode))
+ stream << ", notify = " << prop.name << "Changed";
+ stream << ")\n";
+ }
+
+ stream << "\n";
+ for (const auto &slot : astClass.slotsList) {
+ static bool firstLine = true;
+ if (firstLine) {
+ stream << "\n";
+ firstLine = false;
+ }
+ QStringList names, types;
+ for (const auto &param : slot.params) {
+ names << param.name;
+ types << param.type;
+ }
+ if (mode == Mode::Source)
+ stream << " @abstractmethod\n";
+ else
+ stream << " @Slot(" << types.join(l1(", ")) << ")\n";
+ if (names.count() == 0)
+ stream << " def " << slot.name << "(self):\n";
+ else
+ stream << " def " << slot.name << "(self, " << names.join(l1(", ")) << "):\n";
+ if (mode == Mode::Source) {
+ stream << " pass\n\n";
+ stream << " @Slot(" << types.join(l1(", ")) << (names.count() == 0 ? "" : ", ")
+ << "name='" << slot.name << "')\n";
+ stream << " def _" << slot.name << "(self" << (names.count() == 0 ? "" : ", ")
+ << names.join(l1(", ")) << "):\n";
+ stream << " return self." << slot.name << "(" << names.join(l1(", "))
+ << ")\n";
+ } else {
+ stream << " self.send(QMetaObject.InvokeMetaMethod, " << astClass.name
+ << "Replica." << slot.name << "_index, [" << names.join(l1(", ")) << "])\n";
+ }
+ }
+ stream << "\n";
+ return true;
+}
+
+void PysideGenerator::generatePrefix(const AST &)
+{
+ stream << "from abc import abstractmethod, ABC, ABCMeta\n";
+ stream << "from typing import TypeVar, Generic, Iterator\n";
+ stream << "from PySide6.QtCore import (ClassInfo, Property, QMetaObject,\n";
+ stream << " QObject, Signal, Slot)\n";
+ stream << "from PySide6.QtRemoteObjects import QRemoteObjectReplica\n\n";
+
+ stream << "QObjectType = type(QObject)\n";
+ stream << "T = TypeVar('T')\n\n";
+
+ stream << "class QABCMeta(QObjectType, ABCMeta):\n";
+ stream << " pass\n\n";
+}
+
+QT_BEGIN_NAMESPACE
+
+PythonCodeGenerator::PythonCodeGenerator(QIODevice *outputDevice)
+ : m_outputDevice(outputDevice)
+{
+ Q_ASSERT(m_outputDevice);
+}
+
+void PythonCodeGenerator::generate(const AST &ast, OutputStyle style)
+{
+ QTextStream stream(m_outputDevice);
+
+ QScopedPointer<GeneratorImplBase> generator;
+ if (style == OutputStyle::DataStream)
+ generator.reset(new PythonGenerator(stream));
+ else
+ generator.reset(new PysideGenerator(stream));
+
+ generator->generatePrefix(ast);
+
+ for (const ASTEnum &en : ast.enums)
+ generator->generateEnum(en);
+
+ for (const POD &pod : ast.pods)
+ generator->generatePod(pod);
+
+ for (const ASTClass &astClass : ast.classes) {
+ generator->generateClass(astClass, GeneratorBase::Mode::Source);
+ generator->generateClass(astClass, GeneratorBase::Mode::SimpleSource);
+ generator->generateClass(astClass, GeneratorBase::Mode::Replica);
+ }
+
+ generator->generateSuffix(ast);
+}
+
+QT_END_NAMESPACE
diff --git a/tools/repc/pythoncodegenerator.h b/tools/repc/pythoncodegenerator.h
new file mode 100644
index 0000000..0138b93
--- /dev/null
+++ b/tools/repc/pythoncodegenerator.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 Ford Motor Company
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtRemoteObjects module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtCore/qglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+struct AST;
+class QIODevice;
+
+class PythonCodeGenerator
+{
+public:
+ // Start with minimal QDataStream processing
+ // Protect for CBOR and PyQt
+ enum class OutputStyle
+ {
+ DataStream,
+ PySide
+ };
+
+ explicit PythonCodeGenerator(QIODevice *outputDevice);
+
+ void generate(const AST &ast, OutputStyle style);
+
+private:
+ QIODevice *m_outputDevice;
+};
+
+QT_END_NAMESPACE
diff --git a/tools/repc/repc.pro b/tools/repc/repc.pro
index d3bcc43..24d2c68 100644
--- a/tools/repc/repc.pro
+++ b/tools/repc/repc.pro
@@ -14,11 +14,13 @@ SOURCES += \
main.cpp \
repcodegenerator.cpp \
cppcodegenerator.cpp \
+ pythoncodegenerator.cpp \
utils.cpp
HEADERS += \
repcodegenerator.h \
cppcodegenerator.h \
+ pythoncodegenerator.h \
utils.h
QMAKE_TARGET_DESCRIPTION = "Qt Remote Objects Compiler"
diff --git a/tools/repc/repcodegenerator.cpp b/tools/repc/repcodegenerator.cpp
index ac58130..5fbc4f1 100644
--- a/tools/repc/repcodegenerator.cpp
+++ b/tools/repc/repcodegenerator.cpp
@@ -27,13 +27,12 @@
****************************************************************************/
#include "repcodegenerator.h"
-
#include "repparser.h"
+#include "utils.h"
#include <QFileInfo>
#include <QMetaType>
#include <QTextStream>
-#include <QCryptographicHash>
#include <QRegularExpression>
using namespace Qt;
@@ -117,66 +116,6 @@ RepCodeGenerator::RepCodeGenerator(QIODevice *outputDevice)
Q_ASSERT(m_outputDevice);
}
-static QByteArray enumSignature(const ASTEnum &e)
-{
- QByteArray ret;
- ret += e.name.toLatin1();
- for (const ASTEnumParam &param : e.params)
- ret += param.name.toLatin1() + QByteArray::number(param.value);
- return ret;
-}
-
-static QByteArray typeData(const QString &type, const QHash<QString, QByteArray> &specialTypes)
-{
- QHash<QString, QByteArray>::const_iterator it = specialTypes.find(type);
- if (it != specialTypes.end())
- return it.value();
- const auto pos = type.lastIndexOf(QLatin1String("::"));
- if (pos > 0)
- return typeData(type.mid(pos + 2), specialTypes);
- return type.toLatin1();
-}
-
-static QByteArray functionsData(const QList<ASTFunction> &functions, const QHash<QString, QByteArray> &specialTypes)
-{
- QByteArray ret;
- for (const ASTFunction &func : functions) {
- ret += func.name.toLatin1();
- for (const ASTDeclaration &param : func.params) {
- ret += param.name.toLatin1();
- ret += typeData(param.type, specialTypes);
- ret += QByteArray(reinterpret_cast<const char *>(&param.variableType), sizeof(param.variableType));
- }
- ret += typeData(func.returnType, specialTypes);
- }
- return ret;
-}
-
-QByteArray RepCodeGenerator::classSignature(const ASTClass &ac)
-{
- QCryptographicHash checksum(QCryptographicHash::Sha1);
- QHash<QString, QByteArray> localTypes = m_globalEnumsPODs;
- for (const ASTEnum &e : ac.enums) // add local enums
- localTypes[e.name] = enumSignature(e);
-
- checksum.addData(ac.name.toLatin1());
-
- // Checksum properties
- for (const ASTProperty &p : ac.properties) {
- checksum.addData(p.name.toLatin1());
- checksum.addData(typeData(p.type, localTypes));
- checksum.addData(reinterpret_cast<const char *>(&p.modifier), sizeof(p.modifier));
- }
-
- // Checksum signals
- checksum.addData(functionsData(ac.signalsList, localTypes));
-
- // Checksum slots
- checksum.addData(functionsData(ac.slotsList, localTypes));
-
- return checksum.result().toHex();
-}
-
void RepCodeGenerator::generate(const AST &ast, Mode mode, QString fileName)
{
QTextStream stream(m_outputDevice);
@@ -229,15 +168,15 @@ void RepCodeGenerator::generate(const AST &ast, Mode mode, QString fileName)
const QString replicaMetaTypeRegistrationCode = classMetaTypeRegistrationCode
+ generateMetaTypeRegistrationForPending(pendingMetaTypes);
- if (mode == MERGED) {
- generateClass(REPLICA, stream, astClass, replicaMetaTypeRegistrationCode);
- generateClass(SOURCE, stream, astClass, classMetaTypeRegistrationCode);
- generateClass(SIMPLE_SOURCE, stream, astClass, classMetaTypeRegistrationCode);
+ if (mode == Mode::Merged) {
+ generateClass(Mode::Replica, stream, astClass, replicaMetaTypeRegistrationCode);
+ generateClass(Mode::Source, stream, astClass, classMetaTypeRegistrationCode);
+ generateClass(Mode::SimpleSource, stream, astClass, classMetaTypeRegistrationCode);
generateSourceAPI(stream, astClass);
} else {
- generateClass(mode, stream, astClass, mode == REPLICA ? replicaMetaTypeRegistrationCode : classMetaTypeRegistrationCode);
- if (mode == SOURCE) {
- generateClass(SIMPLE_SOURCE, stream, astClass, classMetaTypeRegistrationCode);
+ generateClass(mode, stream, astClass, mode == Mode::Replica ? replicaMetaTypeRegistrationCode : classMetaTypeRegistrationCode);
+ if (mode == Mode::Source) {
+ generateClass(Mode::SimpleSource, stream, astClass, classMetaTypeRegistrationCode);
generateSourceAPI(stream, astClass);
}
}
@@ -273,13 +212,13 @@ void RepCodeGenerator::generateHeader(Mode mode, QTextStream &out, const AST &as
out << "\n"
"#include <QtRemoteObjects/qremoteobjectnode.h>\n";
- if (mode == MERGED) {
+ if (mode == Mode::Merged) {
out << "#include <QtRemoteObjects/qremoteobjectpendingcall.h>\n";
out << "#include <QtRemoteObjects/qremoteobjectreplica.h>\n";
out << "#include <QtRemoteObjects/qremoteobjectsource.h>\n";
if (hasModel)
out << "#include <QtRemoteObjects/qremoteobjectabstractitemmodelreplica.h>\n";
- } else if (mode == REPLICA) {
+ } else if (mode == Mode::Replica) {
out << "#include <QtRemoteObjects/qremoteobjectpendingcall.h>\n";
out << "#include <QtRemoteObjects/qremoteobjectreplica.h>\n";
if (hasModel)
@@ -382,13 +321,13 @@ QString RepCodeGenerator::typeForMode(const ASTProperty &property, RepCodeGenera
return property.type;
if (property.type.startsWith(QStringLiteral("QAbstractItemModel")))
- return mode == REPLICA ? property.type + QStringLiteral("Replica*") : property.type + QStringLiteral("*");
+ return mode == Mode::Replica ? property.type + QStringLiteral("Replica*") : property.type + QStringLiteral("*");
switch (mode) {
- case REPLICA: return property.type + QStringLiteral("Replica*");
- case SIMPLE_SOURCE:
+ case Mode::Replica: return property.type + QStringLiteral("Replica*");
+ case Mode::SimpleSource:
Q_FALLTHROUGH();
- case SOURCE: return property.type + QStringLiteral("Source*");
+ case Mode::Source: return property.type + QStringLiteral("Source*");
default: qCritical("Invalid mode");
}
@@ -401,7 +340,7 @@ void RepCodeGenerator::generateSimpleSetter(QTextStream &out, const ASTProperty
out << " virtual ";
else
out << " ";
- out << "void set" << cap(property.name) << "(" << typeForMode(property, SIMPLE_SOURCE) << " " << property.name << ")";
+ out << "void set" << cap(property.name) << "(" << typeForMode(property, Mode::SimpleSource) << " " << property.name << ")";
if (generateOverride)
out << " override";
out << Qt::endl;
@@ -415,13 +354,11 @@ void RepCodeGenerator::generateSimpleSetter(QTextStream &out, const ASTProperty
void RepCodeGenerator::generatePOD(QTextStream &out, const POD &pod)
{
- QByteArray podData = pod.name.toLatin1();
+ m_globalEnumsPODs[pod.name] = podSignature(pod);
QStringList equalityCheck;
- for (const PODAttribute &attr : pod.attributes) {
+ for (const PODAttribute &attr : pod.attributes)
equalityCheck << QStringLiteral("left.%1() == right.%1()").arg(attr.name);
- podData += attr.name.toLatin1() + typeData(attr.type, m_globalEnumsPODs);
- }
- m_globalEnumsPODs[pod.name] = podData;
+
out << "class " << pod.name << "\n"
"{\n"
" Q_GADGET\n"
@@ -606,17 +543,17 @@ void RepCodeGenerator::generateStreamOperatorsForEnums(QTextStream &out, const Q
void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass &astClass, const QString &metaTypeRegistrationCode)
{
- const QString className = (astClass.name + (mode == REPLICA ? QStringLiteral("Replica") : mode == SOURCE ? QStringLiteral("Source") : QStringLiteral("SimpleSource")));
- if (mode == REPLICA)
+ const QString className = (astClass.name + (mode == Mode::Replica ? QStringLiteral("Replica") : mode == Mode::Source ? QStringLiteral("Source") : QStringLiteral("SimpleSource")));
+ if (mode == Mode::Replica)
out << "class " << className << " : public QRemoteObjectReplica" << Qt::endl;
- else if (mode == SIMPLE_SOURCE)
+ else if (mode == Mode::SimpleSource)
out << "class " << className << " : public " << astClass.name << "Source" << Qt::endl;
else
out << "class " << className << " : public QObject" << Qt::endl;
out << "{" << Qt::endl;
out << " Q_OBJECT" << Qt::endl;
- if (mode != SIMPLE_SOURCE) {
+ if (mode != Mode::SimpleSource) {
out << " Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_TYPE, \"" << astClass.name << "\")" << Qt::endl;
out << " Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_SIGNATURE, \"" << QLatin1String(classSignature(astClass)) << "\")" << Qt::endl;
for (int i = 0; i < astClass.modelMetadata.count(); i++) {
@@ -635,7 +572,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
for (const ASTProperty &property : astClass.properties) {
out << " Q_PROPERTY(" << typeForMode(property, mode) << " " << property.name << " READ " << property.name;
if (property.modifier == ASTProperty::Constant) {
- if (mode == REPLICA) // We still need to notify when we get the initial value
+ if (mode == Mode::Replica) // We still need to notify when we get the initial value
out << " NOTIFY " << property.name << "Changed";
else
out << " CONSTANT";
@@ -644,7 +581,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
else if (property.modifier == ASTProperty::ReadWrite)
out << " WRITE set" << cap(property.name) << " NOTIFY " << property.name << "Changed";
else if (property.modifier == ASTProperty::ReadPush || property.modifier == ASTProperty::SourceOnlySetter) {
- if (mode == REPLICA) // The setter slot isn't known to the PROP
+ if (mode == Mode::Replica) // The setter slot isn't known to the PROP
out << " NOTIFY " << property.name << "Changed";
else // The Source can use the setter, since non-asynchronous
out << " WRITE set" << cap(property.name) << " NOTIFY " << property.name << "Changed";
@@ -662,7 +599,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
out << "" << Qt::endl;
out << "public:" << Qt::endl;
- if (mode == REPLICA) {
+ if (mode == Mode::Replica) {
out << " " << className << "() : QRemoteObjectReplica() { initialize(); }" << Qt::endl;
out << " static void registerMetatypes()" << Qt::endl;
out << " {" << Qt::endl;
@@ -744,7 +681,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
}
out << " setProperties(properties);" << Qt::endl;
out << " }" << Qt::endl;
- } else if (mode == SOURCE) {
+ } else if (mode == Mode::Source) {
out << " explicit " << className << "(QObject *parent = nullptr) : QObject(parent)" << Qt::endl;
out << " {" << Qt::endl;
if (!metaTypeRegistrationCode.isEmpty())
@@ -763,7 +700,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
QStringList parameters;
for (int index : constIndices) {
const ASTProperty &property = astClass.properties.at(index);
- parameters.append(QString::fromLatin1("%1 %2 = %3").arg(typeForMode(property, SOURCE), property.name, property.defaultValue));
+ parameters.append(QString::fromLatin1("%1 %2 = %3").arg(typeForMode(property, Mode::Source), property.name, property.defaultValue));
}
parameters.append(QStringLiteral("QObject *parent = nullptr"));
out << " explicit " << className << "(" << parameters.join(QStringLiteral(", ")) << ") : " << astClass.name << "Source(parent)" << Qt::endl;
@@ -781,7 +718,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
out << "" << Qt::endl;
out << "public:" << Qt::endl;
- if (mode == REPLICA && astClass.hasPersisted) {
+ if (mode == Mode::Replica && astClass.hasPersisted) {
out << " ~" << className << "() override {" << Qt::endl;
out << " QVariantList persisted;" << Qt::endl;
for (int i = 0; i < astClass.properties.size(); i++) {
@@ -796,11 +733,11 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
}
out << "" << Qt::endl;
- if (mode != SIMPLE_SOURCE)
+ if (mode != Mode::SimpleSource)
generateConversionFunctionsForEnums(out, astClass.enums);
//Next output getter/setter
- if (mode == REPLICA) {
+ if (mode == Mode::Replica) {
int i = 0;
for (const ASTProperty &property : astClass.properties) {
auto type = typeForMode(property, mode);
@@ -832,7 +769,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
}
out << "" << Qt::endl;
}
- } else if (mode == SOURCE) {
+ } else if (mode == Mode::Source) {
for (const ASTProperty &property : astClass.properties)
out << " virtual " << typeForMode(property, mode) << " " << property.name << "() const = 0;" << Qt::endl;
for (const ASTProperty &property : astClass.properties) {
@@ -854,7 +791,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
}
}
- if (mode != SIMPLE_SOURCE) {
+ if (mode != Mode::SimpleSource) {
//Next output property signals
if (!astClass.properties.isEmpty() || !astClass.signalsList.isEmpty()) {
out << "" << Qt::endl;
@@ -872,7 +809,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
// update (once) when the value is initialized. Put these last, so they don't mess
// up the signal index order
for (const ASTProperty &property : astClass.properties) {
- if (mode == REPLICA && property.modifier == ASTProperty::Constant)
+ if (mode == Mode::Replica && property.modifier == ASTProperty::Constant)
out << " void " << property.name << "Changed(" << fullyQualifiedTypeName(astClass, className, typeForMode(property, mode)) << " " << property.name << ");" << Qt::endl;
}
}
@@ -889,7 +826,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
for (const ASTProperty &property : astClass.properties) {
if (property.modifier == ASTProperty::ReadPush) {
const auto type = fullyQualifiedTypeName(astClass, className, property.type);
- if (mode != REPLICA) {
+ if (mode != Mode::Replica) {
out << " virtual void push" << cap(property.name) << "(" << type << " " << property.name << ")" << Qt::endl;
out << " {" << Qt::endl;
out << " set" << cap(property.name) << "(" << property.name << ");" << Qt::endl;
@@ -908,7 +845,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
const QList<ASTFunction> slotsList = transformEnumParams(astClass, astClass.slotsList, className);
for (const ASTFunction &slot : slotsList) {
const auto returnType = fullyQualifiedTypeName(astClass, className, slot.returnType);
- if (mode != REPLICA) {
+ if (mode != Mode::Replica) {
out << " virtual " << returnType << " " << slot.name << "(" << slot.paramsAsString() << ") = 0;" << Qt::endl;
} else {
// TODO: Discuss whether it is a good idea to special-case for void here,
@@ -956,18 +893,18 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass
out << "private:" << Qt::endl;
//Next output data members
- if (mode == SIMPLE_SOURCE) {
+ if (mode == Mode::SimpleSource) {
for (const ASTProperty &property : astClass.properties)
- out << " " << typeForMode(property, SOURCE) << " " << "m_" << property.name << ";" << Qt::endl;
+ out << " " << typeForMode(property, Mode::Source) << " " << "m_" << property.name << ";" << Qt::endl;
}
- if (mode != SIMPLE_SOURCE)
+ if (mode != Mode::SimpleSource)
out << " friend class QT_PREPEND_NAMESPACE(QRemoteObjectNode);" << Qt::endl;
out << "};" << Qt::endl;
out << "" << Qt::endl;
- if (mode != SIMPLE_SOURCE)
+ if (mode != Mode::SimpleSource)
generateStreamOperatorsForEnums(out, astClass.enums, className);
out << "" << Qt::endl;
@@ -1007,7 +944,7 @@ void RepCodeGenerator::generateSourceAPI(QTextStream &out, const ASTClass &astCl
QList<qsizetype> propertyChangeIndex;
for (qsizetype i = 0; i < propCount; ++i) {
const ASTProperty &prop = astClass.properties.at(i);
- const QString propTypeName = fullyQualifiedTypeName(astClass, QStringLiteral("typename ObjectType"), typeForMode(prop, SOURCE));
+ const QString propTypeName = fullyQualifiedTypeName(astClass, QStringLiteral("typename ObjectType"), typeForMode(prop, Mode::Source));
out << QString::fromLatin1(" m_properties[%1] = QtPrivate::qtro_property_index<ObjectType>(&ObjectType::%2, "
"static_cast<%3 (QObject::*)()>(nullptr),\"%2\");")
.arg(QString::number(i+1), prop.name, propTypeName) << Qt::endl;
@@ -1028,7 +965,7 @@ void RepCodeGenerator::generateSourceAPI(QTextStream &out, const ASTClass &astCl
out << QString::fromLatin1(" m_signals[%1] = QtPrivate::qtro_signal_index<ObjectType>(&ObjectType::%2Changed, "
"static_cast<void (QObject::*)(%3)>(nullptr),m_signalArgCount+%4,&m_signalArgTypes[%4]);")
.arg(QString::number(i+1), onChangeProperties.at(i).name,
- fullyQualifiedTypeName(astClass, QStringLiteral("typename ObjectType"), typeForMode(onChangeProperties.at(i), SOURCE)),
+ fullyQualifiedTypeName(astClass, QStringLiteral("typename ObjectType"), typeForMode(onChangeProperties.at(i), Mode::Source)),
QString::number(i)) << Qt::endl;
QList<ASTFunction> signalsList = transformEnumParams(astClass, astClass.signalsList, QStringLiteral("typename ObjectType"));
@@ -1188,7 +1125,7 @@ void RepCodeGenerator::generateSourceAPI(QTextStream &out, const ASTClass &astCl
.arg(QString::number(i), prop.name, prop.type) << Qt::endl;
else
out << QString::fromLatin1(" case %1: return QByteArrayLiteral(\"%2Changed(%3)\");")
- .arg(QString::number(i), prop.name, typeForMode(prop, SOURCE)) << Qt::endl;
+ .arg(QString::number(i), prop.name, typeForMode(prop, Mode::Source)) << Qt::endl;
}
for (int i = 0; i < signalCount; ++i)
{
diff --git a/tools/repc/repcodegenerator.h b/tools/repc/repcodegenerator.h
index 7a727e4..d345405 100644
--- a/tools/repc/repcodegenerator.h
+++ b/tools/repc/repcodegenerator.h
@@ -33,6 +33,8 @@
#include <QSet>
#include <QString>
+#include "utils.h"
+
QT_BEGIN_NAMESPACE
struct AST;
struct ASTClass;
@@ -43,22 +45,13 @@ struct ASTProperty;
class QIODevice;
class QTextStream;
-class RepCodeGenerator
+class RepCodeGenerator : public GeneratorBase
{
public:
- enum Mode
- {
- REPLICA,
- SOURCE,
- SIMPLE_SOURCE,
- MERGED
- };
-
explicit RepCodeGenerator(QIODevice *outputDevice);
void generate(const AST &ast, Mode mode, QString fileName);
- QByteArray classSignature(const ASTClass &ac);
private:
void generateHeader(Mode mode, QTextStream &out, const AST &ast);
QString generateMetaTypeRegistration(const QSet<QString> &metaTypes);
@@ -85,7 +78,6 @@ private:
private:
QIODevice *m_outputDevice;
- QHash<QString, QByteArray> m_globalEnumsPODs;
};
QT_END_NAMESPACE
diff --git a/tools/repc/utils.cpp b/tools/repc/utils.cpp
index b6260a3..946f394 100644
--- a/tools/repc/utils.cpp
+++ b/tools/repc/utils.cpp
@@ -26,12 +26,13 @@
**
****************************************************************************/
-#include <qjsonvalue.h>
-#include <qjsonarray.h>
-#include <qjsonobject.h>
+#include <QCryptographicHash>
+#include <QJsonArray>
+#include <QJsonObject>
+#include <QJsonValue>
-#include "utils.h"
#include "repparser.h"
+#include "utils.h"
#define _(X) QLatin1String(X)
@@ -286,4 +287,136 @@ AST classList2AST(const QJsonArray &classes)
return ret;
}
+static QByteArray typeData(const QString &type, const QHash<QString, QByteArray> &specialTypes)
+{
+ QHash<QString, QByteArray>::const_iterator it = specialTypes.find(type);
+ if (it != specialTypes.end())
+ return it.value();
+ const auto pos = type.lastIndexOf(QLatin1String("::"));
+ if (pos > 0)
+ return typeData(type.mid(pos + 2), specialTypes);
+ return type.toLatin1();
+}
+
+static QByteArray functionsData(const QList<ASTFunction> &functions, const QHash<QString, QByteArray> &specialTypes)
+{
+ QByteArray ret;
+ for (const ASTFunction &func : functions) {
+ ret += func.name.toLatin1();
+ for (const ASTDeclaration &param : func.params) {
+ ret += param.name.toLatin1();
+ ret += typeData(param.type, specialTypes);
+ ret += QByteArray(reinterpret_cast<const char *>(&param.variableType), sizeof(param.variableType));
+ }
+ ret += typeData(func.returnType, specialTypes);
+ }
+ return ret;
+}
+
+GeneratorBase::GeneratorBase()
+{
+}
+
+GeneratorBase::~GeneratorBase()
+{
+
+}
+
+QByteArray GeneratorBase::enumSignature(const ASTEnum &e)
+{
+ QByteArray ret;
+ ret += e.name.toLatin1();
+ for (const ASTEnumParam &param : e.params)
+ ret += param.name.toLatin1() + QByteArray::number(param.value);
+ return ret;
+}
+
+QByteArray GeneratorBase::classSignature(const ASTClass &ac)
+{
+ QCryptographicHash checksum(QCryptographicHash::Sha1);
+ auto specialTypes = m_globalEnumsPODs;
+ for (const ASTEnum &e : ac.enums) // add local enums
+ specialTypes[e.name] = enumSignature(e);
+
+ checksum.addData(ac.name.toLatin1());
+
+ // Checksum properties
+ for (const ASTProperty &p : ac.properties) {
+ checksum.addData(p.name.toLatin1());
+ checksum.addData(typeData(p.type, specialTypes));
+ checksum.addData(reinterpret_cast<const char *>(&p.modifier), sizeof(p.modifier));
+ }
+
+ // Checksum signals
+ checksum.addData(functionsData(ac.signalsList, specialTypes));
+
+ // Checksum slots
+ checksum.addData(functionsData(ac.slotsList, specialTypes));
+
+ return checksum.result().toHex();
+}
+
+QByteArray GeneratorBase::podSignature(const POD &pod)
+{
+ QByteArray podData = pod.name.toLatin1();
+ for (const PODAttribute &attr : pod.attributes)
+ podData += attr.name.toLatin1() + typeData(attr.type, m_globalEnumsPODs);
+
+ return podData;
+}
+
+GeneratorImplBase::GeneratorImplBase(QTextStream &_stream) : stream(_stream)
+{
+}
+
+bool GeneratorImplBase::hasNotify(const ASTProperty &property, Mode mode)
+{
+ switch (property.modifier) {
+ case ASTProperty::Constant:
+ if (mode == Mode::Replica) // We still need to notify when we get the initial value
+ return true;
+ else
+ return false;
+ case ASTProperty::ReadOnly:
+ case ASTProperty::ReadWrite:
+ case ASTProperty::ReadPush:
+ case ASTProperty::SourceOnlySetter:
+ return true;
+ }
+ Q_UNREACHABLE();
+}
+
+bool GeneratorImplBase::hasPush(const ASTProperty &property, Mode mode)
+{
+ Q_UNUSED(mode)
+ switch (property.modifier) {
+ case ASTProperty::ReadPush:
+ return true;
+ case ASTProperty::Constant:
+ case ASTProperty::ReadOnly:
+ case ASTProperty::ReadWrite:
+ case ASTProperty::SourceOnlySetter:
+ return false;
+ }
+ Q_UNREACHABLE();
+}
+
+bool GeneratorImplBase::hasSetter(const ASTProperty &property, Mode mode)
+{
+ switch (property.modifier) {
+ case ASTProperty::Constant:
+ case ASTProperty::ReadOnly:
+ return false;
+ case ASTProperty::ReadWrite:
+ return true;
+ case ASTProperty::ReadPush:
+ case ASTProperty::SourceOnlySetter:
+ if (mode == Mode::Replica) // The setter slot isn't known to the PROP
+ return false;
+ else // The Source can use the setter, since non-asynchronous
+ return true;
+ }
+ Q_UNREACHABLE();
+}
+
QT_END_NAMESPACE
diff --git a/tools/repc/utils.h b/tools/repc/utils.h
index 236f60e..bdbfa9d 100644
--- a/tools/repc/utils.h
+++ b/tools/repc/utils.h
@@ -30,14 +30,66 @@
#define UTILS_H
#include <QByteArray>
+#include <QHash>
+#include <QString>
+#include <QTextStream>
QT_BEGIN_NAMESPACE
+
class QJsonValue;
class QJsonArray;
struct AST;
+struct ASTClass;
+struct ASTEnum;
+struct ASTProperty;
+struct POD;
QByteArray generateClass(const QJsonValue &cls, bool alwaysGenerateClass = false);
AST classList2AST(const QJsonArray &classes);
+
+// The Qt/C++ generator is not easy to refactor at this point, so GeneratorBase
+// has the shared components used by all generators
+class GeneratorBase
+{
+public:
+
+ enum class Mode
+ {
+ Replica,
+ Source,
+ SimpleSource,
+ Merged
+ };
+
+ GeneratorBase();
+ virtual ~GeneratorBase();
+
+protected:
+ QByteArray classSignature(const ASTClass &ac);
+ QByteArray enumSignature(const ASTEnum &e);
+ QByteArray podSignature(const POD &pod);
+ QHash<QString, QByteArray> m_globalEnumsPODs;
+};
+
+// GeneratorImplBase has shared components used by new generators
+class GeneratorImplBase : public GeneratorBase
+{
+public:
+ GeneratorImplBase(QTextStream &_stream);
+
+ virtual bool generateClass(const ASTClass &astClass, Mode mode) = 0;
+ virtual void generateEnum(const ASTEnum &astEnum) = 0;
+ virtual void generatePod(const POD &pod) = 0;
+ virtual void generatePrefix(const AST &) {}
+ virtual void generateSuffix(const AST &) {}
+ bool hasNotify(const ASTProperty &property, Mode mode);
+ bool hasPush(const ASTProperty &property, Mode mode);
+ bool hasSetter(const ASTProperty &property, Mode mode);
+
+protected:
+ QTextStream &stream;
+};
+
QT_END_NAMESPACE
#endif // UTILS_H