From: Patrick Franz Date: Sun, 22 Jan 2023 10:37:39 +0000 (+0000) Subject: Import qt6-remoteobjects_6.4.2.orig.tar.xz X-Git-Tag: archive/raspbian/6.4.2-1+rpi1^2~2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=20a36ab1c45693a22dd1d049b8acdb8e5c9fe5b0;p=qt6-remoteobjects.git Import qt6-remoteobjects_6.4.2.orig.tar.xz [dgit import orig qt6-remoteobjects_6.4.2.orig.tar.xz] --- 20a36ab1c45693a22dd1d049b8acdb8e5c9fe5b0 diff --git a/.QT-ENTERPRISE-LICENSE-AGREEMENT b/.QT-ENTERPRISE-LICENSE-AGREEMENT new file mode 100644 index 0000000..8d68b15 --- /dev/null +++ b/.QT-ENTERPRISE-LICENSE-AGREEMENT @@ -0,0 +1,1977 @@ +QT LICENSE AGREEMENT +Agreement version 4.4.1 + +This Qt License Agreement ("Agreement") is a legal agreement for the licensing +of Licensed Software (as defined below) between The Qt Company (as defined +below) and the Licensee who has accepted the terms of this Agreement by signing +this Agreement or by downloading or using the Licensed Software or in any other +appropriate means. + +Capitalized terms used herein are defined in Section 1. + +WHEREAS: + (A) Licensee wishes to use the Licensed Software for the purpose of + developing and distributing Applications and/or Devices (each as defined + below); + (B) The Qt Company is willing to grant the Licensee a right to use Licensed + Software for such a purpose pursuant to term and conditions of this + Agreement; and + (C) Parties wish to enable that their respective Affiliates also can sell + and purchase licenses to serve Licensee Affiliates' needs to use Licensed + Software pursuant to terms of the Agreement. Any such license purchases by + Licensee Affiliates from The Qt Company or its Affiliates will create + contractual relationship directly between the relevant The Qt Company and + the respective ordering Licensee Affiliate "Acceding Agreement"). + Accordingly, Licensee shall not be a party to any such Acceding Agreement, + and no rights or obligations are created to the Licensee thereunder but all + rights and obligations under such Acceding Agreement are vested and borne + solely by the ordering Licensee Affiliate and the relevant The Qt Company + as a contracting parties under such Acceding Agreement. + +NOW, THEREFORE, THE PARTIES HEREBY AGREE AS FOLLOWS: + +1. DEFINITIONS + +"Affiliate" of a Party shall mean an entity + (i) which is directly or indirectly controlling such Party; + (ii) which is under the same direct or indirect ownership or control as + such Party; or + (iii) which is directly or indirectly owned or controlled by such Party. +For these purposes, an entity shall be treated as being controlled by another +if that other entity has fifty percent (50 %) or more of the votes in such +entity, is able to direct its affairs and/or to control the composition of its +board of directors or equivalent body. + +"Add-on Products" shall mean The Qt Company's specific add-on software products +which are not licensed as part of The Qt Company's standard product offering, +but shall be included into the scope of Licensed Software only if so +specifically agreed between the Parties. + +"Agreement Term" shall mean the validity period of this Agreement, as set forth +in Section 12. + +"Applications" shall mean software products created using the Licensed +Software, which include the Redistributables, or part thereof. + +"Contractor(s)" shall mean third party consultants, distributors and +contractors performing services to the Licensee under applicable contractual +arrangement. + +"Customer(s)" shall mean Licensee's customers to whom Licensee, directly or +indirectly, distributes copies of the Redistributables as integrated or +incorporated into Applications or Devices. + +"Data Protection Legislation" shall mean the General Data Protection Regulation +(EU 2016/679) (GDPR) and any national implementing laws, regulations and +secondary legislation, as may be amended or updated from time to time, as well +as any other data protection laws or regulations applicable in relevant +territory. + +"Deployment Platforms" shall mean target operating systems and/or hardware +specified in the License Certificate, on which the Redistributables can be +distributed pursuant to the terms and conditions of this Agreement. + +"Designated User(s)" shall mean the employee(s) of Licensee or Licensee's +Affiliates acting within the scope of their employment or Licensee's +Contractors acting within the scope of their services on behalf of Licensee. + +"Development License" shall mean the license needed by the Licensee for each +Designated User to use the Licensed Software under the license grant described +in Section 3.1 of this Agreement. Development Licenses are available per +respective Licensed Software products, each product having its designated scope +and purpose of use. + +"Development License Term" shall mean the agreed validity period of the +Development License or QA Tools license during which time the relevant Licensed +Software product can be used pursuant to this Agreement. Agreed Development +License Term, as ordered and paid for by the Licensee, shall be memorialized in +the applicable License Certificate. + +"Development Platforms" shall mean those host operating systems specified in +the License Certificate, in which the Licensed Software can be used under the +Development License. + +"Devices" shall mean + (1) hardware devices or products that + i. are manufactured and/or distributed by the Licensee, its Affiliates, + Contractors or Customers, and + ii. incorporate, integrate or link to Applications such that + substantial functionality of such unit, when used by an End User, + is provided by Application(s) or otherwise depends on the Licensed + Software, regardless of whether the Application is developed by + Licensee or its Contractors; or + (2) Applications designed for the hardware devices specified in item (1). + + Devices covered by this Agreement shall be specified in Appendix 2 or in a + quote. + +"Distribution License(s)" shall mean a royalty-bearing license required for any +kind of sale, trade, exchange, loan, lease, rental or other distribution by or +on behalf of Licensee to a third party of Redistributables in connection with +Devices pursuant to license grant described in Section 3.3 of this Agreement. +Distribution Licensed are sold separately for each type of Device respectively +and cannot be used for any type of Devices at Licensee's discretion. + +"Distribution License Packs" shall mean set of prepaid Distribution Licenses +for distribution of Redistributables, as defined in The Qt Company's standard +price list, quote, Purchase Order confirmation or in an Appendix 2 hereto, as +the case may be. + +"End User" shall mean the final end user of the Application or a Device. + +"Evaluation License Term" shall mean a time period specified in the License +Certificate for the Licensee to use the relevant Licensed Software for +evaluation purposes according to Section 3.6 herein. + +"Intellectual Property Rights" shall mean patents (including utility models), +design patents, and designs (whether or not capable of registration), chip +topography rights and other like protection, copyrights, trademarks, service +marks, trade names, logos or other words or symbols and any other form of +statutory protection of any kind and applications for any of the foregoing as +well as any trade secrets. + +"License Certificate" shall mean a certificate generated by The Qt Company for +each Designated User respectively upon them downloading the Licensed Software, +which will be available under respective Designated User's Qt Account at +account.qt.io. License Certificates will specify relevant information +pertaining the Licensed Software purchased by Licensee and Designated User's +license to the Licensed Software. + +"License Fee" shall mean the fee charged to the Licensee for rights granted +under the terms of this Agreement. + +"Licensed Software" shall mean specified product of commercially licensed +version of Qt Software and/or QA Tools defined in Appendix 1 and/or Appendix 3, +which Licensee has purchased and which is provided to Licensee under the terms +of this Agreement. Licensed Software shall include corresponding online or +electronic documentation, associated media and printed materials, including the +source code (where applicable), example programs and the documentation. +Licensed Software does not include Third Party Software (as defined in Section +4) or Open Source Qt. The Qt Company may, in the course of its development +activities, at its free and absolute discretion and without any obligation to +send or publish any notifications to the Licensee or in general, make changes, +additions or deletions in the components and functionalities of the Licensed +Software, provided that no such changes, additions or deletions will affect +the already released version of the Licensed Software, but only upcoming +version(s). + +"Licensee" shall mean the individual or legal entity that is party to this +Agreement. + +"Licensee's Records" shall mean books and records that contain information +bearing on Licensee's compliance with this Agreement, Licensee's use of Open +Source Qt and/or the payments due to The Qt Company under this Agreement, +including, but not limited to user information, assembly logs, sales records +and distribution records. + +"Modified Software" shall have the meaning as set forth in Section 2.3. + +"Online Services" shall mean any services or access to systems made available +by The Qt Company to the Licensee over the Internet relating to the Licensed +Software or for the purpose of use by the Licensee of the Licensed Software or +Support. Use of any such Online Services is discretionary for the Licensee and +some of them may be subject to additional fees. + +"Open Source Qt" shall mean Qt Software available under the terms of the GNU +Lesser General Public License, version 2.1 or later ("LGPL") or the GNU General +Public License, version 2.0 or later ("GPL"). For clarity, Open Source Qt shall +not be provided, governed or used under this Agreement. + +"Party" or "Parties" shall mean Licensee and/or The Qt Company. + +"Permitted Software" shall mean (i) third party open source software products +that are generally available for public in source code form and free of any +charge under any of the licenses approved by Open Source Initiative as listed +on https://opensource.org/licenses, which may include parts of Open Source Qt +or be developed using Open Source Qt; and (ii) software The Qt Company has made +available via its Qt Marketplace online distribution channel. + +"Pre-Release Code" shall have the meaning as set forth in Section 4. + +"Prohibited Combination" shall mean any effort to use, combine, incorporate, +link or integrate Licensed Software with any software created with or +incorporating Open Source Qt, or use Licensed Software for creation of any such +software. + +"Purchase Order" shall have the meaning as set forth in Section 10.2. + +"QA Tools" shall mean software libraries and tools as defined in Appendix 1 +depending on which product(s) the Licensee has purchased under the Agreement. + +"Qt Software" shall mean the software libraries and tools of The Qt Company, +which The Qt Company makes available under commercial and/or open source +licenses. + +"Redistributables" shall mean the portions of the Licensed Software set forth +in Appendix 1 that may be distributed pursuant to the terms of this Agreement +in object code form only, including any relevant documentation. Where relevant, +any reference to Licensed Software in this Agreement shall include and refer +also to Redistributables. + +"Renewal Term" shall mean an extension of previous Development License Term as +agreed between the Parties. + +"Submitted Modified Software" shall have the meaning as set forth in Section +2.3. + +"Support" shall mean standard developer support that is provided by The Qt +Company to assist Designated Users in using the Licensed Software in accordance +with this Agreement and the Support Terms. + +"Support Terms" shall mean The Qt Company's standard support terms specified in +Appendix 9 hereto. + +"Taxes" shall have the meaning set forth in Section 10.5. + +"The Qt Company" shall mean: + (i) in the event Licensee is an individual residing in the United States or + a legal entity incorporated in the United States or having its + headquarters in the United States, The Qt Company Inc., a Delaware + corporation with its office at 3031 Tisch Way, 110 Plaza West, + San Jose, CA 95128, USA.; or + (ii) in the event the Licensee is an individual residing outside of the + United States or a legal entity incorporated outside of the United + States or having its registered office outside of the United States, + The Qt Company Ltd., a Finnish company with its registered office at + Miestentie 7, 02150 Espoo, Finland. + +"Third-Party Software" shall have the meaning set forth in Section 4. + +"Updates" shall mean a release or version of the Licensed Software containing +bug fixes, error corrections and other changes that are generally made +available to users of the Licensed Software that have contracted for Support. +Updates are generally depicted as a change to the digits following the decimal +in the Licensed Software version number. The Qt Company shall make Updates +available to the Licensee under the Support. Updates shall be considered as +part of the Licensed Software hereunder. + +"Upgrades" shall mean a release or version of the Licensed Software containing +enhancements and new features and are generally depicted as a change to the +first digit of the Licensed Software version number. In the event Upgrades are +provided to the Licensee under this Agreement, they shall be considered as part +of the Licensed Software hereunder. + +2. OWNERSHIP + +2.1. Ownership of The Qt Company + +The Licensed Software is protected by copyright laws and international +copyright treaties, as well as other intellectual property laws and treaties. +The Licensed Software is licensed, not sold. + +All of The Qt Company's Intellectual Property Rights are and shall remain the +exclusive property of The Qt Company or its licensors respectively. No rights +to The Qt Company's Intellectual Property Rights are assigned or granted to +Licensee under this Agreement, except when and to the extent expressly +specified herein. + +2.2. Ownership of Licensee + +All the Licensee's Intellectual Property Rights are and shall remain the +exclusive property of the Licensee or its licensors respectively. + +All Intellectual Property Rights to the Modified Software, Applications and +Devices shall remain with the Licensee and no rights thereto shall be granted +by the Licensee to The Qt Company under this Agreement (except as set forth in +Section 2.3 below). + +2.3. Modified Software + +Licensee may create bug-fixes, error corrections, patches or modifications to +the Licensed Software ("Modified Software"). Such Modified Software may break +the source or binary compatibility with the Licensed Software (including +without limitation through changing the application programming interfaces +("API") or by adding, changing or deleting any variable, method, or class +signature in the Licensed Software and/or any inter-process protocols, +services or standards in the Licensed Software libraries). To the extent that +Licensee's Modified Software so breaks source or binary compatibility with the +Licensed Software, Licensee acknowledges that The Qt Company's ability to +provide Support may be prevented or limited and Licensee's ability to make use +of Updates may be restricted. + +Licensee may, at its sole and absolute discretion, choose to submit Modified +Software to The Qt Company ("Submitted Modified Software") in connection with +Licensee's Support request, service request or otherwise. In the event +Licensee does so, then, Licensee hereby grants The Qt Company a sublicensable, +assignable, irrevocable, perpetual, worldwide, non-exclusive, royalty-free and +fully paid-up license, under all of Licensee's Intellectual Property Rights, to +reproduce, adapt, translate, modify, and prepare derivative works of, publicly +display, publicly perform, sublicense, make available and distribute such +Submitted Modified Software as The Qt Company sees fit at its free and absolute +discretion. + +3. LICENSES GRANTED + +3.1. Development with Licensed Software + +Subject to the terms of this Agreement, The Qt Company grants to Licensee a +worldwide, non-exclusive, non-transferable license, valid for each Development +License Term, to use, modify and copy the Licensed Software by Designated +Users on the Development Platforms for the sole purposes of designing, +developing, demonstrating and testing Application(s) and/or Devices, and to +provide thereto related support and other related services to Customers. Each +Application and/or Device can only include, incorporate or integrate +contributions by such Designated Users who are duly licensed for the applicable +Development Platform(s) and Deployment Platform(s) (i.e have a valid license +for the appropriate Licensed Software product). + +Licensee may install copies of the Licensed Software on five (5) computers per +Designated User, provided that only the Designated Users who have a valid +Development License may use the Licensed Software. + +Licensee may at any time designate another Designated User to replace a +then-current Designated User by notifying The Qt Company in writing, where such +replacement is due to termination of employment, change of job duties, long +time absence or other such permanent reason affecting Designated User's need +for Licensed Software. + +Upon expiry of the initially agreed Development License Term, the respective +Development License Term shall be automatically extended to one or more Renewal +Term(s), unless and until either Party notifies the other Party in writing, or +any other method acceptable to The Qt Company (it being specifically +acknowledged and understood that verbal notification is explicitly deemed +inadequate in all circumstances), that it does not wish to continue the +Development License Term, such notification to be provided to the other Party +no less than thirty (30) days before expiry of the respective Development +License Term. The Qt Company shall, in good time before the due date for the +above notification, remind the Licensee on the coming Renewal Term. Unless +otherwise agreed between the Parties, Renewal Term shall be 12 months. + +Any such Renewal Term shall be subject to License Fees agreed between the +Parties or, if no advance agreement exists, subject to The Qt Company's +standard list pricing applicable at the commencement date of any such +Renewal Term. + +The Qt Company may either request the Licensee to place a purchase order +corresponding to a quote by The Qt Company, or use Licensee's stored Credit +Card information in the Qt Account to automatically charge the Licensee for the +relevant Renewal Term. + +3.2. Distribution of Applications + +Subject to the terms of this Agreement, The Qt Company grants to Licensee a +worldwide, non-exclusive, non-transferable, revocable (for cause pursuant to +this Agreement), right and license, valid for the Agreement Term, to + (i) distribute, by itself or through its Contractors, Redistributables as + installed, incorporated or integrated into Applications for execution + on the Deployment Platforms, and + (ii) grant perpetual and irrevocable sublicenses to Redistributables, as + distributed hereunder, for Customers solely to the extent necessary in + order for the Customers to use the Applications for their respective + intended purposes. + +Right to distribute the Redistributables as part of an Application as provided +herein is not royalty-bearing but is conditional upon the Application having +been created, updated and maintained under a valid and duly paid Development +Licenses. + +3.3. Distribution of Devices + +Subject to the terms of this Agreement, The Qt Company grants to Licensee a +worldwide, non-exclusive, non-transferable, revocable (for cause pursuant to +this Agreement), right and license, valid for the Agreement Term, to + (i) distribute, by itself or through one or more tiers of Contractors, + Redistributables as installed, incorporated or integrated, or intended + to be installed, incorporated or integrated into Devices for execution + on the Deployment Platforms, and + (ii) grant perpetual and irrevocable sublicenses to Redistributables, as + distributed hereunder, for Customers solely to the extent necessary in + order for the Customers to use the Devices for their respective + intended purposes. + +Right to distribute the Devices as provided herein is conditional upon + (i) the Devices having been created, updated and maintained under a valid + and duly paid Development Licenses, and + (ii) the Licensee having acquired corresponding Distribution Licenses at + the time of distribution of any Devices to Customers. + +3.4. Further Requirements + +The licenses granted above in this Section 3 by The Qt Company to Licensee are +conditional and subject to Licensee's compliance with the following terms: + (i) Licensee acknowledges that The Qt Company has separate products of + Licensed Software for the purpose of Applications and Devices + respectively, where development and distribution of Devices is only + allowed using the correct designated product. Licensee shall make sure + and bear the burden of proof that Licensee is using a correct product + of Licensed Software entitling Licensee to development and distribution + of Devices; + (ii) Licensee shall not remove or alter any copyright, trademark or other + proprietary rights notice(s) contained in any portion of the Licensed + Software; + (iii) Applications must add primary and substantial functionality to the + Licensed Software so as not to compete with the Licensed Software; + (iv) Applications may not pass on functionality which in any way makes it + possible for others to create software with the Licensed Software; + provided however that Licensee may use the Licensed Software's + scripting and QML ("Qt Quick") functionality solely in order to enable + scripting, themes and styles that augment the functionality and + appearance of the Application(s) without adding primary and substantial + functionality to the Application(s); + (v) Licensee shall not use Licensed Software in any manner or for any + purpose that infringes, misappropriates or otherwise violates any + Intellectual property or right of any third party, or that violates any + applicable law; + (vi) Licensee shall not use The Qt Company's or any of its suppliers' + names, logos, or trademarks to market Applications, except that + Licensee may use "Built with Qt" logo to indicate that Application(s) + or Device(s) was developed using the Licensed Software; + (vii) Licensee shall not distribute, sublicense or disclose source code of + Licensed Software to any third party (provided however that Licensee + may appoint employee(s) of Contractors and Affiliates as Designated + Users to use Licensed Software pursuant to this Agreement). Such right + may be available for the Licensee subject to a separate software + development kit ("SDK") license agreement to be concluded with The Qt + Company; + (viii) Licensee shall not grant the Customers a right to (a) make copies of + the Redistributables except when and to the extent required to use the + Applications and/or Devices for their intended purpose, (b) modify the + Redistributables or create derivative works thereof, (c) decompile, + disassemble or otherwise reverse engineer Redistributables, or (d) + redistribute any copy or portion of the Redistributables to any third + party, except as part of the onward sale of the Application or Device + on which the Redistributables are installed; + (ix) Licensee shall not and shall cause that its Affiliates or Contractors + shall not use Licensed Software in any Prohibited Combination, unless + Licensee has received an advance written permission from The Qt Company + to do so. Absent such written permission, any and all distribution by + the Licensee during the Agreement Term of a hardware device or product + a) which incorporate or integrate any part of Licensed Software or Open + Source Qt; or b) where substantial functionality is provided by + software built with Licensed Software or Open Source Qt or otherwise + depends on the Licensed Software or Open Source Qt, shall be considered + to be Device distribution under this Agreement and shall be dependent + on Licensee's compliance thereof (including but not limited to + obligation to pay applicable License Fees for such distribution). + Notwithstanding what is provided above in this sub-section (ix), + Licensee is entitled to use and combine Licensed Software with any + Permitted Software; + (x) Licensee shall cause all of its Affiliates, Contractors and Customers + entitled to make use of the licenses granted under this Agreement, to + be contractually bound to comply with the relevant terms of this + Agreement and not to use the Licensed Software beyond the terms hereof + and for any purposes other than operating within the scope of their + services for Licensee. Licensee shall be responsible for any and all + actions and omissions of its Affiliates and Contractors relating to the + Licensed Software and use thereof (including but not limited to payment + of all applicable License Fees); + (xi) Except when and to the extent explicitly provided in this Section 3, + Licensee shall not transfer, publish, disclose, display or otherwise + make available the Licensed Software; and + (xii) Licensee shall not attempt or enlist a third party to conduct or + attempt to conduct any of the above. + +Above terms shall not be applicable if and to the extent they conflict with any +mandatory provisions of any applicable laws. + +Any use of Licensed Software beyond the provisions of this Agreement is +strictly prohibited and requires an additional license from The Qt Company. + +3.5 QA Tools License + +Subject to the terms of this Agreement, The Qt Company grants to Licensee a +worldwide, non-exclusive, non-transferable license, valid for the Development +License Term, to use the QA Tools for Licensee's internal business purposes in +the manner provided below and in Appendix 1 hereto. + +Licensee may modify the QA Tools except for altering or removing any details of +ownership, copyright, trademark or other property right connected with the QA +Tools. + +Licensee shall not distribute the QA Tools or any part thereof, modified or +unmodified, separately or as part of any software package, Application or +Device. + +Upon expiry of the initially agreed Development License Term, the respective +Development License Term shall be automatically extended to one or more Renewal +Term(s), unless and until either Party notifies the other Party in writing, or +any other method acceptable to The Qt Company (it being specifically +acknowledged and understood that verbal notification is explicitly deemed +inadequate in all circumstances), that it does not wish to continue the +Development License Term, such notification to be provided to the other Party +no less than thirty (30) days before expiry of the respective Development +License Term. The Qt Company shall, in good time before the due date for the +above notification, remind the Licensee on the coming Renewal Term. Unless +otherwise agreed between the Parties, Renewal Term shall be 12 months. + +Any such Renewal Term shall be subject to License Fees agreed between the +Parties or, if no advance agreement exists, subject to The Qt Company's +standard list pricing applicable at the commencement date of any such +Renewal Term. + +3.6 Evaluation License + +Subject to the terms of this Agreement, The Qt Company grants to Licensee a +worldwide, non-exclusive, non-transferable license, valid for the Evaluation +License Term to use the Licensed Software solely for the Licensee's internal +use to evaluate and determine whether the Licensed Software meets Licensee's +business requirements, specifically excluding any commercial use of the +Licensed Software or any derived work thereof. + +Upon the expiry of the Evaluation License Term, Licensee must either +discontinue use of the relevant Licensed Software or acquire a commercial +Development License or QA Tools License specified herein. + +4. THIRD-PARTY SOFTWARE + +The Licensed Software may provide links or access to third party libraries or +code (collectively "Third-Party Software") to implement various functions. +Third-Party Software does not, however, comprise part of the Licensed Software, +but is provided to Licensee complimentary and use thereof is discretionary for +the Licensee. Third-Party Software will be listed in the ".../src/3rdparty" +source tree delivered with the Licensed Software or documented in the Licensed +Software, as such may be amended from time to time. Licensee acknowledges that +use or distribution of Third-Party Software is in all respects subject to +applicable license terms of applicable third-party right holders. + +5. PRE-RELEASE CODE + +The Licensed Software may contain pre-release code and functionality, or sample +code marked or otherwise stated with appropriate designation such as +"Technology Preview", "Alpha", "Beta", "Sample", "Example" etc. +("Pre-Release Code"). + +Such Pre-Release Code may be present complimentary for the Licensee, in order +to provide experimental support or information for new platforms or +preliminary versions of one or more new functionalities or for other similar +reasons. The Pre-Release Code may not be at the level of performance and +compatibility of a final, generally available, product offering. The +Pre-Release Code may not operate correctly, may contain errors and may be +substantially modified by The Qt Company prior to the first commercial +product release, if any. The Qt Company is under no obligation to make +Pre-Release Code commercially available, or provide any Support or Updates +relating thereto. The Qt Company assumes no liability whatsoever regarding +any Pre-Release Code, but any use thereof is exclusively at Licensee's own risk +and expense. + +For clarity, unless Licensed Software specifies different license terms for the +respective Pre-Release Code, the Licensee is entitled to use such pre-release +code pursuant to Section 3, just like other Licensed Software. + +6. LIMITED WARRANTY AND WARRANTY DISCLAIMER + +The Qt Company hereby represents and warrants that (i) it has the power and +authority to grant the rights and licenses granted to Licensee under this +Agreement, and (ii) Licensed Software will operate materially in accordance +with its specifications. + +Except as set forth above, the Licensed Software is licensed to Licensee "as +is" and Licensee's exclusive remedy and The Qt Company's entire liability for +errors in the Licensed Software shall be limited, at The Qt Company's option, +to correction of the error, replacement of the Licensed Software or return of +the applicable fees paid for the defective Licensed Software for the time +period during which the License is not able to utilize the Licensed Software +under the terms of this Agreement. + +TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE QT COMPANY ON BEHALF OF +ITSELF AND ITS LICENSORS, SUPPLIERS AND AFFILIATES, DISCLAIMS ALL OTHER +WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND +NON-INFRINGEMENT WITH REGARD TO THE LICENSED SOFTWARE. THE QT COMPANY DOES NOT +WARRANT THAT THE LICENSED SOFTWARE WILL SATISFY LICENSEE'S REQUIREMENTS OR THAT +IT WILL OPERATE WITHOUT DEFECT OR ERROR OR THAT THE OPERATION THEREOF WILL BE +UNINTERRUPTED. + +7. LIMITATION OF LIABILITY + +EXCEPT FOR (I) CASES OF GROSS NEGLIGENCE OR INTENTIONAL MISCONDUCT, AND (II) +BREACH OF CONFIDENTIALITY, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, IN NO +EVENT SHALL EITHER PARTY BE LIABLE TO THE OTHER PARTY FOR ANY LOSS OF PROFIT, +LOSS OF DATA, LOSS OF BUSINESS OR GOODWILL OR ANY OTHER INDIRECT, SPECIAL, +CONSEQUENTIAL, INCIDENTAL OR PUNITIVE COST, DAMAGES OR EXPENSE OF ANY KIND, +HOWSOEVER ARISING UNDER OR IN CONNECTION WITH THIS AGREEMENT. + +EXCEPT FOR (I) CASES OF GROSS NEGLIGENCE OR INTENTIONAL MISCONDUCT, AND (II) +BREACH OF CONFIDENTIALITY, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, IN NO +EVENT SHALL EITHER PARTY'S TOTAL AGGREGATE LIABILITY UNDER THIS AGREEMENT +EXCEED THE AGGREGATE LICENSE FEES PAID OR PAYABLE TO THE QT COMPANY BY LICENSEE +DURING THE DEVELOPMENT LICENSE TERM DURING WHICH THE EVENT RESULTING IN SUCH +LIABILITY OCCURRED. + +THE PROVISIONS OF THIS SECTION 7 ALLOCATE THE RISKS UNDER THIS AGREEMENT +BETWEEN THE QT COMPANY AND LICENSEE AND THE PARTIES HAVE RELIED UPON THE +LIMITATIONS SET FORTH HEREIN IN DETERMINING WHETHER TO ENTER INTO THIS +AGREEMENT. + +NOTWITHSTANDING ANYTHING TO THE CONTRARY IN THIS AGREEMENT, LICENSEE SHALL +ALWAYS BE LIABLE TO PAY THE APPLICABLE LICENSE FEES CORRESPONDING TO ITS +ACTUAL USE OF LICENSED SOFTWARE. + +8. SUPPORT, UPDATES AND ONLINE SERVICES + +Upon due payment of the agreed License Fees the Licensee will be eligible to +receive Support and Updates and to use the Online Services during the agreed +Development License Term or other agreed fixed time period. Support is +provided according to agreed support level and subject to applicable +requirements and restrictions, as specified in the Support Terms. + +Unless otherwise decided by The Qt Company at its free and absolute discretion, +Upgrades will not be included in the Support but may be available subject to +additional fees. + +From time to time The Qt Company may change the Support Terms, provided that +during the respective ongoing Support period the level of Support may not be +reduced without the consent of the Licensee. + +Unless otherwise agreed, The Qt Company shall not be responsible for providing +any service or support to Customers. + +9. CONFIDENTIALITY + +Each Party acknowledges that during the Agreement Term each Party may receive +information about the other Party's business, business methods, business plans, +customers, business relations, technology, and other information, including the +terms of this Agreement, that is confidential and of great value to the other +Party, and the value of which would be significantly reduced if disclosed to +third parties ("Confidential Information"). Accordingly, when a Party (the +"Receiving Party") receives Confidential Information from the other Party (the +"Disclosing Party"), the Receiving Party shall only disclose such information +to employees and Contractors on a need to know basis, and shall cause its +employees and employees of its Affiliates to: (i) maintain any and all +Confidential Information in confidence; (ii) not disclose the Confidential +Information to a third party without the Disclosing Party's prior written +approval; and (iii) not, directly or indirectly, use the Confidential +Information for any purpose other than for exercising its rights and +fulfilling its responsibilities pursuant to this Agreement. Each Party shall +take reasonable measures to protect the Confidential Information of the other +Party, which measures shall not be less than the measures taken by such Party +to protect its own confidential and proprietary information. + +Obligation of confidentiality shall not apply to information that (i) is or +becomes generally known to the public through no act or omission of the +Receiving Party; (ii) was in the Receiving Party's lawful possession prior to +the disclosure hereunder and was not subject to limitations on disclosure or +use; (iii) is developed independently by employees or Contractors of the +Receiving Party or other persons working for the Receiving Party who have not +had access to the Confidential Information of the Disclosing Party, as proven +by the written records of the Receiving Party; (iv) is lawfully disclosed to +the Receiving Party without restrictions, by a third party not under an +obligation of confidentiality; or (v) the Receiving Party is legally compelled +to disclose, in which case the Receiving Party shall notify the Disclosing +Party of such compelled disclosure and assert the privileged and confidential +nature of the information and cooperate fully with the Disclosing Party to +limit the scope of disclosure and the dissemination of disclosed Confidential +Information to the minimum extent necessary. + +The obligations under this Section 9 shall continue to remain in force for a +period of five (5) years after the last disclosure, and, with respect to trade +secrets, for so long as such trade secrets are protected under applicable trade +secret laws. + +10. FEES, DELIVERY AND PAYMENT + +10.1. License Fees + +License Fees are described in The Qt Company's standard price list, quote or +Purchase Order confirmation or in an Appendix 2 hereto, as the case may be. + +Unless otherwise expressly provided in this Agreement, the License Fees shall +not be refunded or claimed as a credit in any event or for any reason +whatsoever. + +10.2. Ordering Licenses + +Licensee may purchase Development Licenses, Distribution Licenses and QA Tools +Licenses pursuant to agreed pricing terms or, if no specific pricing terms have +been agreed upon, at The Qt Company's standard pricing terms applicable at the +time of purchase. + +Unless expressly otherwise agreed, any price or other term quoted to the +Licensee or specified herein shall only be valid for the thirty (30) days from +the effective date of this Agreement, Appendix 2 or the date of the quote, as +applicable. + +Licensee shall submit all purchase orders for Development Licenses and +Distribution Licenses to The Qt Company by email or any other method acceptable +to The Qt Company (each such order is referred to herein as a "Purchase Order") +for confirmation, whereupon the Purchase Order shall become binding between the +Parties. + +Licensee acknowledges and agrees that all Purchase Orders for Licensed Software +the Licensee makes during the Agreement Term shall be governed exclusively +under the terms of this Agreement. + +10.3. Distribution License Packs + +Unless otherwise agreed, Distribution Licenses shall be purchased by way of +Distribution License Packs. + +Upon due payment of the ordered Distribution License Pack(s), the Licensee will +have an account of Distribution Licenses available for distributing the +Redistributables in accordance with this Agreement. + +Each time Licensee distributes a copy of Redistributables, then one +Distribution License is used, and Licensee's account of available Distribution +Licenses is decreased accordingly. + +Licensee may distribute copies of the Redistributables so long as Licensee has +Distribution Licenses remaining on its account. + +10.4. Payment Terms + +License Fees and any other charges under this Agreement shall be paid by +Licensee no later than thirty (30) days from the date of the applicable invoice +from The Qt Company. + +The Qt Company will submit an invoice to Licensee after the date of this +Agreement and/or after The Qt Company receives a Purchase Order from Licensee. + +A late payment charge of the lower of (a) one percent per month; or (b) the +interest rate stipulated by applicable law, shall be charged on any unpaid +balances that remain past due and which have not been disputed by the Licensee +in good faith. + +10.5. Taxes + +All License Fees and other charges payable hereunder are gross amounts but +exclusive of any value added tax, use tax, sales tax, withholding tax and other +taxes, duties or tariffs ("Taxes") levied directly for the sale, delivery or +use of Licensed Software hereunder pursuant to any applicable law. Such +applicable Taxes shall be paid by Licensee to The Qt Company, or, where +applicable, in lieu of payment of such Taxes to The Qt Company, Licensee shall +provide an exemption certificate to The Qt Company and any applicable +authority. + +11. RECORD-KEEPING AND REPORTING OBLIGATIONS; AUDIT RIGHTS + +11.1. Licensee's Record-keeping + +Licensee shall at all times during the Agreement Term and for a period of two +(2) years thereafter maintain Licensee's Records in an accurate and up-to-date +form. Licensee's Records shall be adequate to reasonably enable The Qt Company +to determine Licensee's compliance with the provisions of this Agreement. The +records shall conform to general good accounting practices. + +Licensee shall, within thirty (30) days from receiving The Qt Company's request +to that effect, deliver to The Qt Company a report based on Licensee's Records, +such report to contain information, in sufficient detail, on (i) number and +identity of users working with Licensed Software or Open Source Qt, (ii) copies +of Redistributables distributed by Licensee during the most recent calendar +quarter and/or any other term specified by The Qt Company, , and (iii) any +other information pertaining to Licensee's compliance with the terms of this +Agreement (like e.g. information on products and/or projects relating to use of +Distribution Licenses), as The Qt Company may reasonably require from time to +time. + +11.2. The Qt Company's Audit Rights + +The Qt Company or an independent auditor acting on behalf of The Qt Company's, +may, upon at least thirty (30) days' prior written notice and at its expense, +audit Licensee with respect to the Licensee's use of the Licensed Software, but +not more frequently than once during each 6-month period. Such audit may be +conducted by mail, electronic means or through an in-person visit to Licensee's +place of business. Any possible in-person audit shall be conducted during +regular business hours at Licensee's facilities and shall not unreasonably +interfere with Licensee's business activities and shall be limited in scope to +verify Licensee's compliance with the terms of this Agreement. The Qt Company +or the independent auditor acting on behalf of The Qt Company shall be entitled +to inspect Licensee's Records and conduct necessary interviews of Licensee's +relevant employees and Contractors. All such Licensee's Records and use thereof +shall be subject to an obligation of confidentiality under this Agreement. + +If an audit reveals that Licensee is using the Licensed Software beyond scope +of the licenses Licensee has paid for, Licensee shall pay to The Qt Company any +amounts owed for such unauthorized use within 30 days from receipt of the +corresponding invoice from The Qt Company. + +In addition, in the event the audit reveals a material violation of the terms +of this Agreement (without limitation, either (i) underpayment of more than 10 +% of License Fees or 10,000 euros (whichever is more) or (ii) distribution of +products, which include or result from Prohibited Combination, shall be deemed +a material violation for purposes of this section), then the Licensee shall +pay The Qt Company's reasonable cost of conducting such audit. + +12. TERM AND TERMINATION + +12.1. Agreement Term + +This Agreement shall enter into force upon due acceptance by both Parties and +remain in force until terminated pursuant to the terms of this Section 12 +("Agreement Term"). + +12.2. Termination for breach and suspension of rights +Either Party shall have the right to terminate this Agreement upon thirty (30) +days prior written notice if the other Party commits a material breach of any +obligation of this Agreement and fails to remedy such breach within such notice +period. + +Instead of termination, The Qt Company shall have the right to suspend or +withhold grants of all rights to the Licensed Software hereunder, including but +not limited to the Development Licenses, Distribution License, and Support, +should Licensee fail to make payment in timely fashion or otherwise violates or +is reasonably suspected to violate its obligations or terms of this Agreement, +and where such violation or breach is not cured within ten (10) business days +following The Qt Company's written notice thereof. + +12.3. Termination for insolvency + +Either Party shall have the right to terminate this Agreement immediately upon +written notice in the event that the other Party becomes insolvent, files for +any form of bankruptcy, makes any assignment for the benefit of creditors, has +a receiver, administrative receiver or officer appointed over the whole or a +substantial part of its assets, ceases to conduct business, or an act +equivalent to any of the above occurs under the laws of the jurisdiction of the +other Party. + +12.4. Parties' Rights and Duties upon Termination + +Upon expiry or termination of the Agreement, Licensee shall cease and shall +cause all Designated Users (including those of its Affiliates' and +Contractors') to cease using the Licensed Software under this Agreement. For +clarity, a Development License of a Designated User or a QA Tools License, and +all rights relating thereto, shall always terminate at the expiry of the +respective Development License Term, even if the Agreement continues to remain +in force. + +Upon such termination the Licensee shall destroy or return to The Qt Company +all copies of the Licensed Software and all related materials and will certify +the same by Licensee's duly authorized officer to The Qt Company upon its +request, provided however that Licensee may retain and exploit such copies of +the Licensed Software as it may reasonably require in providing continued +support to Customers. + +Except when this Agreement is terminated by The Qt Company due to Licensee's +material breach as set forth in Section 12.2, the Licensee may continue +distribution of Applications and Devices under the terms of this Agreement +despite the termination of this Agreement. In such event the terms hereof will +continue to be applicable and govern any such distribution of Applications and +Devices beyond the expiry or termination of this Agreement. In case of +termination by The Qt Company due to Licensee's material breach, Licensee must +cease any distribution of Applications and Devices at the date of termination +of this Agreement. + +Expiry or termination of this Agreement for any reason whatsoever shall not +relieve Licensee of its obligation to pay any License Fees accrued or payable +to The Qt Company prior to the effective date of termination, and Licensee pay +to The Qt Company all such fees within 30 days from the effective date of +termination of this Agreement. + +Termination of this Agreement shall not affect any rights of Customers to +continue use of Applications and Devices (and therein incorporated +Redistributables). + +12.5. Extension of Rights under Special Circumstances + +In the event of The Qt Company choosing not to renew the Development License(s) +or QA Tools Licenses, as set forth in Section 3.1 and 3.5 respectively, and +where such decision of non-renewal is not due to any ongoing breach or alleged +breach (as reasonably determined by The Qt Company) by Licensee of the terms of +this Agreement or any applicable license terms of Open Source Qt, then all +valid and affected Development Licenses and QA Tools licenses possessed by the +Licensee at such date shall be extended to be valid in perpetuity under the +terms of this Agreement and Licensee is entitled to purchase additional +licenses as set forth in Section 10.2. + +In the event The Qt Company is declared bankrupt under a final, non-cancellable +decision by relevant court of law, and this Agreement is not, at the date of +expiry of the Development License(s) or QA Tools Licenses, assigned to party, +who has assumed The Qt Company's position as a legitimate licensor of Licensed +Software under this Agreement, then all valid Development Licenses and QA Tools +Licenses possessed by the Licensee at such date of expiry, and which the +Licensee has not notified for expiry, shall be extended to be valid in +perpetuity under the terms of this Agreement. + +For clarity, in case of an extension under this Section 12.5, any such +extension shall not apply to The Qt Company's Support obligations, but Support +shall be provided only up until the end of the respective fixed Development +License Term regardless of the extension of relevant Development License or QA +Tools License, unless otherwise agreed between the Parties. + +13. GOVERNING LAW AND LEGAL VENUE + +In the event this Agreement is in the name of The Qt Company Inc., a Delaware +Corporation, then: + (i) this Agreement shall be construed and interpreted in accordance with + the laws of the State of California, USA, excluding its choice of law + provisions; + (ii) the United Nations Convention on Contracts for the International Sale + of Goods will not apply to this Agreement; and + (iii) any dispute, claim or controversy arising out of or relating to this + Agreement or the breach, termination, enforcement, interpretation or + validity thereof, including the determination of the scope or + applicability of this Agreement to arbitrate, shall be determined by + arbitration in San Francisco, USA, before one arbitrator. The + arbitration shall be administered by JAMS pursuant to JAMS' Streamlined + Arbitration Rules and Procedures. Judgment on the Award may be entered + in any court having jurisdiction. This Section shall not preclude + parties from seeking provisional remedies in aid of arbitration from a + court of appropriate jurisdiction. + +In the event this Agreement is in the name of The Qt Company Ltd., a Finnish +Company, then: + (i) this Agreement shall be construed and interpreted in accordance with + the laws of Finland, excluding its choice of law provisions; + (ii) the United Nations Convention on Contracts for the International Sale + of Goods will not apply to this Agreement; and + (iii) any disputes, controversy or claim arising out of or relating to this + Agreement, or the breach, termination or validity thereof shall be + finally settled by arbitration in accordance with the Arbitration Rules + of International Chamber of Commerce. The arbitration tribunal shall + consist of one (1), or if either Party so requires, of three (3), + arbitrators. The award shall be final and binding and enforceable in + any court of competent jurisdiction. The arbitration shall be held in + Helsinki, Finland and the process shall be conducted in the English + language. This Section shall not preclude parties from seeking + provisional remedies in aid of arbitration from a court of appropriate + jurisdiction. + +14. GENERAL PROVISIONS + +14.1. No Assignment + +Except in the case of a merger or sale of substantially all of its corporate +assets, Licensee shall not be entitled to assign or transfer all or any of its +rights, benefits and obligations under this Agreement without the prior written +consent of The Qt Company, which shall not be unreasonably withheld or delayed. +The Qt Company shall be entitled to freely assign or transfer any of its +rights, benefits or obligations under this Agreement. + +14.2. No Third-Party Representations + +Licensee shall make no representations or warranties concerning the Licensed +Software on behalf of The Qt Company. Any representation or warranty Licensee +makes or purports to make on The Qt Company's behalf shall be void as to +The Qt Company. + +14.3. Surviving Sections + +Any terms and conditions that by their nature or otherwise reasonably should +survive termination of this Agreement shall so be deemed to survive. Such +sections include especially the following: 1, 2, 6, 7, 9, 11, 12.4, 13 and 14. + +14.4. Entire Agreement + +This Agreement, the Appendices hereto, the License Certificate and any +applicable quote and Purchase Order accepted by The Qt Company constitute the +complete agreement between the Parties and supersedes all prior or +contemporaneous discussions, representations, and proposals, written or oral, +with respect to the subject matters discussed herein. + +In the event of any conflict or inconsistency between this Agreement and any +Purchase Order, the terms of this Agreement will prevail over the terms of the +Purchase Order with respect to such conflict or inconsistency. + +Parties specifically acknowledge and agree that this Agreement prevails over +any click-to-accept or similar agreements the Designated Users may need to +accept online upon download of the Licensed Software, as may be required by +The Qt Company's applicable processes relating to Licensed Software. + +14.5. Modifications + +No modification of this Agreement shall be effective unless contained in a +writing executed by an authorized representative of each Party. No term or +condition contained in Licensee's Purchase Order ("Deviating Terms") shall +apply unless The Qt Company has expressly agreed such Deviating Terms in +writing. Unless and to the extent expressly agreed by The Qt Company, any such +Deviating Terms shall be deemed void and with no legal effect. For clarity, +delivery of the Licensed Software following the receipt of the Purchase Order +including Deviating Terms shall not constitute acceptance of such Deviating +Terms. + +14.6. Force Majeure + +Except for the payment obligations hereunder, neither Party shall be liable to +the other for any delay or non-performance of its obligations hereunder in the +event and to the extent that such delay or non-performance is due to an event +of act of God, terrorist attack or other similar unforeseeable catastrophic +event that prevents either Party for fulfilling its obligations under this +Agreement and which such Party cannot avoid or circumvent ("Force Majeure +Event"). If the Force Majeure Event results in a delay or non-performance of a +Party for a period of three (3) months or longer, then either Party shall have +the right to terminate this Agreement with immediate effect without any +liability (except for the obligations of payment arising prior to the event of +Force Majeure) towards the other Party. + +14.7. Notices + +Any notice given by one Party to the other shall be deemed properly given and +deemed received if specifically acknowledged by the receiving Party in writing +or when successfully delivered to the recipient by hand, fax, or special +courier during normal business hours on a business day to the addresses +specified for each Party on the signature page. Each communication and document +made or delivered by one Party to the other Party pursuant to this Agreement +shall be in the English language. + +14.8. Export Control + +Licensee acknowledges that the Redistributables, as incorporated in +Applications or Devices, may be subject to export control restrictions under +the applicable laws of respective countries. Licensee shall fully comply with +all applicable export license restrictions and requirements as well as with all +laws and regulations relating to the Redistributables and exercise of licenses +hereunder and shall procure all necessary governmental authorizations, +including without limitation, all necessary licenses, approvals, permissions or +consents, where necessary for the re-exportation of the Redistributables, +Applications and/or Devices. + +14.9. No Implied License + +There are no implied licenses or other implied rights granted under this +Agreement, and all rights, save for those expressly granted hereunder, shall +remain with The Qt Company and its licensors. In addition, no licenses or +immunities are granted to the combination of the Licensed Software with any +other software or hardware not delivered by The Qt Company under this +Agreement. + +14.10. Attorney Fees + +The prevailing Party in any action to enforce this Agreement shall be entitled +to recover its attorney's fees and costs in connection with such action, as to +be ordered by the relevant dispute resolution body. + +14.11. Privacy + +Licensee acknowledges and agrees that for the purpose of this Agreement, +The Qt Company may collect, use, transfer and disclose personal data pertaining +to Designated Users as well as any other employees and directors of the +Licensee and its Contractors relevant for carrying out the intent of this +Agreement. Such personal data will be primarily collected from the relevant +individuals but may be collected also from Licensee (e.g. in the course of +Licensee's reporting obligations). The Parties acknowledge that as +The Qt Company determines the purpose and means for such collection and +processing of the applicable personal data, The Qt Company shall be regarded as +the Data Controller under the applicable Data Protection Legislation. +The Qt Company shall process any such personal data in accordance with its +privacy and security policies and practices, which will comply with all +applicable requirements of the Data Protection Legislation. + +14.12. Severability + +If any provision of this Agreement shall be adjudged by any court of competent +jurisdiction to be unenforceable or invalid, that provision shall be limited or +eliminated to the minimum extent necessary so that this Agreement shall +otherwise remain in full force and effect and enforceable. + +14.13. Marketing Rights + +Parties have agreed upon Marketing Rights pursuant to Appendix 7, if any. + + + + +APPENDICES +The Agreement includes following Appendices 1-10, as applicable. +- Appendix 1: Licensed Software details +- Appendix 2: Pricing +- Appendix 3: Add-on Software details (optional) +- Appendix 4: Small business and startup Licenses (optional) +- Appendix 5: Non-commercial and educational Licenses (optional) +- Appendix 6: License Reporting (optional) +- Appendix 7: Marketing Rights (optional) +- Appendix 8: Intentionally left blank (optional) +- Appendix 9: Support Terms +- Appendix 10: Conversion from legacy Licenses to Subscription (optional) + + +APPENDIX 1: LICENSED SOFTWARE + +The modules and/or tools that are included in the latest publicly available +version of the respective product at the effective date of this Agreement- Qt +for Application Development Professional (ADP), Qt for Application Development +Enterprise (ADE), Qt for Device Creation Professional (DCP), Qt for Device +Creation Enterprise (DCE), - are marked with "X" in the below table. The +modules and tools are specific to each product version respectively and may +vary from version to version. Modules and tools included in the latest publicly +available version of the respective product at any given time are listed in +Appendix 1 of the latest version of this Agreement available at +www.qt.io/terms-conditions/. If a new version of Licensed Software does not +include a module or tool present in an older version which Licensee is entitled +to use under a valid license from The Qt Company, then Licensee will continue +to have such right during the Term of this Agreement. In the event a new +version of the Licensed Software adds modules or tools to any previous +version(s), Licensee's rights will extend to cover also such additional modules +and tools. + +Parts of the product that are permitted for distribution in object-code form +only ("Redistributables") are marked with "R" in the below table. + ++----------------------------------------------------------+ +| Modules / Tools | ADP | ADE | DCP | DCE | ++----------------------------------------------------------+ +| Active Qt | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt 3D | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt 5 Core Compatibility APIs | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Android Extras | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Bluetooth | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Canvas 3D | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Charts | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Concurrent | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Core | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Data Visualization | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt D-Bus | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt for Python | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt for WebAssembly | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Gamepad | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Graphical Effects | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt GUI | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Help | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Image Formats | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Location | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Lottie Animation | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Mac Extras | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Multimedia | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Multimedia Widgets | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Network | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Network Authorization | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt NFC | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt OpenGL | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt PDF | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Platform Headers | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Positioning | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Print Support | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Purchasing | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt QML | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick 3D | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick Controls 1 | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick Controls | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick Dialogs | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick Extras | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick Layouts | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick Test | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick Timeline | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick WebGL | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick Widgets | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Remote Objects | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Script | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Script Tools | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt SCXML | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Sensors | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Serial Bus | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Serial Port | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Shader Tools | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Speech | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt State Machine | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt SQL | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt SVG | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Test | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt UI Tools | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Virtual Keyboard | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Wayland Compositor | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt WebChannel | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt WebEngine | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt WebSockets | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt WebView | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Widgets | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Windows Extras | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt X11 Extras | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt XML | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt XML Patterns | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Designer (Qt Widget Designer) | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Linguist | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt Assistant | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| lupdate | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| lrelease | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| lconvert | X,R | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt MQTT | | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt KNX | | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt OPC UA | | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Qt CoAP | | X,R | X,R | X,R | ++----------------------------------------------------------+ +| Boot 2 Qt stacks | | | X,R | X,R | ++----------------------------------------------------------+ +| Qt OTA | | | X,R | X,R | ++----------------------------------------------------------+ +| Device Utilities | | | X,R | X,R | ++----------------------------------------------------------+ +| Qt Debugging Bridge (QBD) Daemon | | | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick Ultralite Controls | | | X,R | X,R | ++----------------------------------------------------------+ +| Qt Quick Ultralite | | | X,R | X,R | ++----------------------------------------------------------+ +| Qt Safe Renderer (QSR) | | | | X,R | ++----------------------------------------------------------+ +| Qt Application Manager | | | | X,R | ++----------------------------------------------------------+ +| Qt Interface Framework | | | | X,R | ++----------------------------------------------------------+ +| Neptune Reference UI | | | | X,R | ++----------------------------------------------------------+ +| Qt for Android Automotive (QAA) | | | | X,R | ++----------------------------------------------------------+ +| Qt Creator | X | X | X | X | ++----------------------------------------------------------+ +| Qt Design Studio Professional | X | X | X | X | ++----------------------------------------------------------+ +| androiddeployqt | X | X | X | X | ++----------------------------------------------------------+ +| androidtestrunner | X | X | X | X | ++----------------------------------------------------------+ +| canbusutil | X | X | X | X | ++----------------------------------------------------------+ +| dumpcpp | X | X | X | X | ++----------------------------------------------------------+ +| dumpdoc | X | X | X | X | ++----------------------------------------------------------+ +| fixqt4headers.pl | X | X | X | X | ++----------------------------------------------------------+ +| idc | X | X | X | X | ++----------------------------------------------------------+ +| moc | X | X | X | X | ++----------------------------------------------------------+ +| pixeltool | X | X | X | X | ++----------------------------------------------------------+ +| qdbus | X | X | X | X | ++----------------------------------------------------------+ +| qdbuscpp2xml | X | X | X | X | ++----------------------------------------------------------+ +| qdbusviwer | X | X | X | X | ++----------------------------------------------------------+ +| qdbusxml2cpp | X | X | X | X | ++----------------------------------------------------------+ +| qdistancefieldgenerator | X | X | X | X | ++----------------------------------------------------------+ +| qdoc | X | X | X | X | ++----------------------------------------------------------+ +| qhelpgenerator | X | X | X | X | ++----------------------------------------------------------+ +| qlalr | X | X | X | X | ++----------------------------------------------------------+ +| qmake | X | X | X | X | ++----------------------------------------------------------+ +| qml | X | X | X | X | ++----------------------------------------------------------+ +| qmlcachegen | X | X | X | X | ++----------------------------------------------------------+ +| qmldom | X | X | X | X | ++----------------------------------------------------------+ +| qmleasing | X | X | X | X | ++----------------------------------------------------------+ +| qmlformat | X | X | X | X | ++----------------------------------------------------------+ +| qmllint | X | X | X | X | ++----------------------------------------------------------+ +| qmlpreview | X | X | X | X | ++----------------------------------------------------------+ +| qmlprofiler | X | X | X | X | ++----------------------------------------------------------+ +| qmlscene | X | X | X | X | ++----------------------------------------------------------+ +| qmltestrunner | X | X | X | X | ++----------------------------------------------------------+ +| qmltime | X | X | X | X | ++----------------------------------------------------------+ +| qmlviewer | X | X | X | X | ++----------------------------------------------------------+ +| qtdiag | X | X | X | X | ++----------------------------------------------------------+ +| qtpaths | X | X | X | X | ++----------------------------------------------------------+ +| qtplugininfo | X | X | X | X | ++----------------------------------------------------------+ +| qvkgen | X | X | X | X | ++----------------------------------------------------------+ +| rcc | X | X | X | X | ++----------------------------------------------------------+ +| tracegen | X | X | X | X | ++----------------------------------------------------------+ +| uic | X | X | X | X | ++----------------------------------------------------------+ +| windeployqt | X | X | X | X | ++----------------------------------------------------------+ +| Target toolchains | | | X | X | ++----------------------------------------------------------+ +| Qt Debugging Bridge Host Tools | | | X | X | ++----------------------------------------------------------+ +| qtconfig-gui | | | X | X | ++----------------------------------------------------------+ +| Qt Emulator | | | X | X | ++----------------------------------------------------------+ +| Qt Creator VxWorks plugin | | | X | X | ++----------------------------------------------------------+ +| Qt Creator plugin for Qt | | | | X | +| Application Manager | | | | | ++----------------------------------------------------------+ +| qmlinterfacegenerator | | | | X | ++----------------------------------------------------------+ +| qmltocpp | | | | X | ++----------------------------------------------------------+ +| qulfontcompiler | | | | X | ++----------------------------------------------------------+ +| Qt Deployment Server | | | | X | ++----------------------------------------------------------+ + + +Rights for Application and Device use cases + +Following table summarizes the rights afforded by different products of the +Licensed Software to create and distribute Applications and Devices as defined +in this Agreement (X marks for rights): + ++---------------------------------------------------------------+ +| | Applications | Devices | ++---------------------------------------------------------------+ +| ADP | X | | ++---------------------------------------------------------------+ +| ADE | X | | ++---------------------------------------------------------------+ +| DCP | X | X | ++---------------------------------------------------------------+ +| DCE | X | X | ++---------------------------------------------------------------+ + +Licensed Software: Designer tools and modules + +The modules and/or tools that are included in the respective product - Qt for +Design Studio Professional (DSP), Qt for Design Studio Enterprise (DSE) - are +marked with "X" in the below table. + +Designer tools provides no Redistributables. + ++---------------------------------------------+ +| | DSP | DSE | ++---------------------------------------------+ +| Qt Design Studio | X | X | ++---------------------------------------------+ +| Qt Design Bridges | | X | ++---------------------------------------------+ +| QML Live on host | X | X | ++---------------------------------------------+ +| QML Live on target | | X | ++---------------------------------------------+ +| Variant Management | | X | ++---------------------------------------------+ +| Shader creation tools | | X | ++---------------------------------------------+ +| Profiling tools | | X | ++---------------------------------------------+ +| Simulink support | | X | ++---------------------------------------------+ + + +Both DSP and DSE can be used to create an user interface for use cases covered +by ADP, ADE, DCP and DCE. + +Licensed Software: QA Tools + +The modules and/or tools that are included in the respective QA Tools product +- Squish (both Tester and execution Licenses), Coco or Test Center - are marked +with "X" in the below table. Optional features that will need additional +licenses are marked with "O". QA Tools include no Redistributables. + ++---------------------------------------------------------------------+ +| | Squish | Coco | Test Center | ++---------------------------------------------------------------------+ +| Squish IDE | X | | | ++---------------------------------------------------------------------+ +| QA Tool-specific command line tools | X | X | X | ++---------------------------------------------------------------------+ +| Coverage Browser | | X | | ++---------------------------------------------------------------------+ +| HTML interface | | | X | ++---------------------------------------------------------------------+ +| Qt Support Module | X | | | ++---------------------------------------------------------------------+ +| Java support module | X | | | ++---------------------------------------------------------------------+ +| Windows support module | X | | | ++---------------------------------------------------------------------+ +| iOS support module | X | | | ++---------------------------------------------------------------------+ +| Android support module | X | | | ++---------------------------------------------------------------------+ +| Web support module | X | | | ++---------------------------------------------------------------------+ +| macOS support module | X | | | ++---------------------------------------------------------------------+ +| VNC support module | X | | | ++---------------------------------------------------------------------+ +| MCU support module | X | | | ++---------------------------------------------------------------------+ +| C and C++ language module | | X | | ++---------------------------------------------------------------------+ +| C# language module | | X | | ++---------------------------------------------------------------------+ +| QML language module | | X | | ++---------------------------------------------------------------------+ +| Tester Cross-Compilation Add-On | O | O | | ++---------------------------------------------------------------------+ + +License capabilities for Squish + +License capabilities that are included in the Squish Tester and Execution +Licenses are marked with "X" in the below table. + ++-----------------------------------------------------------------------------+ +| | Squish Tester License | Squish Execution License | ++-----------------------------------------------------------------------------+ +| Ability to create, edit, | X | | +| and debug test cas | | | ++-----------------------------------------------------------------------------+ +| Ability to execute test | X | X | +| cases | | | ++-----------------------------------------------------------------------------+ + +Install and use capabilities for QA Tools + +Install and use capabilities that are included in the respective QA Tools +products are defined in the below table. + ++-----------------------------------------------------------------------------+ +| | Squish | Squish | Coco | Test | +| | Tester | Execution | License | Center | +| | License | License | | License | ++-----------------------------------------------------------------------------+ +| Number of installation | Unlimited | Unlimited | Unlimited | One(1) | +| instances per license | | | | | ++-----------------------------------------------------------------------------+ +| Number of concurrent | Limited by| Limited by | Limited by | Limited by | +| users | number of | number of | number of | number of | +| | Squish | Squish | Coco | Test Center | +| | Tester | Execution | Tester | Licenses | +| | Licenses | Licenses | Licenses | | ++-----------------------------------------------------------------------------+ + + +APPENDIX 2: PRICING + +Separate template + +APPENDIX 3: ADD-ON PRODUCTS TO LICENSED SOFTWARE + +Intentionally left blank. + +APPENDIX 4: SMALL BUSINESS AND STARTUP + +The provisions of this Appendix 4 are applicable for companies with an annual +revenue, including funding, equivalent to maximum of 250,000 USD (in applicable +currency) during the latest full calendar year, as evidenced by duly audited +records of the Licensee and approved by The Qt Company ("Start-up Company"). + +Start-up Companies are qualified for a discounted License Fee for maximum of +four (4) Development Licenses ("Start-up Development License") unless otherwise +agreed between the parties. + +Start-up Development License entitles the respective Designated User for +Support only for Install Support as defined in Appendix 9, Support Terms. + +Upon expiry of the respective Development License Term, the Start-up +Development Licenses shall be automatically extended, pursuant to Section 3.1 +of the Agreement, for a Renewal Term either as new Start-up Development +Licenses (if the Licensee still qualifies as a Start-up Company), or as normal +then standard list price Development Licenses (if the Licensee no longer +qualifies as a Start-up Company). + +APPENDIX 5: NON-COMMERCIAL AND EDUCATIONAL USE + +The provisions of this Appendix 5 are applicable for non-commercial use of the +Licensed Software by the Licensee. + +For the purpose of this Appendix 5, the following additional definitions +(replacing the relevant definition of the Agreement, where applicable) shall be +applicable: + +"Demo Units" shall mean (i) hardware development platform, which incorporates +the Licensed Software along with Licensee's software and/or hardware, and (ii) +prototype versions of Applications or Devices. + +"Designated User(s)" shall mean the employees and students of the Licensee. + +"Licensee Products" shall mean Applications and/or Devices. + +"Permitted Purpose" shall mean (i) Licensee's internal evaluation and testing +of Licensed Software, (ii) building Demo Units as well as (iii) educational +use. + +"Agreement Term" shall mean a period of twelve (12) months or any such other +period as may be agreed between the Parties. + +For the purpose of this Appendix 5, the following changes shall be agreed with +respect to relevant Sections of the Agreement: + I. Recital (A) shall be replaced in its entirety to read as follows: + "(A) Licensee wishes to use the Licensed Software for the Permitted + Purpose." + II. Section 3.1 shall be replaced in its entirety to read as follows: + "The Qt Company grants to Licensee a personal, non-exclusive, + non-transferable, revocable, royalty-free license, valid for the + Agreement Term, to use, modify and copy the Licensed Software solely + for the Permitted Purpose. Licensee may install copies of the Licensed + Software on five (5) computers per Designated User, provided that only + the Designated Users who have a valid Development License may use the + Licensed Software. Licensee may demonstrate the Demo Units, provided + that such demonstrations must be conducted by Licensee, and the Demo + Units must remain in Licensee's possession and under Licensee's control + at all times. + For clarity, this Agreement does not (i) entitle Licensee to use + Licensed Software to create Applications or Devices (other than + prototypes thereof) or (ii) carry any distribution rights to Licensee, + but such rights are subject to and conditional upon conclusion of a + separate license agreement with The Qt Company." + III. Sections 3.2, 3.3, 3.5, 3.6, 8 and 10 shall be deleted. + IV. Section 3.4 shall be replaced in its entirety to read as follows: + "Licensee shall not: + - remove or alter any copyright, trademark or other proprietary rights + notice contained in any portion of the Licensed Software; + - transfer, publish, sublicense, disclose, display or otherwise make + the Licensed Software available to any third party (except that + Licensee may demonstrate the Demo Units pursuant to Section 3.1); + - in any way combine, incorporate or integrate Licensed Software with, + or use Licensed Software for creation of, any software created with + or incorporating Open Source Qt; Licensee shall cause all Designated + Users who make use of the licenses granted under this Agreement, to + be contractually bound to comply with the relevant terms of this + Agreement and not to use the Licensed Software beyond the terms + hereof. Licensee shall be responsible for any and all actions and + omissions of its Designated Users relating to the Licensed Software + and use thereof. Any use of Licensed Software beyond the provisions + of this Agreement is strictly prohibited and requires an additional + license from The Qt Company." + V. Section 12 shall be replaced in its entirety to read as follows: + "This Agreement shall enter into force upon due acceptance by both + Parties and remain in force for the Agreement Term, unless and until + terminated pursuant to the terms of Section 12. + Upon termination of the Agreement, Licensee shall cease using the + Licensed Software. All other copies of Licensed Software in the + possession or control of Licensee must be erased or destroyed. An + officer of Licensee must, upon request, promptly deliver to The Qt + Company a written confirmation that this has occurred." + +Except for the modifications specified above, this Appendix carries no change +to the terms of the Agreement which shall remain in full force. + +APPENDIX 6: LICENSE REPORTING + +Separate template + +APPENDIX 7: MARKETING RIGHTS + +This Appendix 7 has the purpose to grant visibility through The Qt Company +marketing channels of the usage of Qt and related product and service in +Licensee product. Following related marketing right are agreed between the Qt +Company and the Licensee. + +1. LICENSEE NAME AND LICENSEE LOGO + +The Qt Company has the right to use Licensee name and Licensee logo in public +channel, in respect of the value proposition that the Qt company provided to +the Licensee. + +2. MARKETING CONTENT COOPERATION + +2.1. LICENSEE CASES + +The Licensee is open to collaborate on content creation for marketing and +communication purpose. The Licensee will nominate one responsible that will be +in charge to support The Qt company with this content creation, according to +content format paragraph, answering technical questions or sharing professional +picture or video of required content. The Qt Company will have the right to +advertise this in Content Format and Channel as mentioned in paragraph 3 and 4. + +2.2. FINAL PRODUCT REFERRAL + +Licensee agree that The Qt Company could connect their software product and +services with the Licensee device or application, that the Licensee has created +using The Qt Company technology and competence. Licensee will provide high +quality picture, and video of the created final product where the Qt technology +is running into. The Qt Company will have the right to advertise this in +Content Format and Channel as mentioned in paragraph 3 and 4. + +3. CONTENT FORMAT + +- Video +- Written Licensee case +- Press release +- Social media posts +- Emails +- Event booth Graphics +- Printed material + +4. CHANNELS + +- Social media +- The Qt Company resource center and website +- Email to the Qt company contact database +- Events +- Online webinars +- Public speech +- Public presentations + +APPENDIX 8: INTENTIONALLY LEFT BLANK + +APPENDIX 9: SUPPORT TERMS + +These Qt support terms and conditions ("Support Terms") set forth the legal +framework, where under The Qt Company ("The Qt Company") provides support +services (as herein defined) to the Licensee. + +1 DEFINITIONS + +"Application Code" shall mean a computer software program written strictly +using the Qt programming language, by or for the Licensee, with a user +interface, enabling the Licensee or their users to accomplish a specific task +and display any results of the task on the display monitor or screen. + +"Dedicated Contact" shall mean the employee of The Qt Company who will be the +first point of contact for all Designated Users' requests for Support. + +"Errors" shall mean an error, flaw, mistake, failure, or fault in Licensed +Software that prevents it from behaving as described in the relevant +documentation or as agreed between the Parties. + +"Extended Support" shall mean a continuation to the normal Support period, +which allows Designated Users to receive selected Support (Standard Support or +Premium Support) for a version of Licensed Software that is no longer generally +supported by The Qt Company. + +"Install Support" shall mean Support that is limited to installation related +Error(s) on Development Platforms specified as supported host platforms for +each Qt release under doc.qt.io. + +"Maintenance Release" shall mean a release or version of Licensed Software +containing bug fixes, error corrections and other changes targeted to +maintaining and improving product stability and quality. Maintenance Releases +are generally depicted as a change to the third digit of Licensed Software +version number. + +"Platforms" shall mean both Development Platforms and Deployment Platforms. +Supported host and target Platforms may vary from for each Qt release as +defined under doc.qt.io. + +"Premium Support" shall mean an upgraded level of Support that The Qt Company +provides pursuant to these Support Terms to Licensee if Licensee has purchased +Premium Support instead of Standard Support. Premium Support shall always be +purchased for all Designated User(s) in the respective development team of the +Licensee. + +"Response Time" shall mean the period of time from when Licensee notifies +TheQt Company about an Error or requests Support until The Qt Company provides +Licensee with a response that addresses (but not necessarily resolves) the +reported Error or provides the requested Support. + +"Standard Support" shall mean standard level of Support that The Qt Company +provides pursuant to these Support Terms to Licensee. + +"Support" shall mean developer assistance that is provided by The Qt Company +to assist eligible Designated Users in Licensed Software installation, usage +and functionality problem resolution for Error(s) and Error workarounds +pursuant to the terms of these Support Terms. Support for different products is +available as specified in the below table ("X" marking the Support that is +included in the license price, optional Add-on Support services are marked as +"O"): + ++-----------------------------------------------------------------------+ +| |ADP|ADE|DCP|DCE|DSP|DSE|Squish|Coco|Test Center| ++-----------------------------------------------------------------------+ +| Install Support | X | X | X | X | X | X | X | X | X | ++-----------------------------------------------------------------------+ +| Standard Support | | X | X | X | X | X | X | X | X | ++-----------------------------------------------------------------------+ +| Premium Support | | O | O | O | O | O | O | O | O | ++-----------------------------------------------------------------------+ +| Extended Support | | O | O | O | O | O | | | | ++-----------------------------------------------------------------------+ +| Tool Qualification Kit| | | | | | | O | O | | ++-----------------------------------------------------------------------+ + +"Support Validity Term" shall mean the Development License Term or any other +fixed time period agreed between the Parties during which time the Customer is +eligible to receive Support from The Qt Company. + +"Tool Qualification Kit" shall mean a customized set of documents and +validation test cases. + +2 SUPPORT SERVICES + +2.1 Support Services Provided by The Qt Company + +Subject to these Support Terms and during the Support Validity Term, The Qt +Company will via its web-based support user-interface, provide Designated +User(s) with Support for the Platforms which Customer has licensed under the +Agreement. +The Qt Company will make commercially reasonable efforts to solve any Errors +reported by Designated User(s). Resolution of an Error may be provided through +Designated User(s) themselves downloading of a later released version of the +applicable Licensed Software product(s) or providing the Designated User with a +temporary workaround addressing such Error. + +2.2 Licensee's Obligations + +To report an Error, the Designated User shall register the Error on The Qt +Company's web-based support user interface located at: +https://account.qt.io/login or at another location designated by The Qt Company. + +The Designated User must provide adequate information and documentation to The +Qt Company to enable it to recreate the Error or problem for which the +Designated User has sought assistance. +To ensure efficient handling of Errors, the Designated User must provide the +following information, where relevant: +- A clear, detailed description of the problem, question or suggestion; +- Identification of which Licensed Software product and version is affected; +- Identification of the operating environment (e.g. operating system, hardware + Platform, build tools, etc.) on which the problem exists; +- On Standard Support: A complete and compilable test case of not more than 500 + lines of code that demonstrates the problem; +- On Premium Support: A complete and compilable test case that demonstrates the + problem or access to Application Code source codes. + +Additional relevant content, such as screenshots, etc. +Additional content should be included as attachments. The preferred image +formats are JPEG and PNG. Compressed content should be included in zip or +tar.gz archives. Executable content and documents in platform specific formats +such as Microsoft Office' are not accepted. + +In order for The Qt Company to provide prompt handling of Errors, the +Designated User shall promptly respond to any requests from The Qt Company for +additional information. + +2.3 Support Limitations + +General limitations: + +Each version or release of the Licensed Software will be Supported under +Standard Support or Premium Support only for limited time period as set forth +in doc.qt.io. For example, regular releases of Qt Software are supported for +one (1) year from the release date of the version x.y.0 and Long Term Support +(LTS) Releases are supported for a period of three (3) years from the release +date of the LTS version x.y.0. + +The Qt Company shall only provide Support for Designated User(s). + +Support is made available for the entire development teams only: It is not +allowed to purchase Support only for some members of the development team, and +all Designated Users of the respective development team must be eligible for +the same level of Support. + +Support is not provided for snapshots, preview releases, beta releases or +release candidates. + +The Qt Company shall have no obligation to provide Support for hardware or +operating system specific problems or problems arising from improper use, +accident, neglect or modification of Qt. + +Limitations with Install Support: + +Support limited to Error(s) regarding installation and setting up of the Qt +development environment on host Platforms. + +Limitations with Standard Support: + +The Qt Company shall not provide Support for third-party software or problems +caused by third-party software even if such third-party software is distributed +together with Licensed Software product(s). + +The Qt Company shall only provide Support for Error(s) that are reported on and +can be reproduced on Platforms that are officially supported for the release of +the Licensed Software. + +Limitations with Premium support: + +The Qt Company shall not provide Support for third-party software or problems +caused by third-party software. However, if such third-party software is +distributed together with Licensed Software, The Qt Company will make +commercially reasonable efforts to solve such problems. + +The Qt Company shall only provide Support for Error(s) that can be reproduced +on Platforms that are officially supported for the release of the Licensed +Software. If the Error is on a Platform that is not supported, The Qt Company +will make commercially reasonable efforts to provide a solution on closest +corresponding supported Platform. + +Premium Support is optional and purchased for an agreed bucket of hours +("Bucket"). Hours can be used by any Designated User in the respective +development team. To encourage continuous usage of the Support, ten percent +(10%) of the purchased Bucket shall automatically expire (regardless of whether +such support hours are actually used or not by the Licensee) each month after +three (3) months from the purchase of the Premium Support. + +2.4 Extended Support + +Extended Support extends the Support Validity Term for a release of Licensed +Software that is no longer generally supported. + +Extended Support includes and is by default provided with Standard Support +rules and limitations, unless Extended Support is purchased with Premium +Support in which case Premium Support rules and limitations will apply. + +Extended Support is optional and purchased with annual fee and separately per +each Licensee product. Extended Support will need definition of (i) Licensee +product, (ii) used Platform(s) and (iii) Licensed Software version(s). + +2.5 Tool Qualification Kit + +The Qt Company shall provide set of customized documents and validation tests +that enable Licensee to qualify QA testing tool for the purpose of ISO 26262, +EN 50128, DO-330, IEC 61508, IEC 62304 or IEC 13485 certification Licensee end +to end solution. + +3 RESPONSE TIME + +In performing Support, The Qt Company shall commit to following, non-binding, +Response Times: + +Standard Support: Errors and Support requests will have a Response Time not to +exceed two (2) business days. + +Premium Support: Errors and Support requests will have a Response Time not to +exceed one (1) business day. + +For complex issues, The Qt Company may provide an initial response to the +Designated User and then follow up, without undue delay, with additional +communication before an Error is properly addressed or Support provided. + +4 ADDITIONAL SERVICES IN PREMIUM SUPPORT + +The Designated User(s) will be assigned a Dedicated Contact to handle requests +for Support. Dedicated Contact is subject to change in cases such as sick +leave, vacation and other similar reasons. + +The Designated User(s) can on request ask The Qt Company to access their +computer remotely in order to resolve problems directly. + +The Designated User(s) can request a session via Instant Messaging or phone +call in the support request to The Qt Company. + +Premium Support can assist Licensee in implementing new features, bug fixes +and accessing patches in Licensed Software or Application Code. + +All Support requests will be handled with high priority. + +5 MAINTENANCE RELEASES, UPDATES AND UPGRADES + +Under the Support the Customer is eligible for Maintenance Releases and Updates +that The Qt Company generally makes available to customers who has purchased +Support. Unless otherwise decided by The Company at its free and absolute +discretion, Upgrades will not be provided under the Support. + +The primary focus of Maintenance Releases is product quality. Therefore, each +Maintenance Release typically includes the following types of changes to the +previous version of Licensed Software: +- Bug fixes caused by changes to previously working code; +- Fixes related to build issues on supported Platforms; +- Error corrections specific to a single Platform that are not present on other + Platforms; +- Critical Error corrections such as crashes, data corruption, loss of data, + race conditions; and +- Updates to documentation and license information when deemed necessary by + The Qt Company. + +The primary focus of Updates is introducing new features to Licensed Software +and covering new platforms. Therefore, each Updates typically includes the +following types of changes to the previous version of Licensed Software: +- New platform support; +- New toolchain support; +- New features and Qt modules; + +6 WARRANTY DISCLAIMER + +The Qt Company makes no warranties that the Support provided will be successful +in resolving any difficulties or problems or in diagnosing faults reported by +Licensee. Support is provided to Licensee on an "as is" basis. To the maximum +extent permitted by applicable law, The Qt Company disclaims all warranties and +conditions, either express or implied, including, but not limited to, implied +warranties of merchantability and fitness for a particular purpose for the +Support provided by The Qt Company to Licensee. + +APPENDIX 10: CONVERSION TO SUBSCRIPTION + +Subject to the terms of this Appendix Licensee's current development licenses +("Current Licenses") for commercial version of Qt Software and the license +agreements governing such Current Licenses ("Existing Agreements") are being +replaced by this Agreement and subscription based Development Licenses +governed hereunder, as further specified below. + ++---------------------------------------------------------------------------+ +| Existing Agreement(s) | and | +| signing parties, version | | +| thereof | | ++---------------------------------------------------------------------------+ + +Parties hereby agree on conversion of Current Licenses listed in attached +Exhibit A to the subscription licenses listed in attached Exhibit B for use +through License Term. As of the date hereof, + +i. Licensee's Current Licenses as listed in Exhibit A shall terminate and be +replaced with the Subscription licenses listed in Exhibit B and; +ii. Existing Agreements are terminated. + +Prices for the conversion of Current Licenses are defined in Appendix 2 +Pricing or Quote. + +Notwithstanding anything in this Appendix to the contrary, and in addition to +any payments due pursuant to this Appendix, Licensee remains fully obligated to +fulfill any and all outstanding payment obligations to The Qt Company under any +applicable Existing Agreements. For the avoidance of doubt, if any payments +remain outstanding on the Current Licenses under the applicable terms Licensee +will continue to make such payments in accordance with the applicable order +documentation, notwithstanding the fact that the Current Licenses are being +converted to Development Licenses pursuant to this Appendix. diff --git a/.cmake.conf b/.cmake.conf new file mode 100644 index 0000000..de369df --- /dev/null +++ b/.cmake.conf @@ -0,0 +1,2 @@ +set(QT_REPO_MODULE_VERSION "6.4.2") +set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") diff --git a/.tag b/.tag new file mode 100644 index 0000000..4eb7706 --- /dev/null +++ b/.tag @@ -0,0 +1 @@ +5af075e26e243bc155882720e0736bdae01f6da9 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a17135f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.16) + +include(.cmake.conf) +project(QtRemoteObjects + VERSION "${QT_REPO_MODULE_VERSION}" + DESCRIPTION "Qt RemoteObjects Libraries" + HOMEPAGE_URL "https://qt.io/" + LANGUAGES CXX C +) + +find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals Core Network) +find_package(Qt6 ${PROJECT_VERSION} CONFIG OPTIONAL_COMPONENTS Gui Widgets Quick QuickTest) + +if(NOT TARGET Qt::Network) + message(NOTICE "Skipping the build as the condition \"TARGET Qt::Network\" is not met.") + return() +endif() +qt_build_repo() + +if(NOT QT_BUILD_STANDALONE_TESTS) + # Copy mkspecs for users preferring qmake builds + set(mkspecs_install_dir "${INSTALL_MKSPECSDIR}") + qt_path_join(mkspecs_install_dir ${QT_INSTALL_DIR} ${mkspecs_install_dir}) + + qt_copy_or_install(DIRECTORY mkspecs/ + DESTINATION "${mkspecs_install_dir}" + FILES_MATCHING PATTERN "*.pr[if]" + ) +endif() diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt new file mode 100644 index 0000000..b91bbd8 --- /dev/null +++ b/LICENSES/BSD-3-Clause.txt @@ -0,0 +1,9 @@ +Copyright (c) . + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 2. 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. + 3. Neither the name of the copyright holder 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 HOLDER 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. diff --git a/LICENSES/GFDL-1.3-no-invariants-only.txt b/LICENSES/GFDL-1.3-no-invariants-only.txt new file mode 100644 index 0000000..857214d --- /dev/null +++ b/LICENSES/GFDL-1.3-no-invariants-only.txt @@ -0,0 +1,451 @@ + + GNU Free Documentation License + Version 1.3, 3 November 2008 + + + Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall +subject (or to related matters) and contains nothing that could fall +directly within that overall subject. (Thus, if the Document is in +part a textbook of mathematics, a Secondary Section may not explain +any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +The "publisher" means any person or entity that distributes copies of +the Document to the public. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no +other conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to +give them a chance to provide you with an updated version of the +Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other +documents released under this License, and replace the individual +copies of this License in the various documents with a single copy +that is included in the collection, provided that you follow the rules +of this License for verbatim copying of each of the documents in all +other respects. + +You may extract a single document from such a collection, and +distribute it individually under this License, provided you insert a +copy of this License into the extracted document, and follow this +License in all other respects regarding verbatim copying of that +document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense, or distribute it is void, and +will automatically terminate your rights under this License. + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, receipt of a copy of some or all of the same material does +not give you any rights to use it. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the +GNU Free Documentation License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. See +https://www.gnu.org/licenses/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. If the Document +specifies that a proxy can decide which future versions of this +License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the +Document. + +11. RELICENSING + +"Massive Multiauthor Collaboration Site" (or "MMC Site") means any +World Wide Web server that publishes copyrightable works and also +provides prominent facilities for anybody to edit those works. A +public wiki that anybody can edit is an example of such a server. A +"Massive Multiauthor Collaboration" (or "MMC") contained in the site +means any set of copyrightable works thus published on the MMC site. + +"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 +license published by Creative Commons Corporation, a not-for-profit +corporation with a principal place of business in San Francisco, +California, as well as future copyleft versions of that license +published by that same organization. + +"Incorporate" means to publish or republish a Document, in whole or in +part, as part of another Document. + +An MMC is "eligible for relicensing" if it is licensed under this +License, and if all works that were first published under this License +somewhere other than this MMC, and subsequently incorporated in whole or +in part into the MMC, (1) had no cover texts or invariant sections, and +(2) were thus incorporated prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site +under CC-BY-SA on the same site at any time before August 1, 2009, +provided the MMC is eligible for relicensing. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/LICENSES/GPL-2.0-only.txt b/LICENSES/GPL-2.0-only.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSES/GPL-2.0-only.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/LICENSES/GPL-3.0-only.txt b/LICENSES/GPL-3.0-only.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSES/GPL-3.0-only.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSES/LGPL-3.0-only.txt b/LICENSES/LGPL-3.0-only.txt new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/LICENSES/LGPL-3.0-only.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/LICENSES/LicenseRef-Qt-Commercial.txt b/LICENSES/LicenseRef-Qt-Commercial.txt new file mode 100644 index 0000000..825b1f3 --- /dev/null +++ b/LICENSES/LicenseRef-Qt-Commercial.txt @@ -0,0 +1,8 @@ +Licensees holding valid commercial Qt licenses may use this software in +accordance with the the terms contained in a written agreement between +you and The Qt Company. Alternatively, the terms and conditions that were +accepted by the licensee when buying and/or downloading the +software do apply. + +For the latest 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. diff --git a/LICENSES/Qt-GPL-exception-1.0.txt b/LICENSES/Qt-GPL-exception-1.0.txt new file mode 100644 index 0000000..d0322bf --- /dev/null +++ b/LICENSES/Qt-GPL-exception-1.0.txt @@ -0,0 +1,22 @@ +The Qt Company GPL Exception 1.0 + +Exception 1: + +As a special exception you may create a larger work which contains the +output of this application and distribute that work under terms of your +choice, so long as the work is not otherwise derived from or based on +this application and so long as the work does not in itself generate +output that contains the output from this application in its original +or modified form. + +Exception 2: + +As a special exception, you have permission to combine this application +with Plugins licensed under the terms of your choice, to produce an +executable, and to copy and distribute the resulting executable under +the terms of your choice. However, the executable must be accompanied +by a prominent notice offering all users of the executable the entire +source code to this application, excluding the source code of the +independent modules, but including any changes you have made to this +application, under the terms of this license. + diff --git a/coin/module_config.yaml b/coin/module_config.yaml new file mode 100644 index 0000000..16d158c --- /dev/null +++ b/coin/module_config.yaml @@ -0,0 +1,12 @@ +version: 2 +accept_configuration: + condition: property + property: features + not_contains_value: Disable + +instructions: + Build: + - !include "{{qt/qtbase}}/coin_module_build_template_v2.yaml" + + Test: + - !include "{{qt/qtbase}}/coin_module_test_template_v3.yaml" diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..1082806 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,35 @@ +# Copyright (C) 2021 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +from conans import ConanFile +import re +from pathlib import Path + + +def _parse_qt_version_by_key(key: str) -> str: + with open(Path(__file__).parent.resolve() / ".cmake.conf") as f: + m = re.search(fr'{key} .*"(.*)"', f.read()) + return m.group(1) if m else "" + + +def _get_qt_minor_version() -> str: + return ".".join(_parse_qt_version_by_key("QT_REPO_MODULE_VERSION").split(".")[:2]) + + +class QtRemoteObjects(ConanFile): + name = "qtremoteobjects" + license = "LGPL-3.0, GPL-2.0+, Commercial Qt License Agreement" + author = "Brett Stottlemyer, Ford Motor Company" + url = "https://code.qt.io/cgit/qt/qtremoteobjects.git" + description = ( + "Qt Remote Objects (QtRO) is an Inter-Process Communication (IPC) module developed for Qt. " + "This module extends Qt's existing functionalities to enable information exchange between " + "processes or computers, easily." + ) + topics = "qt", "qt6", "QtRO", "IPC" + settings = "os", "compiler", "arch", "build_type" + # for referencing the version number and prerelease tag and dependencies info + exports = ".cmake.conf", "dependencies.yaml" + exports_sources = "*", "!conan*.*" + python_requires = f"qt-conan-common/{_get_qt_minor_version()}@qt/everywhere" + python_requires_extend = "qt-conan-common.QtLeafModule" diff --git a/dependencies.yaml b/dependencies.yaml new file mode 100644 index 0000000..d2f29d7 --- /dev/null +++ b/dependencies.yaml @@ -0,0 +1,7 @@ +dependencies: + ../qtbase: + ref: e3e40c44d3f998a433a6a1080297c5f28e9a768f + required: true + ../qtdeclarative: + ref: 1b58f30087eedf42f16572d8ae1d6a5b18f3d698 + required: false diff --git a/dist/changes-5.11.0 b/dist/changes-5.11.0 new file mode 100644 index 0000000..27072a6 --- /dev/null +++ b/dist/changes-5.11.0 @@ -0,0 +1,41 @@ +Qt 5.11 introduces many new features and improvements as well as bugfixes +over the 5.10.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +Note: Tech Preview modules are able to change APIs to refine or enhance the +module's functionality. Thus Qt's binary compatibility quarantees aren't +applicable. Code switching to 5.11.0 from earlier versions of Qt Remote +Objects will need to be recompiled. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.11.0 Changes * +**************************************************************************** + + - QRemoteObjectNode + * [QTBUG-64086] Added heartbeatInterval property, which can be used to + periodically check the connection between replica and source. If the + connection is lost, the replica enters the "Suspect" state and will + attempt to reconnect. + * Extended acquireModel to allow prefetching data. + + - QRemoteObjectAbstractPersistedStore + * Renamed from QRemoteObjectPersistedStore, and made a QObject to allow + use from QML. + * Added QRemoteObjectSettingsStore (SettingsStore in QML) as a concrete + implementation using QSettings. + + - repc + * Added support for nested classes, via an extension to the .rep file + format. The syntax is `CLASS (), where type needs to be a + class defined in the same rep file, and name is the property name + used to access the sub-QObject. diff --git a/dist/changes-5.11.1 b/dist/changes-5.11.1 new file mode 100644 index 0000000..610e8ad --- /dev/null +++ b/dist/changes-5.11.1 @@ -0,0 +1,26 @@ +Qt 5.11.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.11.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +Note: Tech Preview modules are able to change APIs to refine or enhance the +module's functionality. Thus Qt's binary compatibility quarantees aren't +applicable. Code switching to 5.11.0 from earlier versions of Qt Remote +Objects will need to be recompiled. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.11.1 Changes * +**************************************************************************** + + - This release contains several fixes for MODEL/SUBCLASS handling and tests. diff --git a/dist/changes-5.11.2 b/dist/changes-5.11.2 new file mode 100644 index 0000000..8efdf5e --- /dev/null +++ b/dist/changes-5.11.2 @@ -0,0 +1,24 @@ +Qt 5.11.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.11.0 through 5.11.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.11 series is binary compatible with the 5.10.x series. +Applications compiled for 5.10 will continue to run with 5.11. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.11.2 Changes * +**************************************************************************** + + - This release contains only minor code improvements. diff --git a/dist/changes-5.11.3 b/dist/changes-5.11.3 new file mode 100644 index 0000000..28f337e --- /dev/null +++ b/dist/changes-5.11.3 @@ -0,0 +1,27 @@ +Qt 5.11.3 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.11.0 through 5.11.2. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +Note: Tech Preview modules are able to change APIs to refine or enhance the +module's functionality. Thus Qt's binary compatibility quarantees aren't +applicable. Code switching to 5.11.0 from earlier versions of Qt Remote +Objects will need to be recompiled. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.11.3 Changes * +**************************************************************************** + + - QRemoteObjectNode + * Fix a constant reconnect issue diff --git a/dist/changes-5.12.0 b/dist/changes-5.12.0 new file mode 100644 index 0000000..0884e4f --- /dev/null +++ b/dist/changes-5.12.0 @@ -0,0 +1,46 @@ +Qt 5.12 introduces many new features and improvements as well as bugfixes +over the 5.11.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +Note: Tech Preview modules are able to change APIs to refine or enhance the +module's functionality. Thus Qt's binary compatibility quarantees aren't +applicable. Code switching to 5.12.0 from earlier versions of Qt Remote +Objects will need to be recompiled. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.12.0 Changes * +**************************************************************************** + - General + * [QTBUG-68678] Graduate Qt Remote Objects from TP to fully supported + module of Qt + * [QTBUG-65727] QtRemoteObjects: Documentation doesn't match the code + + - QRemoteObjectNode + * Support externally created QIODevices, including SSL sockets. + * Added proxy() method to support use-cases like having a single node + provide a (for instance) tcp url for a device allowing access from + other devices, while using internal connections (like "local:*") on + the device. + + - repc + * [QTBUG-68976] Cannot add comments in .rep file + * [QTBUG-68975] Support multiline PODs and ENUMs in repc + * [QTBUG-67770] repc should be able to use tab characters as a normal + whitespace + +**************************************************************************** +* QML * +**************************************************************************** + + - Updated import statement to better match typical module pattern. From + QML "import QtRemoteObjects 5.12" should now be used. diff --git a/dist/changes-5.12.1 b/dist/changes-5.12.1 new file mode 100644 index 0000000..dfb633f --- /dev/null +++ b/dist/changes-5.12.1 @@ -0,0 +1,30 @@ +Qt 5.12.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +Note: Tech Preview modules are able to change APIs to refine or enhance the +module's functionality. Thus Qt's binary compatibility quarantees aren't +applicable. Code switching to 5.12 (when Remote Objects "graduated" from +Tech Preview) from earlier versions of Qt Remote Objects will need to be +recompiled. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.12.1 Changes * +**************************************************************************** + - General + * [QTBUG-72064] QVariant is also a standalone type and we must treat it + accordingly + * Convert QNX backend to use shared pointers + * Various code cleanup diff --git a/dist/changes-5.12.2 b/dist/changes-5.12.2 new file mode 100644 index 0000000..0c8bd85 --- /dev/null +++ b/dist/changes-5.12.2 @@ -0,0 +1,20 @@ +Qt 5.12.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.0 through 5.12.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + - This release contains only minor code improvements. diff --git a/dist/changes-5.12.3 b/dist/changes-5.12.3 new file mode 100644 index 0000000..8503086 --- /dev/null +++ b/dist/changes-5.12.3 @@ -0,0 +1,29 @@ +Qt 5.12.3 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.0 through 5.12.2. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +Note: Tech Preview modules are able to change APIs to refine or enhance the +module's functionality. Thus Qt's binary compatibility quarantees aren't +applicable. Code switching to 5.12 (when Remote Objects "graduated" from +Tech Preview) from earlier versions of Qt Remote Objects will need to be +recompiled. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.12.1 Changes * +**************************************************************************** + - General + * [QTBUG-74221] Fix restart/nullptr crash + * [QTBUG-73962] Fix crash with AllowExternalRegistration and QtRO schema + * Doc: Add link from index page to QML Types diff --git a/dist/changes-5.12.4 b/dist/changes-5.12.4 new file mode 100644 index 0000000..e415add --- /dev/null +++ b/dist/changes-5.12.4 @@ -0,0 +1,71 @@ +Qt 5.12.4 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.0 through 5.12.3 under most +conditions. There may be exceptions if you are using Qt Remote Objects +between proesses or devices running different versions Qt. The different +versions of Qt must be running compatible versions of QtRO's protocol. + +Changes to the Qt Remote Objects protocol are documented online: + +https://doc.qt.io/qt-5/qtremoteobjects-compatibility.html + +The QtRO protocol has been updated in Qt 5.12.4 and Qt 5.13.0 to 1.3. + +Background: Qt's QueuedConnections work by copying signal parameters so +the copy can be held and then later processed in another threads +eventloop, allowing the original data to be changed in the originating +thread. + +Qt Remote Objects leverages this capability and sends that data from a +QObject in one process to a QObject in another. There is a specific +protocol for this, as both sides need to interpret what is sent the +same way. For example, there are Invoke and PropertyChanged packets +exchanged. In addition to this, both sides need to know how to encode and +decode all types shared. This type awareness is easy to ensure if the +repc compiler is used to generate the headers for all sides. + +However, there are use cases where this isn't possible. In these cases +QtRO will send the type (metaobject) information as well, allowing the +receiving side to generate the required type information at runtime for +certain types (PODs and enumerations). + +There were issues found in this type serialization code that required a +change to the protocol. There are a number of commits going into both +Qt 5.13.0 and Qt 5.12.4 with fixes. This means that you will not be able +to use Qt Remote Objects on a device using Qt 5.12.3 or earlier and +communicate to another device using Qt 5.12.4/5.13.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12 as long as +the protocol change noted above is taken into account. + +**************************************************************************** +* Important Behavior Changes * +**************************************************************************** + + - Qt Remote Objects uses an internal protocol to pass data between + processes and/or devices. The same protocol version needs to be used on + all sides. The version was bumped from 1.2 to 1.3 in this release, + fixing potential crashes (see QTBUG-75017). If there is a mismatch, the + connecting node will output a warning and the host node will not send + any data. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +The following fixes were part of the above protocol change and are in +5.12.4 and 5.13.0. + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + - QTBUG-75017: QtRO processes can crash if type registration is incorrect + - QTBUG-75056: Correctly handle QVariant properties on the replica side + - QTBUG-74084: QT remote objects false conversion warning in case of + QVariant property diff --git a/dist/changes-5.12.5 b/dist/changes-5.12.5 new file mode 100644 index 0000000..e8be931 --- /dev/null +++ b/dist/changes-5.12.5 @@ -0,0 +1,20 @@ +Qt 5.12.5 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.0 through 5.12.4. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + - This release contains only minor code improvements. diff --git a/dist/changes-5.13.0 b/dist/changes-5.13.0 new file mode 100644 index 0000000..cc8d4ff --- /dev/null +++ b/dist/changes-5.13.0 @@ -0,0 +1,57 @@ +Qt 5.13 introduces many new features and improvements as well as bugfixes +over the 5.12.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.13 series is binary compatible with the 5.12.x series. +Applications compiled for 5.12 will continue to run with 5.13 under most +conditions. There may be exceptions if you are using Qt Remote Objects +between proesses or devices running different versions Qt. The different +versions of Qt must be running compatible versions of QtRO's protocol. + +Changes to the Qt Remote Objects protocol are documented online: + +https://doc.qt.io/qt-5/qtremoteobjects-compatibility.html + +The QtRO protocol has been updated in Qt 5.12.4 and Qt 5.13.0 to 1.3. + +Background: Qt's QueuedConnections work by copying signal parameters so +the copy can be held and then later processed in another threads +eventloop, allowing the original data to be changed in the originating +thread. + +Qt Remote Objects leverages this capability and sends that data from a +QObject in one process to a QObject in another. There is a specific +protocol for this, as both sides need to interpret what is sent the +same way. For example, there are Invoke and PropertyChanged packets +exchanged. In addition to this, both sides need to know how to encode and +decode all types shared. This type awareness is easy to ensure if the +repc compiler is used to generate the headers for all sides. + +However, there are use cases where this isn't possible. In these cases +QtRO will send the type (metaobject) information as well, allowing the +receiving side to generate the required type information at runtime for +certain types (PODs and enumerations). + +There were issues found in this type serialization code that required a +change to the protocol. There are a number of commits going into both +Qt 5.13.0 and Qt 5.12.4 with fixes. This means that you will not be able +to use Qt Remote Objects on a device using Qt 5.12.3 or earlier and +communicate to another device using Qt 5.12.4/5.13.0. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +The following fixes were part of the above protocol change and are in +5.12.4 and 5.13.0. + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + - QTBUG-75017: QtRO processes can crash if type registration is incorrect + - QTBUG-75056: Correctly handle QVariant properties on the replica side + - QTBUG-74084: QT remote objects false conversion warning in case of + QVariant property diff --git a/dist/changes-5.13.1 b/dist/changes-5.13.1 new file mode 100644 index 0000000..ce9927b --- /dev/null +++ b/dist/changes-5.13.1 @@ -0,0 +1,21 @@ +Qt 5.13.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.13.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.13 series is binary compatible with the 5.12.x series. +Applications compiled for 5.12 will continue to run with 5.13. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + - Improved clarity and consistency of documentation. + - Made a number of fixes and improvements in the handling of enumerations. diff --git a/dist/changes-5.13.2 b/dist/changes-5.13.2 new file mode 100644 index 0000000..be59a25 --- /dev/null +++ b/dist/changes-5.13.2 @@ -0,0 +1,30 @@ +Qt 5.13.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.13.0 through 5.13.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.13 series is binary compatible with the 5.12.x series. +Applications compiled for 5.12 will continue to run with 5.13 under most +conditions. There may be exceptions if you are using Qt Remote Objects +between proesses or devices running different versions Qt. The different +versions of Qt must be running compatible versions of QtRO's protocol. + +Changes to the Qt Remote Objects protocol are documented online: + +https://doc.qt.io/qt-5/qtremoteobjects-compatibility.html + +The QtRO protocol has been updated in Qt 5.12.4 and Qt 5.13.0 to 1.3. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + - This release contains only minor code improvements and some improvements + to the documentation. diff --git a/dist/changes-5.14.0 b/dist/changes-5.14.0 new file mode 100644 index 0000000..2270fc6 --- /dev/null +++ b/dist/changes-5.14.0 @@ -0,0 +1,25 @@ +Qt 5.14 introduces many new features and improvements as well as bugfixes +over the 5.13.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.14 series is binary compatible with the 5.13.x series. +Applications compiled for 5.13 will continue to run with 5.14. + +If you are using Qt Remote Objects between proesses or devices running +different versions Qt, the different versions of Qt must be running +compatible versions of QtRO's protocol. Changes to the Qt Remote Objects +protocol are documented online: + +https://doc.qt.io/qt-5/qtremoteobjects-compatibility.html + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + - QTBUG-77178 Support SLOTs with return values in QML diff --git a/dist/changes-5.14.1 b/dist/changes-5.14.1 new file mode 100644 index 0000000..bdb1e12 --- /dev/null +++ b/dist/changes-5.14.1 @@ -0,0 +1,27 @@ +Qt 5.14.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.14.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.14 series is binary compatible with the 5.13.x series. +Applications compiled for 5.13 will continue to run with 5.14. + +If you are using Qt Remote Objects between processes or devices running +different versions Qt, the different versions of Qt must be running +compatible versions of QtRO's protocol. Changes to the Qt Remote Objects +protocol are documented online: + +https://doc.qt.io/qt-5/qtremoteobjects-compatibility.html + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + - This release contains only minor code improvements. diff --git a/dist/changes-5.14.2 b/dist/changes-5.14.2 new file mode 100644 index 0000000..ca7ab1e --- /dev/null +++ b/dist/changes-5.14.2 @@ -0,0 +1,27 @@ +Qt 5.14.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.14.0 through 5.14.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.14 series is binary compatible with the 5.13.x series. +Applications compiled for 5.13 will continue to run with 5.14. + +If you are using Qt Remote Objects between processes or devices running +different versions Qt, the different versions of Qt must be running +compatible versions of QtRO's protocol. Changes to the Qt Remote Objects +protocol are documented online: + +https://doc.qt.io/qt-5/qtremoteobjects-compatibility.html + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + - This release contains only minor code improvements. diff --git a/dist/changes-5.15.0 b/dist/changes-5.15.0 new file mode 100644 index 0000000..610d956 --- /dev/null +++ b/dist/changes-5.15.0 @@ -0,0 +1,22 @@ +Qt 5.15 introduces many new features and improvements as well as bugfixes +over the 5.14.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.15 series is binary compatible with the 5.14.x series. +Applications compiled for 5.14 will continue to run with 5.15. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* QML * +**************************************************************************** + + - Exposed Host to enable remoting of source objects from QML. diff --git a/dist/changes-5.15.1 b/dist/changes-5.15.1 new file mode 100644 index 0000000..332ee9d --- /dev/null +++ b/dist/changes-5.15.1 @@ -0,0 +1,20 @@ +Qt 5.15.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.15.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.15 series is binary compatible with the 5.14.x series. +Applications compiled for 5.14 will continue to run with 5.15. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + - This release contains only minor code improvements. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..364f333 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,5 @@ +qt_examples_build_begin(EXTERNAL_BUILD) + +add_subdirectory(remoteobjects) + +qt_examples_build_end() diff --git a/examples/examples.pro b/examples/examples.pro new file mode 100644 index 0000000..09f19f7 --- /dev/null +++ b/examples/examples.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +SUBDIRS = \ + remoteobjects diff --git a/examples/remoteobjects/CMakeLists.txt b/examples/remoteobjects/CMakeLists.txt new file mode 100644 index 0000000..ecaabf5 --- /dev/null +++ b/examples/remoteobjects/CMakeLists.txt @@ -0,0 +1,18 @@ +qt_internal_add_example(remoteobjects_server) +qt_internal_add_example(cppclient) +add_subdirectory(simpleswitch) +add_subdirectory(websockets) +if(TARGET Qt::Widgets) + qt_internal_add_example(modelviewclient) + qt_internal_add_example(modelviewserver) +endif() +if(QT_FEATURE_ssl) + add_subdirectory(ssl) +endif() +if(TARGET Qt::Quick) + qt_internal_add_example(plugins) + qt_internal_add_example(clientapp) +endif() +if(TARGET Qt::Quick AND UNIX AND NOT ANDROID) + qt_internal_add_example(qmlmodelviewclient) +endif() diff --git a/examples/remoteobjects/clientapp/CMakeLists.txt b/examples/remoteobjects/clientapp/CMakeLists.txt new file mode 100644 index 0000000..4b92a5b --- /dev/null +++ b/examples/remoteobjects/clientapp/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.16) +project(clientapp LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/clientapp") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick RemoteObjects) + +qt_add_executable(clientapp + main.cpp +) + +set_target_properties(clientapp PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(clientapp PUBLIC + Qt::Core + Qt::Gui + Qt::Quick + Qt::RemoteObjects +) + +# Resources: +set(clientapp_resource_files + "qml/plugins.qml" + "qml/plugins0.qml" + "qml/plugins1.qml" + "qml/plugins2.qml" +) + +qt6_add_resources(clientapp "clientapp" + PREFIX + "/qml" + FILES + ${clientapp_resource_files} +) + +install(TARGETS clientapp + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/clientapp/clientapp.pro b/examples/remoteobjects/clientapp/clientapp.pro new file mode 100644 index 0000000..6d5a25d --- /dev/null +++ b/examples/remoteobjects/clientapp/clientapp.pro @@ -0,0 +1,11 @@ +SOURCES += main.cpp + +RESOURCES += \ + clientapp.qrc + +QT += remoteobjects quick + +contains(QT_CONFIG, c++11): CONFIG += c++11 + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/clientapp +INSTALLS += target diff --git a/examples/remoteobjects/clientapp/clientapp.qrc b/examples/remoteobjects/clientapp/clientapp.qrc new file mode 100644 index 0000000..23bdb39 --- /dev/null +++ b/examples/remoteobjects/clientapp/clientapp.qrc @@ -0,0 +1,8 @@ + + + qml/plugins0.qml + qml/plugins.qml + qml/plugins1.qml + qml/plugins2.qml + + diff --git a/examples/remoteobjects/clientapp/main.cpp b/examples/remoteobjects/clientapp/main.cpp new file mode 100644 index 0000000..d4e5ba8 --- /dev/null +++ b/examples/remoteobjects/clientapp/main.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQuickView viewer; + viewer.engine()->addImportPath(QStringLiteral("qrc:/qml")); + viewer.setSource(QUrl(QStringLiteral("qrc:/qml/qml/plugins.qml"))); + viewer.show(); + + return app.exec(); +} diff --git a/examples/remoteobjects/clientapp/qml/plugins.qml b/examples/remoteobjects/clientapp/qml/plugins.qml new file mode 100644 index 0000000..0139d24 --- /dev/null +++ b/examples/remoteobjects/clientapp/qml/plugins.qml @@ -0,0 +1,24 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick 2.0 + +Item { + width: 200 + height: 400 + property int counter: 0; + MouseArea { + anchors.fill: parent + onClicked: + { + counter = (counter + 1) % 3; + console.log(counter); + pageLoader.source = "plugins"+counter+".qml" + } + } + Loader { + id: pageLoader + source: "plugins0.qml" + } +} +//![0] diff --git a/examples/remoteobjects/clientapp/qml/plugins0.qml b/examples/remoteobjects/clientapp/qml/plugins0.qml new file mode 100644 index 0000000..aa5ed68 --- /dev/null +++ b/examples/remoteobjects/clientapp/qml/plugins0.qml @@ -0,0 +1,11 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +//![0] +import QtQuick 2.0 + +Rectangle { + width: 200 + height: 400 + color: "blue" +} +//![0] diff --git a/examples/remoteobjects/clientapp/qml/plugins1.qml b/examples/remoteobjects/clientapp/qml/plugins1.qml new file mode 100644 index 0000000..ba05d61 --- /dev/null +++ b/examples/remoteobjects/clientapp/qml/plugins1.qml @@ -0,0 +1,23 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +//![0] +import QtQuick 2.0 +import TimeExample 1.0 // import types from the plugin + +Rectangle { + width: 200 + height: 400 + color: "blue" + Clock { // this class is defined in QML (imports/TimeExample/Clock.qml) + id: clock1 + anchors.top: parent.top + Time { // this class is defined in C++ (plugin.cpp) + id: time + } + + hours: time.hour + minutes: time.minute + + } +} +//![0] diff --git a/examples/remoteobjects/clientapp/qml/plugins2.qml b/examples/remoteobjects/clientapp/qml/plugins2.qml new file mode 100644 index 0000000..576f3f8 --- /dev/null +++ b/examples/remoteobjects/clientapp/qml/plugins2.qml @@ -0,0 +1,34 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +//![0] +import QtQuick 2.0 +import TimeExample 1.0 // import types from the plugin + +Rectangle { + width: 200 + height: 400 + Clock { // this class is defined in QML (imports/TimeExample/Clock.qml) + id: clock1 + anchors.top: parent.top + Time { // this class is defined in C++ (plugin.cpp) + id: time + } + + hours: time.hour + minutes: time.minute + + } + Clock { // this class is defined in QML (imports/TimeExample/Clock.qml) + id: clock2 + anchors.top: clock1.bottom + Time { // this class is defined in C++ (plugin.cpp) + id: time2 + } + + hours: time2.hour + minutes: time2.minute + + } + +} +//![0] diff --git a/examples/remoteobjects/cppclient/CMakeLists.txt b/examples/remoteobjects/cppclient/CMakeLists.txt new file mode 100644 index 0000000..a30e7a1 --- /dev/null +++ b/examples/remoteobjects/cppclient/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.16) +project(CppClient LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/cppclient") + +find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects) + +qt_add_executable(CppClient + main.cpp +) + +set_target_properties(CppClient PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE FALSE +) + +target_link_libraries(CppClient PUBLIC + Qt::Core + Qt::RemoteObjects +) + +qt6_add_repc_replicas(CppClient + timemodel.rep +) + +install(TARGETS CppClient + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/cppclient/cppclient.pro b/examples/remoteobjects/cppclient/cppclient.pro new file mode 100644 index 0000000..7b57442 --- /dev/null +++ b/examples/remoteobjects/cppclient/cppclient.pro @@ -0,0 +1,19 @@ +QT += core + +REPC_REPLICA += timemodel.rep +QT += remoteobjects + +QT -= gui + +TARGET = CppClient +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += main.cpp + +OTHER_FILES += \ + timemodel.rep + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/cppclient +INSTALLS += target diff --git a/examples/remoteobjects/cppclient/main.cpp b/examples/remoteobjects/cppclient/main.cpp new file mode 100644 index 0000000..1a13fd4 --- /dev/null +++ b/examples/remoteobjects/cppclient/main.cpp @@ -0,0 +1,52 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include "rep_timemodel_replica.h" + +class tester : public QObject +{ + Q_OBJECT +public: + tester() : QObject(nullptr) + { + QRemoteObjectNode m_client(QUrl(QStringLiteral("local:registry"))); + ptr1.reset(m_client.acquire< MinuteTimerReplica >()); + ptr2.reset(m_client.acquire< MinuteTimerReplica >()); + ptr3.reset(m_client.acquire< MinuteTimerReplica >()); + QTimer::singleShot(0, this, &tester::clear); + QTimer::singleShot(1, this, &tester::clear); + QTimer::singleShot(10000, this, &tester::clear); + QTimer::singleShot(11000, this, &tester::clear); + } +public slots: + void clear() + { + static int i = 0; + if (i == 0) { + i++; + ptr1.reset(); + } else if (i == 1) { + i++; + ptr2.reset(); + } else if (i == 2) { + i++; + ptr3.reset(); + } else { + qApp->quit(); + } + } + +private: + QScopedPointer ptr1, ptr2, ptr3; +}; + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + tester t; + return a.exec(); +} + +#include "main.moc" diff --git a/examples/remoteobjects/cppclient/timemodel.rep b/examples/remoteobjects/cppclient/timemodel.rep new file mode 100644 index 0000000..cbfaf24 --- /dev/null +++ b/examples/remoteobjects/cppclient/timemodel.rep @@ -0,0 +1,12 @@ +#include + +POD PresetInfo(int presetNumber, float frequency, QString stationName) +class MinuteTimer +{ + PROP(int hour=1); + PROP(int minute=51); + SIGNAL(timeChanged()); + SIGNAL(timeChanged2(QTime t)); + SIGNAL(sendCustom(PresetInfo info)); + SLOT(void SetTimeZone(int zn)); +}; diff --git a/examples/remoteobjects/modelviewclient/CMakeLists.txt b/examples/remoteobjects/modelviewclient/CMakeLists.txt new file mode 100644 index 0000000..03c5591 --- /dev/null +++ b/examples/remoteobjects/modelviewclient/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.16) +project(modelviewclient LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/modelviewclient") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui RemoteObjects Widgets) + +qt_add_executable(modelviewclient + main.cpp +) + +set_target_properties(modelviewclient PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE FALSE +) + +target_link_libraries(modelviewclient PUBLIC + Qt::Core + Qt::Gui + Qt::RemoteObjects + Qt::Widgets +) + +install(TARGETS modelviewclient + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/modelviewclient/doc/src/modelviewclient.qdoc b/examples/remoteobjects/modelviewclient/doc/src/modelviewclient.qdoc new file mode 100644 index 0000000..ae02d3a --- /dev/null +++ b/examples/remoteobjects/modelviewclient/doc/src/modelviewclient.qdoc @@ -0,0 +1,32 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example modelviewclient + \title Model-View Client + + This is the client-side application that accompanies the + \l {Model-View Server}. + + This example showcases how to make a very simple client program which + displays the content and changes made on a server. + + \snippet modelviewclient/main.cpp ObjectNode creation + + We start by creating a QRemoteObjectNode and connecting it to a registry + found on the local machine. We also set a + \l{QRemoteObjectNode::heartbeatInterval}{heartbeat interval}. + The heartbeat is useful to detect if the connection to the \l{Source} has + been disrupted. In this case, since all the traffic is local, it would + detect when the server has been closed. + + \snippet modelviewclient/main.cpp Model acquisition + + We then \l {QRemoteObjectNode::acquireModel}{acquire} the model which + contains all of our data. In this case, we're looking to acquire a model + named \c{RemoteModel} from the remote object network we are connected to. + + \snippet modelviewclient/main.cpp QTreeView-creation + + And finally, we display the model in a very basic application. +*/ diff --git a/examples/remoteobjects/modelviewclient/main.cpp b/examples/remoteobjects/modelviewclient/main.cpp new file mode 100644 index 0000000..880b8a8 --- /dev/null +++ b/examples/remoteobjects/modelviewclient/main.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + + QLoggingCategory::setFilterRules("qt.remoteobjects.debug=false\n" + "qt.remoteobjects.warning=false\n" + "qt.remoteobjects.models.debug=false\n" + "qt.remoteobjects.models.debug=false"); + + QApplication app(argc, argv); + +//! [ObjectNode creation] + QRemoteObjectNode node(QUrl(QStringLiteral("local:registry"))); + node.setHeartbeatInterval(1000); +//! [ObjectNode creation] +//! [Model acquisition] + QScopedPointer model(node.acquireModel(QStringLiteral("RemoteModel"))); +//! [Model acquisition] + +//! [QTreeView-creation] + QTreeView view; + view.setWindowTitle(QStringLiteral("RemoteView")); + view.resize(640,480); + view.setModel(model.data()); + view.show(); +//! [QTreeView-creation] + + return app.exec(); +} diff --git a/examples/remoteobjects/modelviewclient/modelviewclient.pro b/examples/remoteobjects/modelviewclient/modelviewclient.pro new file mode 100644 index 0000000..473f38f --- /dev/null +++ b/examples/remoteobjects/modelviewclient/modelviewclient.pro @@ -0,0 +1,10 @@ +SOURCES = main.cpp + +CONFIG -= app_bundle + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/modelviewclient +INSTALLS += target + +QT += widgets remoteobjects +requires(qtConfig(treeview)) diff --git a/examples/remoteobjects/modelviewserver/CMakeLists.txt b/examples/remoteobjects/modelviewserver/CMakeLists.txt new file mode 100644 index 0000000..c194743 --- /dev/null +++ b/examples/remoteobjects/modelviewserver/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.16) +project(modelviewserver LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/modelviewserver") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui RemoteObjects Widgets) + +qt_add_executable(modelviewserver + main.cpp +) + +set_target_properties(modelviewserver PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(modelviewserver PUBLIC + Qt::Core + Qt::Gui + Qt::RemoteObjects + Qt::Widgets +) + +install(TARGETS modelviewserver + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/modelviewserver/doc/src/modelviewserver.qdoc b/examples/remoteobjects/modelviewserver/doc/src/modelviewserver.qdoc new file mode 100644 index 0000000..a2d6a56 --- /dev/null +++ b/examples/remoteobjects/modelviewserver/doc/src/modelviewserver.qdoc @@ -0,0 +1,47 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example modelviewserver + \title Model-View Server + + This is the server-side application that accompanies the + \l {Model-View Client}. + + This example showcases how to make a simple server program that displays + and makes changes to a QTreeView which is made available on a Remote + Objects network. + + \snippet modelviewserver/main.cpp RegistryHost setup + + We start by creating a QRemoteObjectRegistryHost with which other + Remote Objects will connect, be registered and then be advertised by. + The model we create can then be easily acquired from the client side by just + connecting to the registry. + + \snippet modelviewserver/main.cpp Model-creation and role-selection + + Now we have to create the model we need. The exact implementation is + available in the source code, to which you can navigate by pressing the + link further down on this page. We also define which \e roles we want to + expose to the \l{Replica} on the client side. + + \snippet modelviewserver/main.cpp Model-remoting + + Here, we create the QRemoteObjectHost that connects to, and shares all its + Remote Objects with, the Registry we created earlier. We then start remoting + the model we just created with the name \c{RemoteModel}. We also pass the + \e roles argument here. + + \snippet modelviewserver/main.cpp TreeView-creation + + We then display the model with a QTreeView widget. + + \snippet modelviewserver/main.cpp Automated actions + + For the sake of keeping the example light-weight it performs some + automated actions to affect the model shortly after the server application + launches. These changes can then be seen on both the server and client side. + You can also change the text in the fields on the server side and see it + update on the client side. +*/ diff --git a/examples/remoteobjects/modelviewserver/main.cpp b/examples/remoteobjects/modelviewserver/main.cpp new file mode 100644 index 0000000..47ba004 --- /dev/null +++ b/examples/remoteobjects/modelviewserver/main.cpp @@ -0,0 +1,140 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#include +#include +#include +#include +#include +#include + +#include + +struct TimerHandler : public QObject +{ + Q_OBJECT +public: + QStandardItemModel *model; +public Q_SLOTS: + void changeData() { + Q_ASSERT(model); + Q_ASSERT(model->rowCount() > 50); + Q_ASSERT(model->columnCount() > 1); + for (int i = 10; i < 50; ++i) + model->setData(model->index(i, 1), QColor(Qt::blue), Qt::BackgroundRole); + } + void insertData() { + Q_ASSERT(model); + Q_ASSERT(model->rowCount() > 50); + Q_ASSERT(model->columnCount() > 1); + model->insertRows(2, 9); + for (int i = 2; i < 11; ++i) { + model->setData(model->index(i, 1), QColor(Qt::green), Qt::BackgroundRole); + model->setData(model->index(i, 1), QLatin1String("InsertedRow"), Qt::DisplayRole); + } + } + void removeData() { + model->removeRows(2, 4); + } + + void changeFlags() { + QStandardItem *item = model->item(0, 0); + item->setEnabled(false); + item = item->child(0, 0); + item->setFlags(item->flags() & Qt::ItemIsSelectable); + } + + void moveData() { + model->moveRows(QModelIndex(), 2, 4, QModelIndex(), 10); + } +}; + +QList addChild(int numChildren, int nestingLevel) +{ + QList result; + if (nestingLevel == 0) + return result; + for (int i = 0; i < numChildren; ++i) { + QStandardItem *child = new QStandardItem(QStringLiteral("Child num %1, nesting Level %2").arg(i+1).arg(nestingLevel)); + if (i == 0) + child->appendRow(addChild(numChildren, nestingLevel -1)); + result.push_back(child); + } + return result; +} + +std::unique_ptr createModel() +{ + std::unique_ptr sourceModel = std::make_unique(); + const int modelSize = 100000; + QStringList list; + QStringList hHeaderList; + hHeaderList << QStringLiteral("First Column with spacing") << QStringLiteral("Second Column with spacing"); + sourceModel->setHorizontalHeaderLabels(hHeaderList); + list.reserve(modelSize); + for (int i = 0; i < modelSize; ++i) { + QStandardItem *firstItem = new QStandardItem(QStringLiteral("FancyTextNumber %1").arg(i)); + if (i == 0) + firstItem->appendRow(addChild(2, 2)); + QStandardItem *secondItem = new QStandardItem(QStringLiteral("FancyRow2TextNumber %1").arg(i)); + if (i % 2 == 0) + firstItem->setBackground(Qt::red); + QList row; + row << firstItem << secondItem; + sourceModel->invisibleRootItem()->appendRow(row); + list << QStringLiteral("FancyTextNumber %1").arg(i); + } + + // Needed by QMLModelViewClient + QHash roleNames = { + {Qt::DisplayRole, "_text"}, + {Qt::BackgroundRole, "_color"} + }; + sourceModel->setItemRoleNames(roleNames); + return sourceModel; +} + +int main(int argc, char *argv[]) +{ + QLoggingCategory::setFilterRules("qt.remoteobjects.debug=false\n" + "qt.remoteobjects.warning=false"); + QApplication app(argc, argv); + + qDebug() << "Creating registry host"; +//! [RegistryHost setup] + QRemoteObjectRegistryHost node(QUrl(QStringLiteral("local:registry"))); +//! [RegistryHost setup] + +//! [Model-creation and role-selection] + std::unique_ptr sourceModel = createModel(); + + QList roles; + roles << Qt::DisplayRole << Qt::BackgroundRole; +//! [Model-creation and role-selection] + +//! [Model-remoting] + QRemoteObjectHost node2(QUrl(QStringLiteral("local:replica")), QUrl(QStringLiteral("local:registry"))); + node2.enableRemoting(sourceModel.get(), QStringLiteral("RemoteModel"), roles); +//! [Model-remoting] + +//! [TreeView-creation] + QTreeView view; + view.setWindowTitle(QStringLiteral("SourceView")); + view.setModel(sourceModel.get()); + view.show(); +//! [TreeView-creation] + +//! [Automated actions] + TimerHandler handler; + handler.model = sourceModel.get(); + QTimer::singleShot(5000, &handler, &TimerHandler::changeData); + QTimer::singleShot(10000, &handler, &TimerHandler::insertData); + QTimer::singleShot(11000, &handler, &TimerHandler::changeFlags); + QTimer::singleShot(12000, &handler, &TimerHandler::removeData); + QTimer::singleShot(13000, &handler, &TimerHandler::moveData); +//! [Automated actions] + + + return app.exec(); +} + +#include "main.moc" diff --git a/examples/remoteobjects/modelviewserver/modelviewserver.pro b/examples/remoteobjects/modelviewserver/modelviewserver.pro new file mode 100644 index 0000000..d2fc6c4 --- /dev/null +++ b/examples/remoteobjects/modelviewserver/modelviewserver.pro @@ -0,0 +1,11 @@ +QT += widgets remoteobjects +requires(qtConfig(treeview)) + +TEMPLATE = app + +CONFIG += c++11 + +SOURCES += main.cpp + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/modelviewserver +INSTALLS += target diff --git a/examples/remoteobjects/plugins/CMakeLists.txt b/examples/remoteobjects/plugins/CMakeLists.txt new file mode 100644 index 0000000..848b93f --- /dev/null +++ b/examples/remoteobjects/plugins/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.16) +project(qmlqrotimeexampleplugin LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/plugins/imports/TimeExample") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml RemoteObjects) + +qt6_add_qml_module(qmlqrotimeexampleplugin + OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/imports/TimeExample" + VERSION 1.0 + URI "TimeExample" +) + +target_sources(qmlqrotimeexampleplugin PRIVATE + plugin.cpp +) + +set_target_properties(qmlqrotimeexampleplugin PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(qmlqrotimeexampleplugin PUBLIC + Qt::Core + Qt::Gui + Qt::Qml + Qt::RemoteObjects +) + +qt6_add_repc_replicas(qmlqrotimeexampleplugin + ../timemodel.rep +) + +install(TARGETS qmlqrotimeexampleplugin + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/plugins/imports/TimeExample/Clock.qml b/examples/remoteobjects/plugins/imports/TimeExample/Clock.qml new file mode 100644 index 0000000..b2bf598 --- /dev/null +++ b/examples/remoteobjects/plugins/imports/TimeExample/Clock.qml @@ -0,0 +1,51 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick 2.0 + +Rectangle { + id: clock + width: 200; height: 200; color: "gray" + + property alias city: cityLabel.text + property variant hours + property variant minutes + property variant shift : 0 + + Image { id: background; source: "clock.png" } + + Image { + x: 92.5; y: 27 + source: "hour.png" + transform: Rotation { + id: hourRotation + origin.x: 7.5; origin.y: 73; + angle: (clock.hours * 30) + (clock.minutes * 0.5) + Behavior on angle { + SpringAnimation{ spring: 2; damping: 0.2; modulus: 360 } + } + } + } + + Image { + x: 93.5; y: 17 + source: "minute.png" + transform: Rotation { + id: minuteRotation + origin.x: 6.5; origin.y: 83; + angle: clock.minutes * 6 + Behavior on angle { + SpringAnimation{ spring: 2; damping: 0.2; modulus: 360 } + } + } + } + + Image { + anchors.centerIn: background; source: "center.png" + } + + Text { + id: cityLabel; font.bold: true; font.pixelSize: 14; y:200; color: "white" + anchors.horizontalCenter: parent.horizontalCenter + } +} diff --git a/examples/remoteobjects/plugins/imports/TimeExample/center.png b/examples/remoteobjects/plugins/imports/TimeExample/center.png new file mode 100644 index 0000000..7fbd802 Binary files /dev/null and b/examples/remoteobjects/plugins/imports/TimeExample/center.png differ diff --git a/examples/remoteobjects/plugins/imports/TimeExample/clock.png b/examples/remoteobjects/plugins/imports/TimeExample/clock.png new file mode 100644 index 0000000..462edac Binary files /dev/null and b/examples/remoteobjects/plugins/imports/TimeExample/clock.png differ diff --git a/examples/remoteobjects/plugins/imports/TimeExample/hour.png b/examples/remoteobjects/plugins/imports/TimeExample/hour.png new file mode 100644 index 0000000..f8061a1 Binary files /dev/null and b/examples/remoteobjects/plugins/imports/TimeExample/hour.png differ diff --git a/examples/remoteobjects/plugins/imports/TimeExample/minute.png b/examples/remoteobjects/plugins/imports/TimeExample/minute.png new file mode 100644 index 0000000..1297ec7 Binary files /dev/null and b/examples/remoteobjects/plugins/imports/TimeExample/minute.png differ diff --git a/examples/remoteobjects/plugins/imports/TimeExample/qmldir b/examples/remoteobjects/plugins/imports/TimeExample/qmldir new file mode 100644 index 0000000..be259d4 --- /dev/null +++ b/examples/remoteobjects/plugins/imports/TimeExample/qmldir @@ -0,0 +1,3 @@ +module TimeExample +Clock 1.0 Clock.qml +plugin qmlqrotimeexampleplugin diff --git a/examples/remoteobjects/plugins/plugin.cpp b/examples/remoteobjects/plugins/plugin.cpp new file mode 100644 index 0000000..0232c5c --- /dev/null +++ b/examples/remoteobjects/plugins/plugin.cpp @@ -0,0 +1,92 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rep_timemodel_replica.h" + +// Implements a "TimeModel" class with hour and minute properties +// that change on-the-minute yet efficiently sleep the rest +// of the time. + +static QRemoteObjectNode m_client; + +class TimeModel : public QObject +{ + Q_OBJECT + Q_PROPERTY(int hour READ hour NOTIFY timeChanged) + Q_PROPERTY(int minute READ minute NOTIFY timeChanged) + Q_PROPERTY(bool isValid READ isValid NOTIFY isValidChanged) + +public: + TimeModel(QObject *parent = nullptr) : QObject(parent), d_ptr(nullptr) + { + d_ptr.reset(m_client.acquire< MinuteTimerReplica >()); + connect(d_ptr.data(), &MinuteTimerReplica::hourChanged, this, &TimeModel::timeChanged); + connect(d_ptr.data(), &MinuteTimerReplica::minuteChanged, this, &TimeModel::timeChanged); + connect(d_ptr.data(), &MinuteTimerReplica::timeChanged, this, &TimeModel::timeChanged); + connect(d_ptr.data(), &MinuteTimerReplica::timeChanged2, this, &TimeModel::test); + connect(d_ptr.data(), &MinuteTimerReplica::sendCustom, this, &TimeModel::testCustom); + } + + ~TimeModel() override + { + } + + int minute() const { return d_ptr->minute(); } + int hour() const { return d_ptr->hour(); } + bool isValid() const { return d_ptr->state() == QRemoteObjectReplica::Valid; } + +public slots: + //Test a signal with parameters + void test(QTime t) + { + qDebug()<<"Test"<SetTimeZone(t.minute()); + } + //Test a signal with a custom type + void testCustom(PresetInfo info) + { + qDebug()<<"testCustom"< d_ptr; +}; + +class QExampleQmlPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void initializeEngine(QQmlEngine *engine, const char *uri) override + { + Q_UNUSED(engine) + Q_UNUSED(uri) + Q_ASSERT(uri == QLatin1String("TimeExample")); + engine->addImportPath(QStringLiteral("qrc:/qml")); + m_client.setRegistryUrl(QUrl(QStringLiteral("local:registry"))); + } + void registerTypes(const char *uri) override + { + Q_ASSERT(uri == QLatin1String("TimeExample")); + qmlRegisterType(uri, 1, 0, "Time"); + } + +}; + +#include "plugin.moc" diff --git a/examples/remoteobjects/plugins/plugins.pro b/examples/remoteobjects/plugins/plugins.pro new file mode 100644 index 0000000..9d3f3d1 --- /dev/null +++ b/examples/remoteobjects/plugins/plugins.pro @@ -0,0 +1,30 @@ +QT += qml remoteobjects + +TEMPLATE = lib +CONFIG += plugin + +REPC_REPLICA += $$PWD/../timemodel.rep + +DESTDIR = imports/TimeExample +TARGET = qmlqrotimeexampleplugin + +SOURCES += plugin.cpp + +pluginfiles.files += \ + imports/TimeExample/qmldir \ + imports/TimeExample/center.png \ + imports/TimeExample/clock.png \ + imports/TimeExample/Clock.qml \ + imports/TimeExample/hour.png \ + imports/TimeExample/minute.png + +qml.files = plugins.qml +qml.path += $$[QT_INSTALL_EXAMPLES]/remoteobjects/plugins +target.path += $$[QT_INSTALL_EXAMPLES]/remoteobjects/plugins/imports/TimeExample +pluginfiles.path += $$[QT_INSTALL_EXAMPLES]/remoteobjects/plugins/imports/TimeExample + +INSTALLS += target qml pluginfiles + +CONFIG += install_ok # Do not cargo-cult this! + +contains(QT_CONFIG, c++11): CONFIG += c++11 diff --git a/examples/remoteobjects/plugins/plugins.qml b/examples/remoteobjects/plugins/plugins.qml new file mode 100644 index 0000000..e631ee8 --- /dev/null +++ b/examples/remoteobjects/plugins/plugins.qml @@ -0,0 +1,24 @@ +// Copyright (C) 2014 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick 2.0 + +Item { + width: 200 + height: 400 + property int counter: 0; + MouseArea { + anchors.fill: parent + onClicked: + { + counter = (counter + 1) % 3; + console.log(counter); + pageLoader.source = "plugins"+counter+".qml" + } + } + Loader { + id: pageLoader + source: "plugins0.qml" + } +} +//![0] diff --git a/examples/remoteobjects/plugins/plugins0.qml b/examples/remoteobjects/plugins/plugins0.qml new file mode 100644 index 0000000..aa5ed68 --- /dev/null +++ b/examples/remoteobjects/plugins/plugins0.qml @@ -0,0 +1,11 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +//![0] +import QtQuick 2.0 + +Rectangle { + width: 200 + height: 400 + color: "blue" +} +//![0] diff --git a/examples/remoteobjects/plugins/plugins1.qml b/examples/remoteobjects/plugins/plugins1.qml new file mode 100644 index 0000000..ba05d61 --- /dev/null +++ b/examples/remoteobjects/plugins/plugins1.qml @@ -0,0 +1,23 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +//![0] +import QtQuick 2.0 +import TimeExample 1.0 // import types from the plugin + +Rectangle { + width: 200 + height: 400 + color: "blue" + Clock { // this class is defined in QML (imports/TimeExample/Clock.qml) + id: clock1 + anchors.top: parent.top + Time { // this class is defined in C++ (plugin.cpp) + id: time + } + + hours: time.hour + minutes: time.minute + + } +} +//![0] diff --git a/examples/remoteobjects/plugins/plugins2.qml b/examples/remoteobjects/plugins/plugins2.qml new file mode 100644 index 0000000..576f3f8 --- /dev/null +++ b/examples/remoteobjects/plugins/plugins2.qml @@ -0,0 +1,34 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +//![0] +import QtQuick 2.0 +import TimeExample 1.0 // import types from the plugin + +Rectangle { + width: 200 + height: 400 + Clock { // this class is defined in QML (imports/TimeExample/Clock.qml) + id: clock1 + anchors.top: parent.top + Time { // this class is defined in C++ (plugin.cpp) + id: time + } + + hours: time.hour + minutes: time.minute + + } + Clock { // this class is defined in QML (imports/TimeExample/Clock.qml) + id: clock2 + anchors.top: clock1.bottom + Time { // this class is defined in C++ (plugin.cpp) + id: time2 + } + + hours: time2.hour + minutes: time2.minute + + } + +} +//![0] diff --git a/examples/remoteobjects/qmlmodelviewclient/CMakeLists.txt b/examples/remoteobjects/qmlmodelviewclient/CMakeLists.txt new file mode 100644 index 0000000..6d3aafe --- /dev/null +++ b/examples/remoteobjects/qmlmodelviewclient/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.16) +project(qmlmodelviewclient LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/qmlmodelviewclient") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick RemoteObjects) + +qt_add_executable(qmlmodelviewclient + main.cpp +) + +set_target_properties(qmlmodelviewclient PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(qmlmodelviewclient PUBLIC + Qt::Core + Qt::Gui + Qt::Qml + Qt::Quick + Qt::RemoteObjects +) + +# Resources: +set(qml_resource_files + "main.qml" +) + +qt6_add_resources(qmlmodelviewclient "qml" + PREFIX + "/" + FILES + ${qml_resource_files} +) + +install(TARGETS qmlmodelviewclient + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/qmlmodelviewclient/doc/src/qmlmodelviewclient.qdoc b/examples/remoteobjects/qmlmodelviewclient/doc/src/qmlmodelviewclient.qdoc new file mode 100644 index 0000000..c21582c --- /dev/null +++ b/examples/remoteobjects/qmlmodelviewclient/doc/src/qmlmodelviewclient.qdoc @@ -0,0 +1,14 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example qmlmodelviewclient + \title QML Model-View Client + + This is an alternate client application you can use along with the + \l {Model-View Server}. + + This short example showcases how to quickly make a QAbstractItemModelReplica + available to and use from QML. See the \l {Model-View Client} example for a + more detailed explanation on how the model is acquired and used. +*/ diff --git a/examples/remoteobjects/qmlmodelviewclient/main.cpp b/examples/remoteobjects/qmlmodelviewclient/main.cpp new file mode 100644 index 0000000..4992022 --- /dev/null +++ b/examples/remoteobjects/qmlmodelviewclient/main.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QLoggingCategory::setFilterRules("qt.remoteobjects.debug=false\n" + "qt.remoteobjects.warning=false\n" + "qt.remoteobjects.models.debug=false\n" + "qt.remoteobjects.models.debug=false"); + + QGuiApplication app(argc, argv); + + QRemoteObjectNode node(QUrl(QStringLiteral("local:registry"))); + QQmlApplicationEngine engine; + QScopedPointer model(node.acquireModel(QStringLiteral("RemoteModel"))); + engine.rootContext()->setContextProperty("remoteModel", model.data()); + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + + return app.exec(); +} diff --git a/examples/remoteobjects/qmlmodelviewclient/main.qml b/examples/remoteobjects/qmlmodelviewclient/main.qml new file mode 100644 index 0000000..c72eb6c --- /dev/null +++ b/examples/remoteobjects/qmlmodelviewclient/main.qml @@ -0,0 +1,58 @@ +// Copyright (C) 2016 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick 2.5 +import QtQuick.Window 2.2 + +Window { + visible: true + width: 800 + height: 600 + Text { + id: dummy + } + + ListView { + id: view + anchors.fill: parent + focus: true + + currentIndex: 10 + + model: remoteModel + + snapMode: ListView.SnapToItem + highlightFollowsCurrentItem: true + highlightMoveDuration: 0 + + delegate: Rectangle { + width: view.width + height: dummy.font.pixelSize * 2 + color: _color + Text { + anchors.centerIn: parent + text: _text + } + } + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Home: + view.currentIndex = 0; + break; + case Qt.Key_PageUp: + currentIndex -= Math.random() * 300; + if (currentIndex < 0) + currentIndex = 0; + break; + case Qt.Key_PageDown: + currentIndex += Math.random() * 300; + if (currentIndex >= count) + currentIndex = count - 1; + break; + case Qt.Key_End: + currentIndex = count - 1; + break; + } + } + } +} diff --git a/examples/remoteobjects/qmlmodelviewclient/qml.qrc b/examples/remoteobjects/qmlmodelviewclient/qml.qrc new file mode 100644 index 0000000..5f6483a --- /dev/null +++ b/examples/remoteobjects/qmlmodelviewclient/qml.qrc @@ -0,0 +1,5 @@ + + + main.qml + + diff --git a/examples/remoteobjects/qmlmodelviewclient/qmlmodelviewclient.pro b/examples/remoteobjects/qmlmodelviewclient/qmlmodelviewclient.pro new file mode 100644 index 0000000..4e9c12d --- /dev/null +++ b/examples/remoteobjects/qmlmodelviewclient/qmlmodelviewclient.pro @@ -0,0 +1,11 @@ +TEMPLATE = app + +QT += qml quick remoteobjects +CONFIG += c++11 + +SOURCES += main.cpp + +RESOURCES += qml.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/qmlmodelviewclient +INSTALLS += target diff --git a/examples/remoteobjects/remoteobjects.pro b/examples/remoteobjects/remoteobjects.pro new file mode 100644 index 0000000..096d88b --- /dev/null +++ b/examples/remoteobjects/remoteobjects.pro @@ -0,0 +1,23 @@ +TEMPLATE = subdirs +CONFIG += debug_and_release ordered +SUBDIRS = \ + remoteobjects_server \ + cppclient \ + simpleswitch \ + websockets + +qtHaveModule(widgets) { + SUBDIRS += \ + modelviewclient \ + modelviewserver +} + +contains(QT_CONFIG, ssl): SUBDIRS += ssl + +qtHaveModule(quick) { + SUBDIRS += \ + plugins \ + clientapp + + unix:!android: SUBDIRS += qmlmodelviewclient +} diff --git a/examples/remoteobjects/remoteobjects_server/CMakeLists.txt b/examples/remoteobjects/remoteobjects_server/CMakeLists.txt new file mode 100644 index 0000000..21ce955 --- /dev/null +++ b/examples/remoteobjects/remoteobjects_server/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.16) +project(remoteobjects_server LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/remoteobjects_server") + +find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects) + +qt_add_executable(remoteobjects_server + main.cpp + timemodel.cpp timemodel.h +) + +set_target_properties(remoteobjects_server PROPERTIES + WIN32_EXECUTABLE FALSE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(remoteobjects_server PUBLIC + Qt::Core + Qt::RemoteObjects +) + +qt6_add_repc_sources(remoteobjects_server + ../timemodel.rep +) + +install(TARGETS remoteobjects_server + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/remoteobjects_server/main.cpp b/examples/remoteobjects/remoteobjects_server/main.cpp new file mode 100644 index 0000000..952b799 --- /dev/null +++ b/examples/remoteobjects/remoteobjects_server/main.cpp @@ -0,0 +1,57 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "timemodel.h" + +#include +/* +* http://stackoverflow.com/questions/7404163/windows-handling-ctrlc-in-different-thread +*/ + +void SigIntHandler() +{ + qDebug()<<"Ctrl-C received. Quitting."; + qApp->quit(); +} + +#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_QNX) + #include + + void unix_handler(int s) + { + if (s==SIGINT) + SigIntHandler(); + } + +#elif defined(Q_OS_WIN32) + #include + + BOOL WINAPI WinHandler(DWORD CEvent) + { + switch (CEvent) + { + case CTRL_C_EVENT: + SigIntHandler(); + break; + } + return TRUE; + } +#endif + +int main(int argc, char *argv[]) +{ + 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(QStringLiteral("local:replica")),QUrl(QStringLiteral("local:registry"))); + QRemoteObjectRegistryHost node2(QUrl(QStringLiteral("local:registry"))); + MinuteTimer timer; + node2.enableRemoting(&timer); + + Q_UNUSED(timer) + return app.exec(); +} diff --git a/examples/remoteobjects/remoteobjects_server/remoteobjects_server.pro b/examples/remoteobjects/remoteobjects_server/remoteobjects_server.pro new file mode 100644 index 0000000..1c1441d --- /dev/null +++ b/examples/remoteobjects/remoteobjects_server/remoteobjects_server.pro @@ -0,0 +1,13 @@ +CONFIG += console + + +REPC_SOURCE += ../timemodel.rep +QT = remoteobjects core + +SOURCES += timemodel.cpp main.cpp +HEADERS += timemodel.h + +contains(QT_CONFIG, c++11): CONFIG += c++11 + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/remoteobjects_server +INSTALLS += target diff --git a/examples/remoteobjects/remoteobjects_server/timemodel.cpp b/examples/remoteobjects/remoteobjects_server/timemodel.cpp new file mode 100644 index 0000000..91fc42c --- /dev/null +++ b/examples/remoteobjects/remoteobjects_server/timemodel.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "timemodel.h" + +MinuteTimer::MinuteTimer(QObject *parent) : MinuteTimerSimpleSource(parent), zone(0) +{ + time = QTime::currentTime(); + setHour(time.hour()); + setMinute(time.minute()); + timer.start(60000-time.second()*1000, this); +} +MinuteTimer::~MinuteTimer() +{ + timer.stop(); +} +void MinuteTimer::timerEvent(QTimerEvent *) +{ + QTime now = QTime::currentTime(); + if (now.second() == 59 && now.minute() == time.minute() && now.hour() == time.hour()) { + // just missed time tick over, force it, wait extra 0.5 seconds + time = time.addSecs(60); + timer.start(60500, this); + } else { + time = now; + timer.start(60000-time.second()*1000, this); + } + qDebug()<<"Time"< +#include "rep_timemodel_source.h" + +class MinuteTimer : public MinuteTimerSimpleSource +{ + Q_OBJECT +public: + MinuteTimer(QObject *parent = nullptr); + ~MinuteTimer() override; + +public slots: + void SetTimeZone(const int &zn) override; + +protected: + void timerEvent(QTimerEvent *) override; + +private: + QTime time; + QBasicTimer timer; + int zone; +}; diff --git a/examples/remoteobjects/simpleswitch/CMakeLists.txt b/examples/remoteobjects/simpleswitch/CMakeLists.txt new file mode 100644 index 0000000..d827823 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/CMakeLists.txt @@ -0,0 +1,5 @@ +qt_internal_add_example(directconnectclient) +qt_internal_add_example(directconnectdynamicclient) +qt_internal_add_example(directconnectserver) +qt_internal_add_example(registryconnectedclient) +qt_internal_add_example(registryconnectedserver) diff --git a/examples/remoteobjects/simpleswitch/directconnectclient/CMakeLists.txt b/examples/remoteobjects/simpleswitch/directconnectclient/CMakeLists.txt new file mode 100644 index 0000000..8159efb --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectclient/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.16) +project(directconnectclient LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/simpleswitch/directconnectclient") + +find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects) + +qt_add_executable(directconnectclient + client.cpp client.h + main.cpp +) + +set_target_properties(directconnectclient PROPERTIES + WIN32_EXECUTABLE FALSE + MACOSX_BUNDLE FALSE +) + +target_link_libraries(directconnectclient PUBLIC + Qt::Core + Qt::RemoteObjects +) + +qt6_add_repc_replicas(directconnectclient + simpleswitch.rep +) + +install(TARGETS directconnectclient + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/simpleswitch/directconnectclient/client.cpp b/examples/remoteobjects/simpleswitch/directconnectclient/client.cpp new file mode 100644 index 0000000..052db42 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectclient/client.cpp @@ -0,0 +1,38 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "client.h" + +// constructor +Client::Client(QSharedPointer ptr) : + QObject(nullptr), clientSwitchState(false), reptr(ptr) +{ + //connect signal for replica initialized with initialization slot + initConnections(); + // We can connect to SimpleSwitchReplica signals/slots + //directly because our Replica was generated by repc +} + +//destructor +Client::~Client() +{ + +} + + +void Client::initConnections(void) +{ + // initialize connections between signals and slots + + // connect source replica signal currStateChanged() with client's recSwitchState() slot to receive source's current state + QObject::connect(reptr.data(), &SimpleSwitchReplica::currStateChanged, this, &Client::recSwitchState_slot); + // connect client's echoSwitchState(..) signal with replica's server_slot(..) to echo back received state + QObject::connect(this, &Client::echoSwitchState, reptr.data(), &SimpleSwitchReplica::server_slot); +} + +void Client::recSwitchState_slot(bool value) +{ + qDebug() << "Received source state "<< value << reptr.data()->currState(); + clientSwitchState = reptr.data()->currState(); + Q_EMIT echoSwitchState(clientSwitchState); // Emit signal to echo received state back to server +} diff --git a/examples/remoteobjects/simpleswitch/directconnectclient/client.h b/examples/remoteobjects/simpleswitch/directconnectclient/client.h new file mode 100644 index 0000000..3e486f7 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectclient/client.h @@ -0,0 +1,31 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef _CLIENT_H +#define _CLIENT_H + +#include +#include + +#include "rep_simpleswitch_replica.h" + +class Client : public QObject +{ + Q_OBJECT +public: + Client(QSharedPointer ptr); + ~Client() override; + void initConnections();// function connect signals and slots of source and client + +Q_SIGNALS: + void echoSwitchState(bool switchState);// this signal is connected with server_slot(..) on the source object and echoes back switch state received from source + +public Q_SLOTS: + void recSwitchState_slot(bool); // slot to receive source state +private: + bool clientSwitchState; // holds received server switch state + QSharedPointer reptr;// holds reference to replica + + }; + +#endif diff --git a/examples/remoteobjects/simpleswitch/directconnectclient/directconnectclient.pro b/examples/remoteobjects/simpleswitch/directconnectclient/directconnectclient.pro new file mode 100644 index 0000000..3624821 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectclient/directconnectclient.pro @@ -0,0 +1,23 @@ +QT += remoteobjects core + +QT -= gui + +TARGET = directconnectclient +CONFIG += console +CONFIG -= app_bundle + +REPC_REPLICA = simpleswitch.rep + +TEMPLATE = app + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/simpleswitch/directconnectclient +INSTALLS += target + +SOURCES += main.cpp \ + client.cpp + +HEADERS += \ + client.h + +DISTFILES += \ + simpleswitch.rep diff --git a/examples/remoteobjects/simpleswitch/directconnectclient/main.cpp b/examples/remoteobjects/simpleswitch/directconnectclient/main.cpp new file mode 100644 index 0000000..7ff8e8a --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectclient/main.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include "client.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + + QSharedPointer ptr; + + QRemoteObjectNode repNode; // create remote object node + repNode.connectToNode(QUrl(QStringLiteral("local:replica"))); // connect with remote host node + + ptr.reset(repNode.acquire()); // acquire replica of source from host node + + Client rswitch(ptr); // create client switch object and pass reference of replica to it + + return a.exec(); +} diff --git a/examples/remoteobjects/simpleswitch/directconnectclient/simpleswitch.rep b/examples/remoteobjects/simpleswitch/directconnectclient/simpleswitch.rep new file mode 100644 index 0000000..b2d7cd2 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectclient/simpleswitch.rep @@ -0,0 +1,7 @@ +#include + +class SimpleSwitch +{ + PROP(bool currState=false); + SLOT(void server_slot(bool clientState)); +}; diff --git a/examples/remoteobjects/simpleswitch/directconnectdynamicclient/CMakeLists.txt b/examples/remoteobjects/simpleswitch/directconnectdynamicclient/CMakeLists.txt new file mode 100644 index 0000000..3941f8d --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectdynamicclient/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.16) +project(directconnectdynamicclient LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/simpleswitch/directconnectdynamicclient") + +find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects) + +qt_add_executable(directconnectdynamicclient + dynamicclient.cpp dynamicclient.h + main.cpp +) + +set_target_properties(directconnectdynamicclient PROPERTIES + WIN32_EXECUTABLE FALSE + MACOSX_BUNDLE FALSE +) + +target_link_libraries(directconnectdynamicclient PUBLIC + Qt::Core + Qt::RemoteObjects +) + +install(TARGETS directconnectdynamicclient + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/simpleswitch/directconnectdynamicclient/directconnectdynamicclient.pro b/examples/remoteobjects/simpleswitch/directconnectdynamicclient/directconnectdynamicclient.pro new file mode 100644 index 0000000..8b907de --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectdynamicclient/directconnectdynamicclient.pro @@ -0,0 +1,18 @@ +QT += remoteobjects core + +QT -= gui + +TARGET = directconnectdynamicclient +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/simpleswitch/directconnectdynamicclient +INSTALLS += target + +SOURCES += main.cpp \ + dynamicclient.cpp + +HEADERS += \ + dynamicclient.h diff --git a/examples/remoteobjects/simpleswitch/directconnectdynamicclient/dynamicclient.cpp b/examples/remoteobjects/simpleswitch/directconnectdynamicclient/dynamicclient.cpp new file mode 100644 index 0000000..6f88fa6 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectdynamicclient/dynamicclient.cpp @@ -0,0 +1,39 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "dynamicclient.h" + #include + +// constructor +DynamicClient::DynamicClient(QSharedPointer ptr) : + QObject(nullptr), clientSwitchState(false), reptr(ptr) +{ + //connect signal for replica valid changed with signal slot initialization + QObject::connect(reptr.data(), &QRemoteObjectDynamicReplica::initialized, this, + &DynamicClient::initConnection_slot); +} + +//destructor +DynamicClient::~DynamicClient() +{ + +} + +// Function to initialize connections between slots and signals +void DynamicClient::initConnection_slot() +{ + + // connect source replica signal currStateChanged() with client's recSwitchState() slot to receive source's current state + QObject::connect(reptr.data(), SIGNAL(currStateChanged(bool)), this, SLOT(recSwitchState_slot(bool))); + // connect client's echoSwitchState(..) signal with replica's server_slot(..) to echo back received state + QObject::connect(this, SIGNAL(echoSwitchState(bool)),reptr.data(), SLOT(server_slot(bool))); +} + + +void DynamicClient::recSwitchState_slot(bool value) +{ + clientSwitchState = reptr->property("currState").toBool(); // use replica property to get "currState" from source + qDebug() << "Received source state " << value << clientSwitchState; + Q_EMIT echoSwitchState(clientSwitchState); // Emit signal to echo received state back to server +} + diff --git a/examples/remoteobjects/simpleswitch/directconnectdynamicclient/dynamicclient.h b/examples/remoteobjects/simpleswitch/directconnectdynamicclient/dynamicclient.h new file mode 100644 index 0000000..18a0548 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectdynamicclient/dynamicclient.h @@ -0,0 +1,33 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef _DYNAMICCLIENT_H +#define _DYNAMICCLIENT_H + +#include +#include + +#include +#include + +class DynamicClient : public QObject +{ + Q_OBJECT +public: + DynamicClient(QSharedPointer ptr); + ~DynamicClient() override; + +Q_SIGNALS: + void echoSwitchState(bool switchState);// this signal is connected with server_slot(..) slot of source object and echoes back switch state received from source + +public Q_SLOTS: + void recSwitchState_slot(bool); // slot to receive source state + void initConnection_slot(); + +private: + bool clientSwitchState; // holds received server switch state + QSharedPointer reptr;// holds reference to replica + }; + +#endif + diff --git a/examples/remoteobjects/simpleswitch/directconnectdynamicclient/main.cpp b/examples/remoteobjects/simpleswitch/directconnectdynamicclient/main.cpp new file mode 100644 index 0000000..50be9ab --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectdynamicclient/main.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include + +#include "dynamicclient.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + QSharedPointer ptr; // shared pointer to hold replica + + QRemoteObjectNode repNode; // create remote object node + repNode.connectToNode(QUrl(QStringLiteral("local:replica"))); // connect with remote host node + + ptr.reset(repNode.acquireDynamic("SimpleSwitch")); // acquire replica of source from host node + + DynamicClient rswitch(ptr); // create client switch object and pass replica reference to it + + return a.exec(); +} diff --git a/examples/remoteobjects/simpleswitch/directconnectserver/CMakeLists.txt b/examples/remoteobjects/simpleswitch/directconnectserver/CMakeLists.txt new file mode 100644 index 0000000..8405d75 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectserver/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.16) +project(directconnectserver LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/simpleswitch/directconnectserver") + +find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects) + +qt_add_executable(directconnectserver + main.cpp + simpleswitch.cpp simpleswitch.h +) + +set_target_properties(directconnectserver PROPERTIES + WIN32_EXECUTABLE FALSE + MACOSX_BUNDLE FALSE +) + +target_link_libraries(directconnectserver PUBLIC + Qt::Core + Qt::RemoteObjects +) + +qt6_add_repc_sources(directconnectserver + simpleswitch.rep +) + +install(TARGETS directconnectserver + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/simpleswitch/directconnectserver/directconnectserver.pro b/examples/remoteobjects/simpleswitch/directconnectserver/directconnectserver.pro new file mode 100644 index 0000000..bbc3d7e --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectserver/directconnectserver.pro @@ -0,0 +1,21 @@ +QT += remoteobjects core + +QT -= gui + +TARGET = directconnectserver +CONFIG += console +CONFIG -= app_bundle + +REPC_SOURCE = simpleswitch.rep + +TEMPLATE = app + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/simpleswitch/directconnectserver +INSTALLS += target + +SOURCES += main.cpp \ + simpleswitch.cpp + + +HEADERS += \ + simpleswitch.h diff --git a/examples/remoteobjects/simpleswitch/directconnectserver/main.cpp b/examples/remoteobjects/simpleswitch/directconnectserver/main.cpp new file mode 100644 index 0000000..bc1af59 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectserver/main.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include "simpleswitch.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + SimpleSwitch srcSwitch; // create simple switch + + QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:replica"))); // create host node without Registry + srcNode.enableRemoting(&srcSwitch); // enable remoting/Sharing + + return a.exec(); +} diff --git a/examples/remoteobjects/simpleswitch/directconnectserver/simpleswitch.cpp b/examples/remoteobjects/simpleswitch/directconnectserver/simpleswitch.cpp new file mode 100644 index 0000000..16fe6a3 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/directconnectserver/simpleswitch.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "simpleswitch.h" + +// constructor +SimpleSwitch::SimpleSwitch(QObject *parent) : SimpleSwitchSimpleSource(parent) +{ + stateChangeTimer = new QTimer(this); // Initialize timer + QObject::connect(stateChangeTimer, &QTimer::timeout, this, &SimpleSwitch::timeout_slot); // connect timeout() signal from stateChangeTimer to timeout_slot() of simpleSwitch + stateChangeTimer->start(2000); // Start timer and set timout to 2 seconds + qDebug() << "Source Node Started"; +} + +//destructor +SimpleSwitch::~SimpleSwitch() +{ + stateChangeTimer->stop(); +} + +void SimpleSwitch::server_slot(bool clientState) +{ + qDebug() << "Replica state is " << clientState; // print switch state echoed back by client +} + +void SimpleSwitch::timeout_slot(void) +{ + // slot called on timer timeout + if (currState()) // check if current state is true, currState() is defined in repc generated rep_simpleswitch_source.h + setCurrState(false); // set state to false + else + setCurrState(true); // set state to true + qDebug() << "Source State is "< + +class SimpleSwitch +{ + PROP(bool currState=false); + SLOT(void server_slot(bool clientState)); +}; diff --git a/examples/remoteobjects/simpleswitch/registryconnectedclient/CMakeLists.txt b/examples/remoteobjects/simpleswitch/registryconnectedclient/CMakeLists.txt new file mode 100644 index 0000000..432796f --- /dev/null +++ b/examples/remoteobjects/simpleswitch/registryconnectedclient/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.16) +project(registryconnectedclient LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/simpleswitch/registryconnectedclient") + +find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects) + +qt_add_executable(registryconnectedclient + dynamicclient.cpp dynamicclient.h + main.cpp +) + +set_target_properties(registryconnectedclient PROPERTIES + WIN32_EXECUTABLE FALSE + MACOSX_BUNDLE FALSE +) + +target_link_libraries(registryconnectedclient PUBLIC + Qt::Core + Qt::RemoteObjects +) + +install(TARGETS registryconnectedclient + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/simpleswitch/registryconnectedclient/dynamicclient.cpp b/examples/remoteobjects/simpleswitch/registryconnectedclient/dynamicclient.cpp new file mode 100644 index 0000000..6f88fa6 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/registryconnectedclient/dynamicclient.cpp @@ -0,0 +1,39 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "dynamicclient.h" + #include + +// constructor +DynamicClient::DynamicClient(QSharedPointer ptr) : + QObject(nullptr), clientSwitchState(false), reptr(ptr) +{ + //connect signal for replica valid changed with signal slot initialization + QObject::connect(reptr.data(), &QRemoteObjectDynamicReplica::initialized, this, + &DynamicClient::initConnection_slot); +} + +//destructor +DynamicClient::~DynamicClient() +{ + +} + +// Function to initialize connections between slots and signals +void DynamicClient::initConnection_slot() +{ + + // connect source replica signal currStateChanged() with client's recSwitchState() slot to receive source's current state + QObject::connect(reptr.data(), SIGNAL(currStateChanged(bool)), this, SLOT(recSwitchState_slot(bool))); + // connect client's echoSwitchState(..) signal with replica's server_slot(..) to echo back received state + QObject::connect(this, SIGNAL(echoSwitchState(bool)),reptr.data(), SLOT(server_slot(bool))); +} + + +void DynamicClient::recSwitchState_slot(bool value) +{ + clientSwitchState = reptr->property("currState").toBool(); // use replica property to get "currState" from source + qDebug() << "Received source state " << value << clientSwitchState; + Q_EMIT echoSwitchState(clientSwitchState); // Emit signal to echo received state back to server +} + diff --git a/examples/remoteobjects/simpleswitch/registryconnectedclient/dynamicclient.h b/examples/remoteobjects/simpleswitch/registryconnectedclient/dynamicclient.h new file mode 100644 index 0000000..18a0548 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/registryconnectedclient/dynamicclient.h @@ -0,0 +1,33 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef _DYNAMICCLIENT_H +#define _DYNAMICCLIENT_H + +#include +#include + +#include +#include + +class DynamicClient : public QObject +{ + Q_OBJECT +public: + DynamicClient(QSharedPointer ptr); + ~DynamicClient() override; + +Q_SIGNALS: + void echoSwitchState(bool switchState);// this signal is connected with server_slot(..) slot of source object and echoes back switch state received from source + +public Q_SLOTS: + void recSwitchState_slot(bool); // slot to receive source state + void initConnection_slot(); + +private: + bool clientSwitchState; // holds received server switch state + QSharedPointer reptr;// holds reference to replica + }; + +#endif + diff --git a/examples/remoteobjects/simpleswitch/registryconnectedclient/main.cpp b/examples/remoteobjects/simpleswitch/registryconnectedclient/main.cpp new file mode 100644 index 0000000..9ed41fa --- /dev/null +++ b/examples/remoteobjects/simpleswitch/registryconnectedclient/main.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include "dynamicclient.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + QSharedPointer ptr; // shared pointer to hold replica + + QRemoteObjectNode repNode(QUrl(QStringLiteral("local:registry"))); + + ptr.reset(repNode.acquireDynamic("SimpleSwitch")); // acquire replica of source from host node + + DynamicClient rswitch(ptr); // create client switch object and pass replica reference to it + + return a.exec(); +} diff --git a/examples/remoteobjects/simpleswitch/registryconnectedclient/registryconnectedclient.pro b/examples/remoteobjects/simpleswitch/registryconnectedclient/registryconnectedclient.pro new file mode 100644 index 0000000..02aa54c --- /dev/null +++ b/examples/remoteobjects/simpleswitch/registryconnectedclient/registryconnectedclient.pro @@ -0,0 +1,24 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2015-02-13T15:22:50 +# +#------------------------------------------------- + +QT += remoteobjects core + +QT -= gui + +TARGET = registryconnectedclient +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/simpleswitch/registryconnectedclient +INSTALLS += target + +SOURCES += main.cpp \ + dynamicclient.cpp + +HEADERS += \ + dynamicclient.h diff --git a/examples/remoteobjects/simpleswitch/registryconnectedserver/CMakeLists.txt b/examples/remoteobjects/simpleswitch/registryconnectedserver/CMakeLists.txt new file mode 100644 index 0000000..47a51d4 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/registryconnectedserver/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.16) +project(registryconnectedserver LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/simpleswitch/registryconnectedserver") + +find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects) + +qt_add_executable(registryconnectedserver + main.cpp + simpleswitch.cpp simpleswitch.h +) + +set_target_properties(registryconnectedserver PROPERTIES + WIN32_EXECUTABLE FALSE + MACOSX_BUNDLE FALSE +) + +target_link_libraries(registryconnectedserver PUBLIC + Qt::Core + Qt::RemoteObjects +) + +qt6_add_repc_sources(registryconnectedserver + simpleswitch.rep +) + +install(TARGETS registryconnectedserver + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/simpleswitch/registryconnectedserver/main.cpp b/examples/remoteobjects/simpleswitch/registryconnectedserver/main.cpp new file mode 100644 index 0000000..61480e4 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/registryconnectedserver/main.cpp @@ -0,0 +1,23 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include "simpleswitch.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + SimpleSwitch srcSwitch; // create simple switch + + QRemoteObjectRegistryHost regNode(QUrl(QStringLiteral("local:registry"))); // create node that hosts registy + QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:replica")), QUrl(QStringLiteral("local:registry"))); // create node that will host source and connect to registry + + //Note, you can add srcSwitch directly to regNode if desired. + //We use two Nodes here, as the regNode could easily be in a third process. + + srcNode.enableRemoting(&srcSwitch); // enable remoting of source object + + return a.exec(); +} + diff --git a/examples/remoteobjects/simpleswitch/registryconnectedserver/registryconnectedserver.pro b/examples/remoteobjects/simpleswitch/registryconnectedserver/registryconnectedserver.pro new file mode 100644 index 0000000..d00687f --- /dev/null +++ b/examples/remoteobjects/simpleswitch/registryconnectedserver/registryconnectedserver.pro @@ -0,0 +1,30 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2015-02-13T15:02:06 +# +#------------------------------------------------- + +QT += remoteobjects core + +QT -= gui + +TARGET = registryconnectedserver +CONFIG += console +CONFIG -= app_bundle + +REPC_SOURCE = simpleswitch.rep + + +TEMPLATE = app + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/simpleswitch/registryconnectedserver +INSTALLS += target + +SOURCES += main.cpp \ + simpleswitch.cpp + +HEADERS += \ + simpleswitch.h + +DISTFILES += \ + simpleswitch.rep diff --git a/examples/remoteobjects/simpleswitch/registryconnectedserver/simpleswitch.cpp b/examples/remoteobjects/simpleswitch/registryconnectedserver/simpleswitch.cpp new file mode 100644 index 0000000..16fe6a3 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/registryconnectedserver/simpleswitch.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "simpleswitch.h" + +// constructor +SimpleSwitch::SimpleSwitch(QObject *parent) : SimpleSwitchSimpleSource(parent) +{ + stateChangeTimer = new QTimer(this); // Initialize timer + QObject::connect(stateChangeTimer, &QTimer::timeout, this, &SimpleSwitch::timeout_slot); // connect timeout() signal from stateChangeTimer to timeout_slot() of simpleSwitch + stateChangeTimer->start(2000); // Start timer and set timout to 2 seconds + qDebug() << "Source Node Started"; +} + +//destructor +SimpleSwitch::~SimpleSwitch() +{ + stateChangeTimer->stop(); +} + +void SimpleSwitch::server_slot(bool clientState) +{ + qDebug() << "Replica state is " << clientState; // print switch state echoed back by client +} + +void SimpleSwitch::timeout_slot(void) +{ + // slot called on timer timeout + if (currState()) // check if current state is true, currState() is defined in repc generated rep_simpleswitch_source.h + setCurrState(false); // set state to false + else + setCurrState(true); // set state to true + qDebug() << "Source State is "< + +class SimpleSwitch +{ + PROP(bool currState=false); + SLOT(void server_slot(bool clientState)); +}; + diff --git a/examples/remoteobjects/simpleswitch/simpleswitch.pro b/examples/remoteobjects/simpleswitch/simpleswitch.pro new file mode 100644 index 0000000..8b70a12 --- /dev/null +++ b/examples/remoteobjects/simpleswitch/simpleswitch.pro @@ -0,0 +1,8 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + directconnectclient \ + directconnectdynamicclient \ + directconnectserver \ + registryconnectedclient \ + registryconnectedserver diff --git a/examples/remoteobjects/ssl/CMakeLists.txt b/examples/remoteobjects/ssl/CMakeLists.txt new file mode 100644 index 0000000..2d4ac63 --- /dev/null +++ b/examples/remoteobjects/ssl/CMakeLists.txt @@ -0,0 +1,2 @@ +qt_internal_add_example(sslserver) +qt_internal_add_example(sslcppclient) diff --git a/examples/remoteobjects/ssl/doc/src/ssl.qdoc b/examples/remoteobjects/ssl/doc/src/ssl.qdoc new file mode 100644 index 0000000..ba1aef8 --- /dev/null +++ b/examples/remoteobjects/ssl/doc/src/ssl.qdoc @@ -0,0 +1,11 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example ssl + \title QtRemoteObjects SSL Example + + This example shows how you can set up a secure Remote Object network using + QSslSockets! Encrypting communication is critical when you need to pass data + through a network you don't have full control over. +*/ diff --git a/examples/remoteobjects/ssl/ssl.pro b/examples/remoteobjects/ssl/ssl.pro new file mode 100644 index 0000000..f36383f --- /dev/null +++ b/examples/remoteobjects/ssl/ssl.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs +CONFIG += debug_and_release ordered + +SUBDIRS = \ + sslserver \ + sslcppclient diff --git a/examples/remoteobjects/ssl/sslcppclient/CMakeLists.txt b/examples/remoteobjects/ssl/sslcppclient/CMakeLists.txt new file mode 100644 index 0000000..62aa1a8 --- /dev/null +++ b/examples/remoteobjects/ssl/sslcppclient/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.16) +project(SslCppClient LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/ssl/sslcppclient") + +find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects) + +qt_add_executable(SslCppClient + main.cpp +) + +set_target_properties(SslCppClient PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE FALSE +) + +target_link_libraries(SslCppClient PUBLIC + # Remove: gui + Qt::Core + Qt::RemoteObjects + Qt::RemoteObjectsPrivate +) + +# Resources: +set(cert_resource_files + "../sslserver/cert/client.crt" + "../sslserver/cert/client.key" + "../sslserver/cert/rootCA.key" + "../sslserver/cert/rootCA.pem" + "../sslserver/cert/rootCA.srl" + "../sslserver/cert/server.crt" + "../sslserver/cert/server.key" +) + +qt6_add_resources(SslCppClient "cert" + PREFIX + "/sslcert" + BASE + "../sslserver/cert" + FILES + ${cert_resource_files} +) + +qt6_add_repc_replicas(SslCppClient + timemodel.rep +) + +install(TARGETS SslCppClient + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/ssl/sslcppclient/main.cpp b/examples/remoteobjects/ssl/sslcppclient/main.cpp new file mode 100644 index 0000000..9d49a12 --- /dev/null +++ b/examples/remoteobjects/ssl/sslcppclient/main.cpp @@ -0,0 +1,85 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include +#include +#include +#include +#include "rep_timemodel_replica.h" + +#include + +class tester : public QObject +{ + Q_OBJECT +public: + tester() : QObject(nullptr) + { + QRemoteObjectNode m_client; + auto socket = setupConnection(); + connect(socket, &QSslSocket::errorOccurred, + socket, [](QAbstractSocket::SocketError error){ + qDebug() << "QSslSocket::error" << error; + }) ; + m_client.addClientSideConnection(socket); + + ptr1.reset(m_client.acquire< MinuteTimerReplica >()); + ptr2.reset(m_client.acquire< MinuteTimerReplica >()); + ptr3.reset(m_client.acquire< MinuteTimerReplica >()); + QTimer::singleShot(0, this, &tester::clear); + QTimer::singleShot(1, this, &tester::clear); + QTimer::singleShot(10000, this, &tester::clear); + QTimer::singleShot(11000, this, &tester::clear); + } +public slots: + void clear() + { + static int i = 0; + if (i == 0) { + i++; + ptr1.reset(); + } else if (i == 1) { + i++; + ptr2.reset(); + } else if (i == 2) { + i++; + ptr3.reset(); + } else { + qApp->quit(); + } + } + +private: + QScopedPointer ptr1, ptr2, ptr3; + + QSslSocket *setupConnection() + { + auto socketClient = new QSslSocket; + socketClient->setLocalCertificate(QStringLiteral(":/sslcert/client.crt")); + socketClient->setPrivateKey(QStringLiteral(":/sslcert/client.key")); + socketClient->setPeerVerifyMode(QSslSocket::VerifyPeer); + socketClient->connectToHostEncrypted(QStringLiteral("127.0.0.1"), 65511); + if (!socketClient->waitForEncrypted(-1)) { + qWarning("Failed to connect to server %s", + qPrintable(socketClient->errorString())); + exit(0); + } + return socketClient; + } +}; + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + auto config = QSslConfiguration::defaultConfiguration(); + config.setCaCertificates(QSslCertificate::fromPath(QStringLiteral(":/sslcert/rootCA.pem"))); + QSslConfiguration::setDefaultConfiguration(config); + + tester t; + return a.exec(); +} + +#include "main.moc" diff --git a/examples/remoteobjects/ssl/sslcppclient/sslcppclient.pro b/examples/remoteobjects/ssl/sslcppclient/sslcppclient.pro new file mode 100644 index 0000000..07fb8da --- /dev/null +++ b/examples/remoteobjects/ssl/sslcppclient/sslcppclient.pro @@ -0,0 +1,24 @@ +QT_FOR_CONFIG += network +requires(qtConfig(ssl)) + +REPC_REPLICA += timemodel.rep +QT = remoteobjects remoteobjects-private core + +QT -= gui + +TARGET = SslCppClient +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += main.cpp + +OTHER_FILES += \ + timemodel.rep + +RESOURCES += \ + ../sslserver/cert/cert.qrc + + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/ssl/sslcppclient +INSTALLS += target diff --git a/examples/remoteobjects/ssl/sslcppclient/timemodel.rep b/examples/remoteobjects/ssl/sslcppclient/timemodel.rep new file mode 100644 index 0000000..cbfaf24 --- /dev/null +++ b/examples/remoteobjects/ssl/sslcppclient/timemodel.rep @@ -0,0 +1,12 @@ +#include + +POD PresetInfo(int presetNumber, float frequency, QString stationName) +class MinuteTimer +{ + PROP(int hour=1); + PROP(int minute=51); + SIGNAL(timeChanged()); + SIGNAL(timeChanged2(QTime t)); + SIGNAL(sendCustom(PresetInfo info)); + SLOT(void SetTimeZone(int zn)); +}; diff --git a/examples/remoteobjects/ssl/sslserver/CMakeLists.txt b/examples/remoteobjects/ssl/sslserver/CMakeLists.txt new file mode 100644 index 0000000..a276b9d --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 3.16) +project(sslserver LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/ssl/sslserver") + +find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects) + +qt_add_executable(sslserver + main.cpp + sslserver.cpp sslserver.h + timemodel.cpp timemodel.h +) + +set_target_properties(sslserver PROPERTIES + WIN32_EXECUTABLE FALSE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(sslserver PUBLIC + Qt::Core + Qt::RemoteObjects + Qt::RemoteObjectsPrivate +) + +# Resources: +set(cert_resource_files + "cert/client.crt" + "cert/client.key" + "cert/rootCA.key" + "cert/rootCA.pem" + "cert/rootCA.srl" + "cert/server.crt" + "cert/server.key" +) + +qt6_add_resources(sslserver "cert" + PREFIX + "/sslcert" + BASE + "cert" + FILES + ${cert_resource_files} +) + +qt6_add_repc_sources(sslserver + ../../timemodel.rep +) + +install(TARGETS sslserver + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/ssl/sslserver/cert/cert.qrc b/examples/remoteobjects/ssl/sslserver/cert/cert.qrc new file mode 100644 index 0000000..65f92e2 --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/cert/cert.qrc @@ -0,0 +1,11 @@ + + + client.crt + client.key + rootCA.key + rootCA.pem + rootCA.srl + server.crt + server.key + + diff --git a/examples/remoteobjects/ssl/sslserver/cert/client.crt b/examples/remoteobjects/ssl/sslserver/cert/client.crt new file mode 100644 index 0000000..3aa0ff8 --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/cert/client.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICrTCCAZUCFHOQggvUf1o8c5i3yNyiGLNcLC4pMA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB1F0Uk8gQ0EwHhcNMjEwMjI0MTEzMzU1WhcNMjMwNTMwMTEzMzU1 +WjAUMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDbl9iuedw0oSbtpC2m30YdzwRmemijasP9SQGQ6+piUOFUKCZsoGWc +RcEnLGzC+KJ7FXh8jA1kTXSW6ghqvrUysN8VzjgmcCLFee4JAkCUY8yNrlq13ciR +19BE09kJdOPZeI57pCSBNA6iy03Q4nc/GJpG63QTqJv/WUUgMek0UsmZIzDcWaqr +MCMnLMaRi5oKFCnnl8E0XDuRm1nqPAzT+us/4upMv+7Q2xs4QFXbLUpSIToNc1wm +tP6OAGaYClbJZgZbUNowj0wJeCUAwGGcDpliYj1JB8R015z8Kd8pDCvdD7XL35JR +rT+eaBFNLUrl30aIl3lWf/buv3OoRmuVAgMBAAEwDQYJKoZIhvcNAQELBQADggEB +AJjdfuy2pb3KgnpxYiXfKXCqGlN7E1RyoCIsMhldWcxAN5cwIJCrvde5MNI8kDvd +0SfDpRpCP/hZqpR6DsR9iNYJprXlQNZ7Rs41Eswwlb66DqmBlb5ZQcYl8KsKV5fw +7PhvLpjC5hEg1OBg1Ooz+aNvI9OJYIRFUJ1smtRzwXWuQd5QoqYVRpzvmrFawnGa +2NHywiwgKyGvY/y82pPuj1rt0L+bae85cZm32f6gp1me9OuLIqA2G5UafSiigWBY +YL249Rd4rrT87GAeaiBo8ZxZ8de8O7TOBjSNrfAMySepDWjfFfoNpyp+4foRKmpE +aZmgGTIj5rfhYh4Gcj1nZBw= +-----END CERTIFICATE----- diff --git a/examples/remoteobjects/ssl/sslserver/cert/client.key b/examples/remoteobjects/ssl/sslserver/cert/client.key new file mode 100644 index 0000000..b3f4f1a --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/cert/client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA25fYrnncNKEm7aQtpt9GHc8EZnpoo2rD/UkBkOvqYlDhVCgm +bKBlnEXBJyxswviiexV4fIwNZE10luoIar61MrDfFc44JnAixXnuCQJAlGPMja5a +td3IkdfQRNPZCXTj2XiOe6QkgTQOostN0OJ3PxiaRut0E6ib/1lFIDHpNFLJmSMw +3FmqqzAjJyzGkYuaChQp55fBNFw7kZtZ6jwM0/rrP+LqTL/u0NsbOEBV2y1KUiE6 +DXNcJrT+jgBmmApWyWYGW1DaMI9MCXglAMBhnA6ZYmI9SQfEdNec/CnfKQwr3Q+1 +y9+SUa0/nmgRTS1K5d9GiJd5Vn/27r9zqEZrlQIDAQABAoIBAEDLm4pQNuPosV3p +1fapZz0gesHqWLnvpQk145ppom2ERBjbCAuBgLoN8yKl/ynAx+DdwwGtKb5xBHgL +cpRc1YaxngIHKZZd/ESc59oMqhWfJRqhWe7UFHzEW5YTlLUvopPm+NQO6R6ex7rN +lpaOXHVnww4uJ8AtPmqoYrdPQurG/txveRMLo84JJT+IH2YVWOzccp809zw4WZZD +qBcgm/dV8ir+8nUHQlR+loMMrEoKeacNxtHUXWL6d6P93Q72L07t41/l0XmXXq7I +cVJnGxcJtkeqj03FSHqDU3XM5fRg6f+XnnSnhnd4AUmHe8cvyeCnEf4bdh4UpzBG +sCie+XkCgYEA93FU0X6ttWdb+rJNHRnHmb4DxOVo2LeXEk1A1ul+Yj+jFP+TwJH+ +bm8PbV7ALdyH2u66ElQG60gW9ztu86xl5ZLVdhijWJpjHKB45eXVhnRcb2Fy9tDc +pUeRs8+IrrYbWDrNZZYWby83MqPHimCLTmAZl11NMB2ohyFDxr5voGMCgYEA4y/0 +2WN8r74H9I3L2Ghfe8e3i/W35BpjtElJxiL3L1vzGdU5Wo1hDnvjoHvdTxB7LtGU +I+P0l77fwuAC8G8bh4SZ59jcxlqCmbXy7wDAyrYaCja5OWK9xWXvYuya5CCPrg6h +wo7TcrxjdEvEVQ97PMZcq6HVBOtINZGfJeSieacCgYAHyQsQJFo20O+17ZI7jioX +jkD0Gvu3hd889i1KFcKiOLpa2Me/UVieBOSJXmfRiZTEsKouFXK6SGRglwAgrpXu +KTaKJrBNA16G8g2bviV/u32FC53gYiXvFVdiPu9f/97QYdlAjv5ZtTSZZUnL8smv +R5rGhmr9TpGU3tkREcDVXQKBgBUfJ0dyvWvlYf31lOcYxQ/QAJuNi7w0S+K+EZLP +O2X2yYI0VbG6hTSAhigse+XW5Wzz5S71CY92Gn2WsA9EdS3DQT/R5Ky4S34Y8W4R +BtuR1JfwgIX6TSRmFrx+vOPKtzD6gUWCW9xF8YUlaipyVwXOd10pnZFogn0gfchb +GlPvAoGAG2xikjlCTrnKv7KRF9sxO1eLixfzHwWKiAhrtFBoHSM4AwynrpAb0eMf +ObSIjXeBy93LhTluVOsD5J9iXA/SKYoXqt/tDMCHRdwpTsJNBa56GMkpFHHLo6oC +si20nmMXP949gpRIvrYsgYC8WObbi+RQEWDVutv7hVPCF0QvUHs= +-----END RSA PRIVATE KEY----- diff --git a/examples/remoteobjects/ssl/sslserver/cert/readme b/examples/remoteobjects/ssl/sslserver/cert/readme new file mode 100644 index 0000000..1386169 --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/cert/readme @@ -0,0 +1,2 @@ +These files are generated by the script located at tests/auto/external_IODevice/cert/generate.sh + diff --git a/examples/remoteobjects/ssl/sslserver/cert/rootCA.key b/examples/remoteobjects/ssl/sslserver/cert/rootCA.key new file mode 100644 index 0000000..1647817 --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/cert/rootCA.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAulVnnHwRF6e2aAThSi1cZpUlO3ZdqzPIuf75NBbRY2y9Vm+F +cyCqUquNxP/qDE02nfQFBd/PUKqUWQs0EXVEVZPEG6s/l7ionYHkMmWSjh+AAWp7 +Iwx3MlHFNi9c5Xrod7iS1igg0YhDQlnT0xGfTXZasUJ/s6NuNoZiN5L6sEKYDSAu +kzyyqS23WrqE4RvsGAaoaaJqu1MT8DBqI9xoPpIvwb/4gdOZn4YClW2WWrVjCTT2 +zEzUAh1BjdH3dktXogiFfXHuOP4W8suOx46NXDcZ3f5LF8CT/2uq9l8vta+pV2ci +BAIctGu5z+fEdTCojvCWOvCzYmjtep/yTukT+QIDAQABAoIBAQCRvRjkCRnpUZDW +vPJk7SO3THIplwPeUwtthqLtfedaB4PzphhPmr39GRcyfSNXadENK/39wTbKlhCf +sKaR/RFsib26EnATwCeJwj10uYMuTC73bIxRNn/ISLKiFdtn1YEbmq6meA5rNFO/ +Arkt/juF/8shl6yAGZSrauJJK1mOH8ItMaGR+6tVPWLIZOLk6TiOJhj9SXvMTCw/ +HzgNZMgFGfqnbctg1ki/CY0BkIkYNUTCjhoCwjcgBJl4ERCfTQS6UeEG9Ad+beIH +g8WKzpfjF5+Jnjzqw87aWx1200OdlEdouTt677RXHJFN5naUc+GJZGpmA3RGZA17 +LqA4zBYNAoGBAOGwtg7JQkBOmUC0SiKdXnxG1hVnS4N3DMIDVX2tAe/wWIrP168e +0UpCvswLD+JqO1IgWqw9+QUPnhJSQ9JbYB+678esOTLsT5Yd18VcsiRxSacvQfUw +H4YJaHrFuuFlnxYMlMdPYS3knbIPsft9DVQLFBLL7qPVHbrJ3V6Sn4XrAoGBANNb +mfhgVr5m0n3sQVTlYhWwbJq5K+Htzzl7Xl3JHpMLm2C/GoorP/2zLVhbH20lsE3A +FyIfjcwRxGRu2TXCVnMc4GttlMX5leTxykEd2VrZuEVnTdrudm45Z6sZQpdf1QTg +WebwKgN1eCg7Jkuk5YlRX/KwMtuq4MVzPtOvR+CrAoGAA8uC5DDCKm6n6QyfCoH2 +6sQOKYH5JRbFYiXINDrKg4xZEMx55fnwrvz8VFYDSF1c7f6ZR7grDci7cbdsaIcc +0KvGCGd+9ro+hFmwHSN342D8ShFjXIoYnZpe5WGZyNx6llZT0h4lli338NyOs5ng +tX8SMVa4hoy42UE3tbVldU0CgYA0l/K0b6SmNIfkdcm8Cmhh5UjhJ3rX+Yk7UIum +4skM5jJ/3I4KG8EMrG14MxSa4GoCru4Su69ZPIKWS08ZpYZFlsXxdY8zxGucUN53 +XaochVjpTE9/Tx+BRh+Z3+tGJ76mO/2jDdgmjDCeMjnRUPMdPHaXuWiuaNMNzyOv +IUrNiQKBgGvxEQ0Oe3d/om2Lp/cHbkhZkw/jO/FG5HtodxiO3+1YLhExsDOc5GVn ++x2eNv+dQSIrGagko9TJe1p9WqFnD19Ls+ezqfw2fR5Amg1KHKGUA7k1+Qe/QgoK +D+T4/RkvdGRoBv/il+Rj1rfmMAhEzdD7Axek9a6rUj8geO22kp7I +-----END RSA PRIVATE KEY----- diff --git a/examples/remoteobjects/ssl/sslserver/cert/rootCA.pem b/examples/remoteobjects/ssl/sslserver/cert/rootCA.pem new file mode 100644 index 0000000..7f6cce5 --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/cert/rootCA.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBTCCAe2gAwIBAgIUV9eILCYaC+qwZHR7OO23uyd2UjwwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHUXRSTyBDQTAeFw0yMTAyMjQxMTMzNTVaFw0yMzA2MTAx +MTMzNTVaMBIxEDAOBgNVBAMMB1F0Uk8gQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQC6VWecfBEXp7ZoBOFKLVxmlSU7dl2rM8i5/vk0FtFjbL1Wb4Vz +IKpSq43E/+oMTTad9AUF389QqpRZCzQRdURVk8Qbqz+XuKidgeQyZZKOH4ABansj +DHcyUcU2L1zleuh3uJLWKCDRiENCWdPTEZ9NdlqxQn+zo242hmI3kvqwQpgNIC6T +PLKpLbdauoThG+wYBqhpomq7UxPwMGoj3Gg+ki/Bv/iB05mfhgKVbZZatWMJNPbM +TNQCHUGN0fd2S1eiCIV9ce44/hbyy47Hjo1cNxnd/ksXwJP/a6r2Xy+1r6lXZyIE +Ahy0a7nP58R1MKiO8JY68LNiaO16n/JO6RP5AgMBAAGjUzBRMB0GA1UdDgQWBBSu +ehS/XLejTiDbCddGU2mMZ1t3CjAfBgNVHSMEGDAWgBSuehS/XLejTiDbCddGU2mM +Z1t3CjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB8JzSuhHPK +cjhLqOHUGMKtKWOd5p9g2D45cAWh6jdzU/AhmslMPbsO5hZkqfE+3xARtcMmQfF2 +k1Qyp3hDTN1ZqHSM6Urq9uR33/wXZbNRUNCD8lAmqKyzF9NF7Q+tmC//IMRtVQhK +aMN3LciyYGQjT0XhDKFWEz9/AvUQD97mLow2m0/izqE4SI6ekQDNL26IiCWFgFjh +ScZjcJ1ogluD2a6sEUGywRXLNV/bdSjRgkAbpvJFrok7dDZ8xCNhOg4xJJQJRWm7 +ZusUydiVyfgrFan6MD+EdldRHjAs8S9BJfZ0RTOWnD9V8auKuVomzKDed54QlXXi +zwowb3Objpqh +-----END CERTIFICATE----- diff --git a/examples/remoteobjects/ssl/sslserver/cert/rootCA.srl b/examples/remoteobjects/ssl/sslserver/cert/rootCA.srl new file mode 100644 index 0000000..d292c9f --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/cert/rootCA.srl @@ -0,0 +1 @@ +7390820BD47F5A3C7398B7C8DCA218B35C2C2E29 diff --git a/examples/remoteobjects/ssl/sslserver/cert/server.crt b/examples/remoteobjects/ssl/sslserver/cert/server.crt new file mode 100644 index 0000000..df9d55c --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/cert/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFTCCAf2gAwIBAgIUc5CCC9R/WjxzmLfI3KIYs1wsLigwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHUXRSTyBDQTAeFw0yMTAyMjQxMTMzNTVaFw0yMzA1MzAx +MTMzNTVaMBQxEjAQBgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAOKHte9tB66OD+Um/WkqxHtW3sKrBs4IxKuWAef0UMRt3ld6 +5HvWk+xsCZdPxeL53nMOIy9FS6wKGvEWTwYRR4Id9iX2XQsI4cRJWl25qgCYohnm +Eet9CUkXa3ywbyrSBWFD0r956sS+mwhHU9z05jphd6iZEonHu2b4BFFXMN7+prwj +00EtGbte5wSWWE9ZfXzeGYd4cZBReNCRjaS5XJ3IgjZ4tfxsB3JzBjVafCfnth7r +Is8a2SKCGnhYmV+A6Agth4xtSKDho+BSDYSuMux3dftM/eqtxF0wXzlnX5ApNwGB +zWjcoUL63vjjy17oNEtbs5X2e1g8bGRaGRxGUHUCAwEAAaNhMF8wHwYDVR0jBBgw +FoAUrnoUv1y3o04g2wnXRlNpjGdbdwowCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAw +EwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0B +AQsFAAOCAQEAqhBhxRgG9N1ZghwWC3ZhWSx4BFl3YrStWlQcjffcQ6p8NDxsrkFc +gMG51TmJdaz8J4v2AZW8k9GJlEIaZdV/8czeyEwvjKD4vrUw88waeW7n6o8H+8k+ +ak9fRFvnerFrLEWNpyRqbjJWwm8bQ4T5UKsVNXkZnNLyG2Ha29L9gUHffgSMiyLO +hWqcanPxsMJaDVhw/Gd8JwqaEC1nRPCGxhog2/D2sh4vCj1UykykjPwNz5fP/vfA +VujNCA23eXAdgD3lALHu2WrmyPkQCM7Z61g4k8+v0KjhyJjdLSVTwkPePEo87Fv4 +sn4Jp5gPPBf7jDFKp8PDdbPmk0qN+Wm8gA== +-----END CERTIFICATE----- diff --git a/examples/remoteobjects/ssl/sslserver/cert/server.key b/examples/remoteobjects/ssl/sslserver/cert/server.key new file mode 100644 index 0000000..05144f4 --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/cert/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA4oe1720Hro4P5Sb9aSrEe1bewqsGzgjEq5YB5/RQxG3eV3rk +e9aT7GwJl0/F4vnecw4jL0VLrAoa8RZPBhFHgh32JfZdCwjhxElaXbmqAJiiGeYR +630JSRdrfLBvKtIFYUPSv3nqxL6bCEdT3PTmOmF3qJkSice7ZvgEUVcw3v6mvCPT +QS0Zu17nBJZYT1l9fN4Zh3hxkFF40JGNpLlcnciCNni1/GwHcnMGNVp8J+e2Husi +zxrZIoIaeFiZX4DoCC2HjG1IoOGj4FINhK4y7Hd1+0z96q3EXTBfOWdfkCk3AYHN +aNyhQvre+OPLXug0S1uzlfZ7WDxsZFoZHEZQdQIDAQABAoIBAGuKEYzALc1oE5Ot +ls++RdhnvQidOHXHI9ZxOCZtjYoyvkK5TI6dp0utXkA+1qqSBFCKfZmLRAlAItog +xRMUUOYsYxZShokehk8wo32rDlGKJCo3Vnp8uVPBkn13JM8nNPafxASyVAlikyay +9dUHTeSZML0RLgPKleSkCSi0Q7cYOFG/HB9aNjp8F5rdut02KrmC3cxlHKF7QXXG +VU+op1Z9o0V2/iUFJnF5CR40sW2THEbBJkkeYwbvUTnavz4XQtZst//DKsDQEe2r +UrhsIHduvG4tWiBE77m1vyigTxUWCsLQ2KCnn9O+4KyTg9HWCiQ2QSU3istX/rpI +zN2lOgECgYEA9PVVMnY+t59Q73IQ9LRg5KRqg6YyGQOrwJKbCUxDrA6ikh3MDgwV +CkC6Jdl6e4DMog51l3CizrfR2+mtNSTUJDGFE1iGgI+Faem4aopRtFRiLWJ8n4m7 +U8pl3XTP0XFT68aBCAE6O/xVPXs0I/eKNvaF5vokB5zm4R79o37WP+UCgYEA7L26 +TiCFA73Fil/bPupqWJnvm896RlO1S+IBOKlPyCHVvxiGLvtv+YTucCFwXQ4FeNRh +bQEWlURsgeNr7PHATtFUZ/zo/7l1WYNLXZDZwWD+JYllVPwskJOJMx5Rc77Q0aQ6 +7v60XMGwD5cxQ29RHuJs09Iwc9b1WqwOAEJAJVECgYBNsxQXMZKrRAm0KgZe2Ghz +ngN7RthVPujX6KjsxhghF3NRzcnQGt0Bp45kOxuy2SQPs25xXvUFhSE4FGMwnEH+ +SQbhIA9p8BxtgAlTIhTQkoOhyb+mC1Y0Odsd59OTp9Lq0shS9bC3Hk8bdV0Qm5Bn +5sKKhYWwNIC3n9Dsb2seUQKBgAS7biPtpnsCqhYwAFPrn6CRwyZcKVeKiM8xf1DA +oaWgd4NQXC5IPF7Cd3mqUXKquxVFOYVSRj9JlNmr0BZ2Zp+ss4E4nvetn1jgtPrz +0EZ7R9k8O9hNCh8Bs/ZfnsUvhUELhVoNoVFRVdGZ9hQg/4AcioxZYTqPi2v6kHUU +3e9hAoGAec7anF5TiTx2jjcDFS9hrRw0w2PsNX24qjqPFqeuzDIorh6rq4Ip4aA0 +7rxeIXmxjmYA7pPCT9rPxtpEp4BQovF9kHMutd8lyB4rGbLpNpOY4m5v8Oo7cLQ3 +kLAwE+jrEwLNtuq+kUlGwK7YLeiGUm4Rsof5IXlSkXzL/99gHC4= +-----END RSA PRIVATE KEY----- diff --git a/examples/remoteobjects/ssl/sslserver/main.cpp b/examples/remoteobjects/ssl/sslserver/main.cpp new file mode 100644 index 0000000..d882d9c --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/main.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "timemodel.h" + +#include +#include + +#include "sslserver.h" + +#include +/* +* http://stackoverflow.com/questions/7404163/windows-handling-ctrlc-in-different-thread +*/ + +void SigIntHandler() +{ + qDebug()<<"Ctrl-C received. Quitting."; + qApp->quit(); +} + +#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_QNX) +#include + +void unix_handler(int s) +{ + if (s==SIGINT) + SigIntHandler(); +} + +#elif defined(Q_OS_WIN32) +#include + +BOOL WINAPI WinHandler(DWORD CEvent) +{ + switch (CEvent) + { + case CTRL_C_EVENT: + SigIntHandler(); + break; + } + return TRUE; +} +#endif + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + auto config = QSslConfiguration::defaultConfiguration(); + config.setCaCertificates(QSslCertificate::fromPath(QStringLiteral(":/sslcert/rootCA.pem"))); + QSslConfiguration::setDefaultConfiguration(config); + +#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 host; + SslServer server; + server.listen(QHostAddress::Any, 65511); + + host.setHostUrl(server.serverAddress().toString(), QRemoteObjectHost::AllowExternalRegistration); + + QObject::connect(&server, &SslServer::encryptedSocketReady, &server, [&host](QSslSocket *socket) { + QObject::connect(socket, &QSslSocket::errorOccurred, + socket, [](QAbstractSocket::SocketError error){ + qDebug() << "QSslSocket::error" << error; + }) ; + host.addHostSideConnection(socket); + }); + + MinuteTimer timer; + host.enableRemoting(&timer); + + Q_UNUSED(timer) + return app.exec(); +} + diff --git a/examples/remoteobjects/ssl/sslserver/sslserver.cpp b/examples/remoteobjects/ssl/sslserver/sslserver.cpp new file mode 100644 index 0000000..98c0402 --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/sslserver.cpp @@ -0,0 +1,32 @@ +// Copyright (C) 2018 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "sslserver.h" +#include + +SslServer::SslServer(QObject *parent) + : QTcpServer(parent) +{} + + +void SslServer::incomingConnection(qintptr socketDescriptor) +{ + auto serverSocket = new QSslSocket; + if (serverSocket->setSocketDescriptor(socketDescriptor)) { + addPendingConnection(serverSocket); + connect(serverSocket, &QSslSocket::encrypted, this, [this, serverSocket] { + Q_EMIT encryptedSocketReady(serverSocket); + }); + connect(serverSocket, static_cast&)>(&QSslSocket::sslErrors), + this, [serverSocket](const QList& errors){ + qWarning() << "Error:" << serverSocket << errors; + delete serverSocket; + }); + serverSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); + serverSocket->setLocalCertificate(QStringLiteral(":/sslcert/server.crt")); + serverSocket->setPrivateKey(QStringLiteral(":/sslcert/server.key")); + serverSocket->startServerEncryption(); + } else { + delete serverSocket; + } +} diff --git a/examples/remoteobjects/ssl/sslserver/sslserver.h b/examples/remoteobjects/ssl/sslserver/sslserver.h new file mode 100644 index 0000000..c3ffbca --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/sslserver.h @@ -0,0 +1,24 @@ +// Copyright (C) 2018 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SSLSERVER_H +#define SSLSERVER_H + +#include + +QT_BEGIN_NAMESPACE +class QSslSocket; +QT_END_NAMESPACE + +class SslServer : public QTcpServer +{ + Q_OBJECT +public: + SslServer(QObject *parent=nullptr); + void incomingConnection(qintptr socketDescriptor) override; + +signals: + void encryptedSocketReady(QSslSocket *socket); +}; + +#endif // SSLSERVER_H diff --git a/examples/remoteobjects/ssl/sslserver/sslserver.pro b/examples/remoteobjects/ssl/sslserver/sslserver.pro new file mode 100644 index 0000000..b8081d2 --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/sslserver.pro @@ -0,0 +1,21 @@ +QT_FOR_CONFIG += network +requires(qtConfig(ssl)) + +CONFIG += console + + +REPC_SOURCE += ../../timemodel.rep +QT = remoteobjects remoteobjects-private core + +SOURCES += timemodel.cpp main.cpp \ + sslserver.cpp +HEADERS += timemodel.h \ + sslserver.h + +contains(QT_CONFIG, c++11): CONFIG += c++11 + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/ssl/sslserver +INSTALLS += target + +RESOURCES += \ + cert/cert.qrc diff --git a/examples/remoteobjects/ssl/sslserver/timemodel.cpp b/examples/remoteobjects/ssl/sslserver/timemodel.cpp new file mode 100644 index 0000000..91fc42c --- /dev/null +++ b/examples/remoteobjects/ssl/sslserver/timemodel.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "timemodel.h" + +MinuteTimer::MinuteTimer(QObject *parent) : MinuteTimerSimpleSource(parent), zone(0) +{ + time = QTime::currentTime(); + setHour(time.hour()); + setMinute(time.minute()); + timer.start(60000-time.second()*1000, this); +} +MinuteTimer::~MinuteTimer() +{ + timer.stop(); +} +void MinuteTimer::timerEvent(QTimerEvent *) +{ + QTime now = QTime::currentTime(); + if (now.second() == 59 && now.minute() == time.minute() && now.hour() == time.hour()) { + // just missed time tick over, force it, wait extra 0.5 seconds + time = time.addSecs(60); + timer.start(60500, this); + } else { + time = now; + timer.start(60000-time.second()*1000, this); + } + qDebug()<<"Time"< +#include "rep_timemodel_source.h" + +class MinuteTimer : public MinuteTimerSimpleSource +{ + Q_OBJECT +public: + MinuteTimer(QObject *parent = nullptr); + ~MinuteTimer() override; + +public slots: + void SetTimeZone(const int &zn) override; + +protected: + void timerEvent(QTimerEvent *) override; + +private: + QTime time; + QBasicTimer timer; + int zone; +}; diff --git a/examples/remoteobjects/timemodel.rep b/examples/remoteobjects/timemodel.rep new file mode 100644 index 0000000..c07ee2c --- /dev/null +++ b/examples/remoteobjects/timemodel.rep @@ -0,0 +1,14 @@ +#include + +POD PresetInfo(int presetNumber, float frequency, QString stationName) +POD PresetData(QList bla) +class MinuteTimer +{ + PROP(int hour=1); + PROP(int minute=51); + SIGNAL(timeChanged()); + SIGNAL(timeChanged2(QTime t)); + SIGNAL(sendCustom(PresetInfo info)); + SIGNAL(foo(QMap foo)); + SLOT(void SetTimeZone(const int &)); +}; diff --git a/examples/remoteobjects/websockets/CMakeLists.txt b/examples/remoteobjects/websockets/CMakeLists.txt new file mode 100644 index 0000000..5441f66 --- /dev/null +++ b/examples/remoteobjects/websockets/CMakeLists.txt @@ -0,0 +1,4 @@ +if(TARGET Qt::WebSockets AND TARGET Qt::Widgets) + qt_internal_add_example(wsclient) + qt_internal_add_example(wsserver) +endif() diff --git a/examples/remoteobjects/websockets/common/cert/cert.qrc b/examples/remoteobjects/websockets/common/cert/cert.qrc new file mode 100644 index 0000000..65f92e2 --- /dev/null +++ b/examples/remoteobjects/websockets/common/cert/cert.qrc @@ -0,0 +1,11 @@ + + + client.crt + client.key + rootCA.key + rootCA.pem + rootCA.srl + server.crt + server.key + + diff --git a/examples/remoteobjects/websockets/common/cert/client.crt b/examples/remoteobjects/websockets/common/cert/client.crt new file mode 100644 index 0000000..3aa0ff8 --- /dev/null +++ b/examples/remoteobjects/websockets/common/cert/client.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICrTCCAZUCFHOQggvUf1o8c5i3yNyiGLNcLC4pMA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB1F0Uk8gQ0EwHhcNMjEwMjI0MTEzMzU1WhcNMjMwNTMwMTEzMzU1 +WjAUMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDbl9iuedw0oSbtpC2m30YdzwRmemijasP9SQGQ6+piUOFUKCZsoGWc +RcEnLGzC+KJ7FXh8jA1kTXSW6ghqvrUysN8VzjgmcCLFee4JAkCUY8yNrlq13ciR +19BE09kJdOPZeI57pCSBNA6iy03Q4nc/GJpG63QTqJv/WUUgMek0UsmZIzDcWaqr +MCMnLMaRi5oKFCnnl8E0XDuRm1nqPAzT+us/4upMv+7Q2xs4QFXbLUpSIToNc1wm +tP6OAGaYClbJZgZbUNowj0wJeCUAwGGcDpliYj1JB8R015z8Kd8pDCvdD7XL35JR +rT+eaBFNLUrl30aIl3lWf/buv3OoRmuVAgMBAAEwDQYJKoZIhvcNAQELBQADggEB +AJjdfuy2pb3KgnpxYiXfKXCqGlN7E1RyoCIsMhldWcxAN5cwIJCrvde5MNI8kDvd +0SfDpRpCP/hZqpR6DsR9iNYJprXlQNZ7Rs41Eswwlb66DqmBlb5ZQcYl8KsKV5fw +7PhvLpjC5hEg1OBg1Ooz+aNvI9OJYIRFUJ1smtRzwXWuQd5QoqYVRpzvmrFawnGa +2NHywiwgKyGvY/y82pPuj1rt0L+bae85cZm32f6gp1me9OuLIqA2G5UafSiigWBY +YL249Rd4rrT87GAeaiBo8ZxZ8de8O7TOBjSNrfAMySepDWjfFfoNpyp+4foRKmpE +aZmgGTIj5rfhYh4Gcj1nZBw= +-----END CERTIFICATE----- diff --git a/examples/remoteobjects/websockets/common/cert/client.key b/examples/remoteobjects/websockets/common/cert/client.key new file mode 100644 index 0000000..b3f4f1a --- /dev/null +++ b/examples/remoteobjects/websockets/common/cert/client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA25fYrnncNKEm7aQtpt9GHc8EZnpoo2rD/UkBkOvqYlDhVCgm +bKBlnEXBJyxswviiexV4fIwNZE10luoIar61MrDfFc44JnAixXnuCQJAlGPMja5a +td3IkdfQRNPZCXTj2XiOe6QkgTQOostN0OJ3PxiaRut0E6ib/1lFIDHpNFLJmSMw +3FmqqzAjJyzGkYuaChQp55fBNFw7kZtZ6jwM0/rrP+LqTL/u0NsbOEBV2y1KUiE6 +DXNcJrT+jgBmmApWyWYGW1DaMI9MCXglAMBhnA6ZYmI9SQfEdNec/CnfKQwr3Q+1 +y9+SUa0/nmgRTS1K5d9GiJd5Vn/27r9zqEZrlQIDAQABAoIBAEDLm4pQNuPosV3p +1fapZz0gesHqWLnvpQk145ppom2ERBjbCAuBgLoN8yKl/ynAx+DdwwGtKb5xBHgL +cpRc1YaxngIHKZZd/ESc59oMqhWfJRqhWe7UFHzEW5YTlLUvopPm+NQO6R6ex7rN +lpaOXHVnww4uJ8AtPmqoYrdPQurG/txveRMLo84JJT+IH2YVWOzccp809zw4WZZD +qBcgm/dV8ir+8nUHQlR+loMMrEoKeacNxtHUXWL6d6P93Q72L07t41/l0XmXXq7I +cVJnGxcJtkeqj03FSHqDU3XM5fRg6f+XnnSnhnd4AUmHe8cvyeCnEf4bdh4UpzBG +sCie+XkCgYEA93FU0X6ttWdb+rJNHRnHmb4DxOVo2LeXEk1A1ul+Yj+jFP+TwJH+ +bm8PbV7ALdyH2u66ElQG60gW9ztu86xl5ZLVdhijWJpjHKB45eXVhnRcb2Fy9tDc +pUeRs8+IrrYbWDrNZZYWby83MqPHimCLTmAZl11NMB2ohyFDxr5voGMCgYEA4y/0 +2WN8r74H9I3L2Ghfe8e3i/W35BpjtElJxiL3L1vzGdU5Wo1hDnvjoHvdTxB7LtGU +I+P0l77fwuAC8G8bh4SZ59jcxlqCmbXy7wDAyrYaCja5OWK9xWXvYuya5CCPrg6h +wo7TcrxjdEvEVQ97PMZcq6HVBOtINZGfJeSieacCgYAHyQsQJFo20O+17ZI7jioX +jkD0Gvu3hd889i1KFcKiOLpa2Me/UVieBOSJXmfRiZTEsKouFXK6SGRglwAgrpXu +KTaKJrBNA16G8g2bviV/u32FC53gYiXvFVdiPu9f/97QYdlAjv5ZtTSZZUnL8smv +R5rGhmr9TpGU3tkREcDVXQKBgBUfJ0dyvWvlYf31lOcYxQ/QAJuNi7w0S+K+EZLP +O2X2yYI0VbG6hTSAhigse+XW5Wzz5S71CY92Gn2WsA9EdS3DQT/R5Ky4S34Y8W4R +BtuR1JfwgIX6TSRmFrx+vOPKtzD6gUWCW9xF8YUlaipyVwXOd10pnZFogn0gfchb +GlPvAoGAG2xikjlCTrnKv7KRF9sxO1eLixfzHwWKiAhrtFBoHSM4AwynrpAb0eMf +ObSIjXeBy93LhTluVOsD5J9iXA/SKYoXqt/tDMCHRdwpTsJNBa56GMkpFHHLo6oC +si20nmMXP949gpRIvrYsgYC8WObbi+RQEWDVutv7hVPCF0QvUHs= +-----END RSA PRIVATE KEY----- diff --git a/examples/remoteobjects/websockets/common/cert/readme b/examples/remoteobjects/websockets/common/cert/readme new file mode 100644 index 0000000..1386169 --- /dev/null +++ b/examples/remoteobjects/websockets/common/cert/readme @@ -0,0 +1,2 @@ +These files are generated by the script located at tests/auto/external_IODevice/cert/generate.sh + diff --git a/examples/remoteobjects/websockets/common/cert/rootCA.key b/examples/remoteobjects/websockets/common/cert/rootCA.key new file mode 100644 index 0000000..1647817 --- /dev/null +++ b/examples/remoteobjects/websockets/common/cert/rootCA.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAulVnnHwRF6e2aAThSi1cZpUlO3ZdqzPIuf75NBbRY2y9Vm+F +cyCqUquNxP/qDE02nfQFBd/PUKqUWQs0EXVEVZPEG6s/l7ionYHkMmWSjh+AAWp7 +Iwx3MlHFNi9c5Xrod7iS1igg0YhDQlnT0xGfTXZasUJ/s6NuNoZiN5L6sEKYDSAu +kzyyqS23WrqE4RvsGAaoaaJqu1MT8DBqI9xoPpIvwb/4gdOZn4YClW2WWrVjCTT2 +zEzUAh1BjdH3dktXogiFfXHuOP4W8suOx46NXDcZ3f5LF8CT/2uq9l8vta+pV2ci +BAIctGu5z+fEdTCojvCWOvCzYmjtep/yTukT+QIDAQABAoIBAQCRvRjkCRnpUZDW +vPJk7SO3THIplwPeUwtthqLtfedaB4PzphhPmr39GRcyfSNXadENK/39wTbKlhCf +sKaR/RFsib26EnATwCeJwj10uYMuTC73bIxRNn/ISLKiFdtn1YEbmq6meA5rNFO/ +Arkt/juF/8shl6yAGZSrauJJK1mOH8ItMaGR+6tVPWLIZOLk6TiOJhj9SXvMTCw/ +HzgNZMgFGfqnbctg1ki/CY0BkIkYNUTCjhoCwjcgBJl4ERCfTQS6UeEG9Ad+beIH +g8WKzpfjF5+Jnjzqw87aWx1200OdlEdouTt677RXHJFN5naUc+GJZGpmA3RGZA17 +LqA4zBYNAoGBAOGwtg7JQkBOmUC0SiKdXnxG1hVnS4N3DMIDVX2tAe/wWIrP168e +0UpCvswLD+JqO1IgWqw9+QUPnhJSQ9JbYB+678esOTLsT5Yd18VcsiRxSacvQfUw +H4YJaHrFuuFlnxYMlMdPYS3knbIPsft9DVQLFBLL7qPVHbrJ3V6Sn4XrAoGBANNb +mfhgVr5m0n3sQVTlYhWwbJq5K+Htzzl7Xl3JHpMLm2C/GoorP/2zLVhbH20lsE3A +FyIfjcwRxGRu2TXCVnMc4GttlMX5leTxykEd2VrZuEVnTdrudm45Z6sZQpdf1QTg +WebwKgN1eCg7Jkuk5YlRX/KwMtuq4MVzPtOvR+CrAoGAA8uC5DDCKm6n6QyfCoH2 +6sQOKYH5JRbFYiXINDrKg4xZEMx55fnwrvz8VFYDSF1c7f6ZR7grDci7cbdsaIcc +0KvGCGd+9ro+hFmwHSN342D8ShFjXIoYnZpe5WGZyNx6llZT0h4lli338NyOs5ng +tX8SMVa4hoy42UE3tbVldU0CgYA0l/K0b6SmNIfkdcm8Cmhh5UjhJ3rX+Yk7UIum +4skM5jJ/3I4KG8EMrG14MxSa4GoCru4Su69ZPIKWS08ZpYZFlsXxdY8zxGucUN53 +XaochVjpTE9/Tx+BRh+Z3+tGJ76mO/2jDdgmjDCeMjnRUPMdPHaXuWiuaNMNzyOv +IUrNiQKBgGvxEQ0Oe3d/om2Lp/cHbkhZkw/jO/FG5HtodxiO3+1YLhExsDOc5GVn ++x2eNv+dQSIrGagko9TJe1p9WqFnD19Ls+ezqfw2fR5Amg1KHKGUA7k1+Qe/QgoK +D+T4/RkvdGRoBv/il+Rj1rfmMAhEzdD7Axek9a6rUj8geO22kp7I +-----END RSA PRIVATE KEY----- diff --git a/examples/remoteobjects/websockets/common/cert/rootCA.pem b/examples/remoteobjects/websockets/common/cert/rootCA.pem new file mode 100644 index 0000000..7f6cce5 --- /dev/null +++ b/examples/remoteobjects/websockets/common/cert/rootCA.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBTCCAe2gAwIBAgIUV9eILCYaC+qwZHR7OO23uyd2UjwwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHUXRSTyBDQTAeFw0yMTAyMjQxMTMzNTVaFw0yMzA2MTAx +MTMzNTVaMBIxEDAOBgNVBAMMB1F0Uk8gQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQC6VWecfBEXp7ZoBOFKLVxmlSU7dl2rM8i5/vk0FtFjbL1Wb4Vz +IKpSq43E/+oMTTad9AUF389QqpRZCzQRdURVk8Qbqz+XuKidgeQyZZKOH4ABansj +DHcyUcU2L1zleuh3uJLWKCDRiENCWdPTEZ9NdlqxQn+zo242hmI3kvqwQpgNIC6T +PLKpLbdauoThG+wYBqhpomq7UxPwMGoj3Gg+ki/Bv/iB05mfhgKVbZZatWMJNPbM +TNQCHUGN0fd2S1eiCIV9ce44/hbyy47Hjo1cNxnd/ksXwJP/a6r2Xy+1r6lXZyIE +Ahy0a7nP58R1MKiO8JY68LNiaO16n/JO6RP5AgMBAAGjUzBRMB0GA1UdDgQWBBSu +ehS/XLejTiDbCddGU2mMZ1t3CjAfBgNVHSMEGDAWgBSuehS/XLejTiDbCddGU2mM +Z1t3CjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB8JzSuhHPK +cjhLqOHUGMKtKWOd5p9g2D45cAWh6jdzU/AhmslMPbsO5hZkqfE+3xARtcMmQfF2 +k1Qyp3hDTN1ZqHSM6Urq9uR33/wXZbNRUNCD8lAmqKyzF9NF7Q+tmC//IMRtVQhK +aMN3LciyYGQjT0XhDKFWEz9/AvUQD97mLow2m0/izqE4SI6ekQDNL26IiCWFgFjh +ScZjcJ1ogluD2a6sEUGywRXLNV/bdSjRgkAbpvJFrok7dDZ8xCNhOg4xJJQJRWm7 +ZusUydiVyfgrFan6MD+EdldRHjAs8S9BJfZ0RTOWnD9V8auKuVomzKDed54QlXXi +zwowb3Objpqh +-----END CERTIFICATE----- diff --git a/examples/remoteobjects/websockets/common/cert/rootCA.srl b/examples/remoteobjects/websockets/common/cert/rootCA.srl new file mode 100644 index 0000000..d292c9f --- /dev/null +++ b/examples/remoteobjects/websockets/common/cert/rootCA.srl @@ -0,0 +1 @@ +7390820BD47F5A3C7398B7C8DCA218B35C2C2E29 diff --git a/examples/remoteobjects/websockets/common/cert/server.crt b/examples/remoteobjects/websockets/common/cert/server.crt new file mode 100644 index 0000000..df9d55c --- /dev/null +++ b/examples/remoteobjects/websockets/common/cert/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFTCCAf2gAwIBAgIUc5CCC9R/WjxzmLfI3KIYs1wsLigwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHUXRSTyBDQTAeFw0yMTAyMjQxMTMzNTVaFw0yMzA1MzAx +MTMzNTVaMBQxEjAQBgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAOKHte9tB66OD+Um/WkqxHtW3sKrBs4IxKuWAef0UMRt3ld6 +5HvWk+xsCZdPxeL53nMOIy9FS6wKGvEWTwYRR4Id9iX2XQsI4cRJWl25qgCYohnm +Eet9CUkXa3ywbyrSBWFD0r956sS+mwhHU9z05jphd6iZEonHu2b4BFFXMN7+prwj +00EtGbte5wSWWE9ZfXzeGYd4cZBReNCRjaS5XJ3IgjZ4tfxsB3JzBjVafCfnth7r +Is8a2SKCGnhYmV+A6Agth4xtSKDho+BSDYSuMux3dftM/eqtxF0wXzlnX5ApNwGB +zWjcoUL63vjjy17oNEtbs5X2e1g8bGRaGRxGUHUCAwEAAaNhMF8wHwYDVR0jBBgw +FoAUrnoUv1y3o04g2wnXRlNpjGdbdwowCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAw +EwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0B +AQsFAAOCAQEAqhBhxRgG9N1ZghwWC3ZhWSx4BFl3YrStWlQcjffcQ6p8NDxsrkFc +gMG51TmJdaz8J4v2AZW8k9GJlEIaZdV/8czeyEwvjKD4vrUw88waeW7n6o8H+8k+ +ak9fRFvnerFrLEWNpyRqbjJWwm8bQ4T5UKsVNXkZnNLyG2Ha29L9gUHffgSMiyLO +hWqcanPxsMJaDVhw/Gd8JwqaEC1nRPCGxhog2/D2sh4vCj1UykykjPwNz5fP/vfA +VujNCA23eXAdgD3lALHu2WrmyPkQCM7Z61g4k8+v0KjhyJjdLSVTwkPePEo87Fv4 +sn4Jp5gPPBf7jDFKp8PDdbPmk0qN+Wm8gA== +-----END CERTIFICATE----- diff --git a/examples/remoteobjects/websockets/common/cert/server.key b/examples/remoteobjects/websockets/common/cert/server.key new file mode 100644 index 0000000..05144f4 --- /dev/null +++ b/examples/remoteobjects/websockets/common/cert/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA4oe1720Hro4P5Sb9aSrEe1bewqsGzgjEq5YB5/RQxG3eV3rk +e9aT7GwJl0/F4vnecw4jL0VLrAoa8RZPBhFHgh32JfZdCwjhxElaXbmqAJiiGeYR +630JSRdrfLBvKtIFYUPSv3nqxL6bCEdT3PTmOmF3qJkSice7ZvgEUVcw3v6mvCPT +QS0Zu17nBJZYT1l9fN4Zh3hxkFF40JGNpLlcnciCNni1/GwHcnMGNVp8J+e2Husi +zxrZIoIaeFiZX4DoCC2HjG1IoOGj4FINhK4y7Hd1+0z96q3EXTBfOWdfkCk3AYHN +aNyhQvre+OPLXug0S1uzlfZ7WDxsZFoZHEZQdQIDAQABAoIBAGuKEYzALc1oE5Ot +ls++RdhnvQidOHXHI9ZxOCZtjYoyvkK5TI6dp0utXkA+1qqSBFCKfZmLRAlAItog +xRMUUOYsYxZShokehk8wo32rDlGKJCo3Vnp8uVPBkn13JM8nNPafxASyVAlikyay +9dUHTeSZML0RLgPKleSkCSi0Q7cYOFG/HB9aNjp8F5rdut02KrmC3cxlHKF7QXXG +VU+op1Z9o0V2/iUFJnF5CR40sW2THEbBJkkeYwbvUTnavz4XQtZst//DKsDQEe2r +UrhsIHduvG4tWiBE77m1vyigTxUWCsLQ2KCnn9O+4KyTg9HWCiQ2QSU3istX/rpI +zN2lOgECgYEA9PVVMnY+t59Q73IQ9LRg5KRqg6YyGQOrwJKbCUxDrA6ikh3MDgwV +CkC6Jdl6e4DMog51l3CizrfR2+mtNSTUJDGFE1iGgI+Faem4aopRtFRiLWJ8n4m7 +U8pl3XTP0XFT68aBCAE6O/xVPXs0I/eKNvaF5vokB5zm4R79o37WP+UCgYEA7L26 +TiCFA73Fil/bPupqWJnvm896RlO1S+IBOKlPyCHVvxiGLvtv+YTucCFwXQ4FeNRh +bQEWlURsgeNr7PHATtFUZ/zo/7l1WYNLXZDZwWD+JYllVPwskJOJMx5Rc77Q0aQ6 +7v60XMGwD5cxQ29RHuJs09Iwc9b1WqwOAEJAJVECgYBNsxQXMZKrRAm0KgZe2Ghz +ngN7RthVPujX6KjsxhghF3NRzcnQGt0Bp45kOxuy2SQPs25xXvUFhSE4FGMwnEH+ +SQbhIA9p8BxtgAlTIhTQkoOhyb+mC1Y0Odsd59OTp9Lq0shS9bC3Hk8bdV0Qm5Bn +5sKKhYWwNIC3n9Dsb2seUQKBgAS7biPtpnsCqhYwAFPrn6CRwyZcKVeKiM8xf1DA +oaWgd4NQXC5IPF7Cd3mqUXKquxVFOYVSRj9JlNmr0BZ2Zp+ss4E4nvetn1jgtPrz +0EZ7R9k8O9hNCh8Bs/ZfnsUvhUELhVoNoVFRVdGZ9hQg/4AcioxZYTqPi2v6kHUU +3e9hAoGAec7anF5TiTx2jjcDFS9hrRw0w2PsNX24qjqPFqeuzDIorh6rq4Ip4aA0 +7rxeIXmxjmYA7pPCT9rPxtpEp4BQovF9kHMutd8lyB4rGbLpNpOY4m5v8Oo7cLQ3 +kLAwE+jrEwLNtuq+kUlGwK7YLeiGUm4Rsof5IXlSkXzL/99gHC4= +-----END RSA PRIVATE KEY----- diff --git a/examples/remoteobjects/websockets/common/common.pri b/examples/remoteobjects/websockets/common/common.pri new file mode 100644 index 0000000..4ee7eef --- /dev/null +++ b/examples/remoteobjects/websockets/common/common.pri @@ -0,0 +1,11 @@ +CONFIG -= app_bundle + +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/websocketiodevice.h + +SOURCES += \ + $$PWD/websocketiodevice.cpp + +RESOURCES += $$PWD/cert/cert.qrc diff --git a/examples/remoteobjects/websockets/common/websocketiodevice.cpp b/examples/remoteobjects/websockets/common/websocketiodevice.cpp new file mode 100644 index 0000000..537d9cf --- /dev/null +++ b/examples/remoteobjects/websockets/common/websocketiodevice.cpp @@ -0,0 +1,52 @@ +// Copyright (C) 2019 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "websocketiodevice.h" + +#include + +WebSocketIoDevice::WebSocketIoDevice(QWebSocket *webSocket, QObject *parent) + : QIODevice(parent) + , m_socket(webSocket) +{ + open(QIODevice::ReadWrite); + connect(webSocket, &QWebSocket::disconnected, this, &WebSocketIoDevice::disconnected); + connect(webSocket, &QWebSocket::binaryMessageReceived, this, [this](const QByteArray &message){ + m_buffer.append(message); + emit readyRead(); + }); + connect(webSocket, &QWebSocket::bytesWritten, this, &WebSocketIoDevice::bytesWritten); +} + +qint64 WebSocketIoDevice::bytesAvailable() const +{ + return QIODevice::bytesAvailable() + m_buffer.size(); +} + +bool WebSocketIoDevice::isSequential() const +{ + return true; +} + +void WebSocketIoDevice::close() +{ + if (m_socket) + m_socket->close(); +} + +qint64 WebSocketIoDevice::readData(char *data, qint64 maxlen) +{ + auto sz = std::min(maxlen, qint64(m_buffer.size())); + if (sz <= 0) + return sz; + memcpy(data, m_buffer.constData(), size_t(sz)); + m_buffer.remove(0, sz); + return sz; +} + +qint64 WebSocketIoDevice::writeData(const char *data, qint64 len) +{ + if (m_socket) + return m_socket->sendBinaryMessage(QByteArray{data, int(len)}); + return -1; +} diff --git a/examples/remoteobjects/websockets/common/websocketiodevice.h b/examples/remoteobjects/websockets/common/websocketiodevice.h new file mode 100644 index 0000000..433c5cf --- /dev/null +++ b/examples/remoteobjects/websockets/common/websocketiodevice.h @@ -0,0 +1,37 @@ +// Copyright (C) 2019 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef WEBSOCKETIODEVICE_H +#define WEBSOCKETIODEVICE_H + +#include +#include +#include + +class QWebSocket; + +class WebSocketIoDevice : public QIODevice +{ + Q_OBJECT +public: + WebSocketIoDevice(QWebSocket *webSocket, QObject *parent = nullptr); + +signals: + void disconnected(); + + // QIODevice interface +public: + qint64 bytesAvailable() const override; + bool isSequential() const override; + void close() override; + +protected: + qint64 readData(char *data, qint64 maxlen) override; + qint64 writeData(const char *data, qint64 len) override; + +private: + QPointer m_socket; + QByteArray m_buffer; +}; + +#endif // WEBSOCKETIODEVICE_H diff --git a/examples/remoteobjects/websockets/doc/src/websocket.qdoc b/examples/remoteobjects/websockets/doc/src/websocket.qdoc new file mode 100644 index 0000000..2fadb89 --- /dev/null +++ b/examples/remoteobjects/websockets/doc/src/websocket.qdoc @@ -0,0 +1,11 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example websockets + \title QtRemoteObjects WebSockets Example + + This example shows how you could use a non-QIODevice-based transport + (QWebSocket) with QtRemoteObjects. This is achieved by implementing a + small QIODevice-derived wrapper for QWebSocket. +*/ diff --git a/examples/remoteobjects/websockets/websockets.pro b/examples/remoteobjects/websockets/websockets.pro new file mode 100644 index 0000000..bb2685a --- /dev/null +++ b/examples/remoteobjects/websockets/websockets.pro @@ -0,0 +1,7 @@ +TEMPLATE = subdirs + +qtHaveModule(widgets): qtHaveModule(websockets) { + SUBDIRS += \ + wsclient \ + wsserver +} diff --git a/examples/remoteobjects/websockets/wsclient/CMakeLists.txt b/examples/remoteobjects/websockets/wsclient/CMakeLists.txt new file mode 100644 index 0000000..fb121be --- /dev/null +++ b/examples/remoteobjects/websockets/wsclient/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 3.16) +project(wsclient LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/websockets/wsclient") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui RemoteObjects WebSockets Widgets) + +qt_add_executable(wsclient + ../common/websocketiodevice.cpp ../common/websocketiodevice.h + main.cpp +) + +set_target_properties(wsclient PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE FALSE +) + +target_include_directories(wsclient PUBLIC + ../common +) + +target_link_libraries(wsclient PUBLIC + Qt::Core + Qt::Gui + Qt::RemoteObjects + Qt::WebSockets + Qt::Widgets +) + +# Resources: +set(cert_resource_files + "../common/cert/client.crt" + "../common/cert/client.key" + "../common/cert/rootCA.key" + "../common/cert/rootCA.pem" + "../common/cert/rootCA.srl" + "../common/cert/server.crt" + "../common/cert/server.key" +) + +qt6_add_resources(wsclient "cert" + PREFIX + "/sslcert" + BASE + "../common/cert" + FILES + ${cert_resource_files} +) + +install(TARGETS wsclient + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/websockets/wsclient/main.cpp b/examples/remoteobjects/websockets/wsclient/main.cpp new file mode 100644 index 0000000..0d6387b --- /dev/null +++ b/examples/remoteobjects/websockets/wsclient/main.cpp @@ -0,0 +1,60 @@ +// Copyright (C) 2019 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include +#include +#include + +#ifndef QT_NO_SSL +# include +# include +# include +#endif +#include "websocketiodevice.h" + +int main(int argc, char **argv) +{ + + QLoggingCategory::setFilterRules("qt.remoteobjects.debug=false\n" + "qt.remoteobjects.warning=false\n" + "qt.remoteobjects.models.debug=false\n" + "qt.remoteobjects.models.debug=false"); + + QApplication app(argc, argv); + + + + QScopedPointer webSocket{new QWebSocket}; + WebSocketIoDevice socket(webSocket.data()); +#ifndef QT_NO_SSL + // Always use secure connections when available + QSslConfiguration sslConf; + QFile certFile(QStringLiteral(":/sslcert/client.crt")); + if (!certFile.open(QIODevice::ReadOnly)) + qFatal("Can't open client.crt file"); + sslConf.setLocalCertificate(QSslCertificate{certFile.readAll()}); + + QFile keyFile(QStringLiteral(":/sslcert/client.key")); + if (!keyFile.open(QIODevice::ReadOnly)) + qFatal("Can't open client.key file"); + sslConf.setPrivateKey(QSslKey{keyFile.readAll(), QSsl::Rsa}); + + sslConf.setPeerVerifyMode(QSslSocket::VerifyPeer); + webSocket->setSslConfiguration(sslConf); +#endif + QRemoteObjectNode node; + node.addClientSideConnection(&socket); + node.setHeartbeatInterval(1000); + webSocket->open(QStringLiteral("ws://localhost:8088")); + + QTreeView view; + view.setWindowTitle(QStringLiteral("RemoteView")); + view.resize(640,480); + QScopedPointer model(node.acquireModel(QStringLiteral("RemoteModel"))); + view.setModel(model.data()); + view.show(); + + return app.exec(); +} diff --git a/examples/remoteobjects/websockets/wsclient/wsclient.pro b/examples/remoteobjects/websockets/wsclient/wsclient.pro new file mode 100644 index 0000000..f329e4a --- /dev/null +++ b/examples/remoteobjects/websockets/wsclient/wsclient.pro @@ -0,0 +1,9 @@ +QT += widgets remoteobjects websockets +requires(qtConfig(treeview)) + +SOURCES += main.cpp + +include(../common/common.pri) + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/websockets/wsclient +INSTALLS += target diff --git a/examples/remoteobjects/websockets/wsserver/CMakeLists.txt b/examples/remoteobjects/websockets/wsserver/CMakeLists.txt new file mode 100644 index 0000000..34e53b3 --- /dev/null +++ b/examples/remoteobjects/websockets/wsserver/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 3.16) +project(wsserver LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/remoteobjects/websockets/wsserver") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui RemoteObjects WebSockets Widgets) + +qt_add_executable(wsserver + ../common/websocketiodevice.cpp ../common/websocketiodevice.h + main.cpp +) + +set_target_properties(wsserver PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE FALSE +) + +target_include_directories(wsserver PUBLIC + ../common +) + +target_link_libraries(wsserver PUBLIC + Qt::Core + Qt::Gui + Qt::RemoteObjects + Qt::WebSockets + Qt::Widgets +) + +# Resources: +set(cert_resource_files + "../common/cert/client.crt" + "../common/cert/client.key" + "../common/cert/rootCA.key" + "../common/cert/rootCA.pem" + "../common/cert/rootCA.srl" + "../common/cert/server.crt" + "../common/cert/server.key" +) + +qt6_add_resources(wsserver "cert" + PREFIX + "/sslcert" + BASE + "../common/cert" + FILES + ${cert_resource_files} +) + +install(TARGETS wsserver + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/remoteobjects/websockets/wsserver/main.cpp b/examples/remoteobjects/websockets/wsserver/main.cpp new file mode 100644 index 0000000..4c0dd67 --- /dev/null +++ b/examples/remoteobjects/websockets/wsserver/main.cpp @@ -0,0 +1,160 @@ +// Copyright (C) 2019 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_SSL +# include +# include +# include +#endif + +#include "websocketiodevice.h" + +struct TimerHandler : public QObject +{ + Q_OBJECT +public: + QStandardItemModel *model; +public Q_SLOTS: + void changeData() { + Q_ASSERT(model); + Q_ASSERT(model->rowCount() > 50); + Q_ASSERT(model->columnCount() > 1); + for (int i = 10; i < 50; ++i) + model->setData(model->index(i, 1), QColor(Qt::blue), Qt::BackgroundRole); + } + void insertData() { + Q_ASSERT(model); + Q_ASSERT(model->rowCount() > 50); + Q_ASSERT(model->columnCount() > 1); + model->insertRows(2, 9); + for (int i = 2; i < 11; ++i) { + model->setData(model->index(i, 1), QColor(Qt::green), Qt::BackgroundRole); + model->setData(model->index(i, 1), QLatin1String("InsertedRow"), Qt::DisplayRole); + } + } + void removeData() { + model->removeRows(2, 4); + } + + void changeFlags() { + QStandardItem *item = model->item(0, 0); + item->setEnabled(false); + item = item->child(0, 0); + item->setFlags(item->flags() & Qt::ItemIsSelectable); + } + + void moveData() { + model->moveRows(QModelIndex(), 2, 4, QModelIndex(), 10); + } +}; + +QList addChild(int numChildren, int nestingLevel) +{ + QList result; + if (nestingLevel == 0) + return result; + for (int i = 0; i < numChildren; ++i) { + QStandardItem *child = new QStandardItem(QStringLiteral("Child num %1, nesting Level %2").arg(i+1).arg(nestingLevel)); + if (i == 0) + child->appendRow(addChild(numChildren, nestingLevel -1)); + result.push_back(child); + } + return result; +} + +int main(int argc, char *argv[]) +{ + QLoggingCategory::setFilterRules("qt.remoteobjects.debug=false\n" + "qt.remoteobjects.warning=false"); + QApplication app(argc, argv); + + const int modelSize = 100000; + QStringList list; + QStandardItemModel sourceModel; + QStringList hHeaderList; + hHeaderList << QStringLiteral("First Column with spacing") << QStringLiteral("Second Column with spacing"); + sourceModel.setHorizontalHeaderLabels(hHeaderList); + list.reserve(modelSize); + for (int i = 0; i < modelSize; ++i) { + QStandardItem *firstItem = new QStandardItem(QStringLiteral("FancyTextNumber %1").arg(i)); + if (i == 0) + firstItem->appendRow(addChild(2, 2)); + QStandardItem *secondItem = new QStandardItem(QStringLiteral("FancyRow2TextNumber %1").arg(i)); + if (i % 2 == 0) + firstItem->setBackground(Qt::red); + QList row; + row << firstItem << secondItem; + sourceModel.invisibleRootItem()->appendRow(row); + //sourceModel.appendRow(row); + list << QStringLiteral("FancyTextNumber %1").arg(i); + } + + // Needed by QMLModelViewClient + QHash roleNames = { + {Qt::DisplayRole, "_text"}, + {Qt::BackgroundRole, "_color"} + }; + sourceModel.setItemRoleNames(roleNames); + + QList roles; + roles << Qt::DisplayRole << Qt::BackgroundRole; + + QWebSocketServer webSockServer{QStringLiteral("WS QtRO"), QWebSocketServer::NonSecureMode}; + webSockServer.listen(QHostAddress::Any, 8088); + + QRemoteObjectHost hostNode; + hostNode.setHostUrl(webSockServer.serverAddress().toString(), QRemoteObjectHost::AllowExternalRegistration); + + hostNode.enableRemoting(&sourceModel, QStringLiteral("RemoteModel"), roles); + + QObject::connect(&webSockServer, &QWebSocketServer::newConnection, &hostNode, [&hostNode, &webSockServer]{ + while (auto conn = webSockServer.nextPendingConnection()) { +#ifndef QT_NO_SSL + // Always use secure connections when available + QSslConfiguration sslConf; + QFile certFile(QStringLiteral(":/sslcert/server.crt")); + if (!certFile.open(QIODevice::ReadOnly)) + qFatal("Can't open client.crt file"); + sslConf.setLocalCertificate(QSslCertificate{certFile.readAll()}); + + QFile keyFile(QStringLiteral(":/sslcert/server.key")); + if (!keyFile.open(QIODevice::ReadOnly)) + qFatal("Can't open client.key file"); + sslConf.setPrivateKey(QSslKey{keyFile.readAll(), QSsl::Rsa}); + + sslConf.setPeerVerifyMode(QSslSocket::VerifyPeer); + conn->setSslConfiguration(sslConf); + QObject::connect(conn, &QWebSocket::sslErrors, conn, &QWebSocket::deleteLater); +#endif + QObject::connect(conn, &QWebSocket::disconnected, conn, &QWebSocket::deleteLater); + QObject::connect(conn, QOverload::of(&QWebSocket::error), conn, &QWebSocket::deleteLater); + auto ioDevice = new WebSocketIoDevice(conn); + QObject::connect(conn, &QWebSocket::destroyed, ioDevice, &WebSocketIoDevice::deleteLater); + hostNode.addHostSideConnection(ioDevice); + } + }); + + QTreeView view; + view.setWindowTitle(QStringLiteral("SourceView")); + view.setModel(&sourceModel); + view.show(); + TimerHandler handler; + handler.model = &sourceModel; + QTimer::singleShot(5000, &handler, &TimerHandler::changeData); + QTimer::singleShot(10000, &handler, &TimerHandler::insertData); + QTimer::singleShot(11000, &handler, &TimerHandler::changeFlags); + QTimer::singleShot(12000, &handler, &TimerHandler::removeData); + QTimer::singleShot(13000, &handler, &TimerHandler::moveData); + + return app.exec(); +} + +#include "main.moc" diff --git a/examples/remoteobjects/websockets/wsserver/wsserver.pro b/examples/remoteobjects/websockets/wsserver/wsserver.pro new file mode 100644 index 0000000..e892091 --- /dev/null +++ b/examples/remoteobjects/websockets/wsserver/wsserver.pro @@ -0,0 +1,9 @@ +QT += widgets remoteobjects websockets +requires(qtConfig(treeview)) + +SOURCES += main.cpp + +include(../common/common.pri) + +target.path = $$[QT_INSTALL_EXAMPLES]/remoteobjects/websockets/wsserver +INSTALLS += target diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_local_backend_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_local_backend_p.h new file mode 100644 index 0000000..a056f08 --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_local_backend_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qconnection_local_backend_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_qnx_backend_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_qnx_backend_p.h new file mode 100644 index 0000000..3d676da --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_qnx_backend_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qconnection_qnx_backend_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_qnx_global_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_qnx_global_p.h new file mode 100644 index 0000000..b1df3a5 --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_qnx_global_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qconnection_qnx_global_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_qnx_qiodevices_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_qnx_qiodevices_p.h new file mode 100644 index 0000000..fb61d2b --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_qnx_qiodevices_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qconnection_qnx_qiodevices_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_qnx_server_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_qnx_server_p.h new file mode 100644 index 0000000..c9c4733 --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_qnx_server_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qconnection_qnx_server_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_tcpip_backend_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_tcpip_backend_p.h new file mode 100644 index 0000000..5b920f2 --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnection_tcpip_backend_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qconnection_tcpip_backend_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnectionfactories_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnectionfactories_p.h new file mode 100644 index 0000000..501d938 --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qconnectionfactories_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qconnectionfactories_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectabstractitemmodeladapter_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectabstractitemmodeladapter_p.h new file mode 100644 index 0000000..e9c8964 --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectabstractitemmodeladapter_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qremoteobjectabstractitemmodeladapter_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectabstractitemmodelreplica_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectabstractitemmodelreplica_p.h new file mode 100644 index 0000000..64da33f --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectabstractitemmodelreplica_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qremoteobjectabstractitemmodelreplica_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectabstractitemmodeltypes_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectabstractitemmodeltypes_p.h new file mode 100644 index 0000000..646a4bc --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectabstractitemmodeltypes_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qremoteobjectabstractitemmodeltypes_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectcontainers_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectcontainers_p.h new file mode 100644 index 0000000..8d637d5 --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectcontainers_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qremoteobjectcontainers_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectnode_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectnode_p.h new file mode 100644 index 0000000..be73dae --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectnode_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qremoteobjectnode_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectpacket_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectpacket_p.h new file mode 100644 index 0000000..bdad605 --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectpacket_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qremoteobjectpacket_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectpendingcall_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectpendingcall_p.h new file mode 100644 index 0000000..7c2b703 --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectpendingcall_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qremoteobjectpendingcall_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectregistrysource_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectregistrysource_p.h new file mode 100644 index 0000000..bb6f128 --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectregistrysource_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qremoteobjectregistrysource_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectreplica_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectreplica_p.h new file mode 100644 index 0000000..f457bca --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectreplica_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qremoteobjectreplica_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectsource_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectsource_p.h new file mode 100644 index 0000000..b7e50ab --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectsource_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qremoteobjectsource_p.h" diff --git a/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectsourceio_p.h b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectsourceio_p.h new file mode 100644 index 0000000..20eeb5e --- /dev/null +++ b/include/QtRemoteObjects/6.4.2/QtRemoteObjects/private/qremoteobjectsourceio_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjects/qremoteobjectsourceio_p.h" diff --git a/include/QtRemoteObjects/QAbstractItemModelReplica b/include/QtRemoteObjects/QAbstractItemModelReplica new file mode 100644 index 0000000..790a361 --- /dev/null +++ b/include/QtRemoteObjects/QAbstractItemModelReplica @@ -0,0 +1 @@ +#include "qremoteobjectabstractitemmodelreplica.h" diff --git a/include/QtRemoteObjects/QConnectionAbstractServer b/include/QtRemoteObjects/QConnectionAbstractServer new file mode 100644 index 0000000..9306cd4 --- /dev/null +++ b/include/QtRemoteObjects/QConnectionAbstractServer @@ -0,0 +1 @@ +#include "qconnectionfactories.h" diff --git a/include/QtRemoteObjects/QIOQnxSource b/include/QtRemoteObjects/QIOQnxSource new file mode 100644 index 0000000..bd609a9 --- /dev/null +++ b/include/QtRemoteObjects/QIOQnxSource @@ -0,0 +1 @@ +#include "qconnection_qnx_qiodevices.h" diff --git a/include/QtRemoteObjects/QIntHash b/include/QtRemoteObjects/QIntHash new file mode 100644 index 0000000..46b3e1b --- /dev/null +++ b/include/QtRemoteObjects/QIntHash @@ -0,0 +1 @@ +#include "qtremoteobjectglobal.h" diff --git a/include/QtRemoteObjects/QQnxNativeIo b/include/QtRemoteObjects/QQnxNativeIo new file mode 100644 index 0000000..bd609a9 --- /dev/null +++ b/include/QtRemoteObjects/QQnxNativeIo @@ -0,0 +1 @@ +#include "qconnection_qnx_qiodevices.h" diff --git a/include/QtRemoteObjects/QQnxNativeServer b/include/QtRemoteObjects/QQnxNativeServer new file mode 100644 index 0000000..ee615ae --- /dev/null +++ b/include/QtRemoteObjects/QQnxNativeServer @@ -0,0 +1 @@ +#include "qconnection_qnx_server.h" diff --git a/include/QtRemoteObjects/QRemoteObjectAbstractPersistedStore b/include/QtRemoteObjects/QRemoteObjectAbstractPersistedStore new file mode 100644 index 0000000..789612f --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectAbstractPersistedStore @@ -0,0 +1 @@ +#include "qremoteobjectnode.h" diff --git a/include/QtRemoteObjects/QRemoteObjectDynamicReplica b/include/QtRemoteObjects/QRemoteObjectDynamicReplica new file mode 100644 index 0000000..3c8ac01 --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectDynamicReplica @@ -0,0 +1 @@ +#include "qremoteobjectdynamicreplica.h" diff --git a/include/QtRemoteObjects/QRemoteObjectHost b/include/QtRemoteObjects/QRemoteObjectHost new file mode 100644 index 0000000..789612f --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectHost @@ -0,0 +1 @@ +#include "qremoteobjectnode.h" diff --git a/include/QtRemoteObjects/QRemoteObjectHostBase b/include/QtRemoteObjects/QRemoteObjectHostBase new file mode 100644 index 0000000..789612f --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectHostBase @@ -0,0 +1 @@ +#include "qremoteobjectnode.h" diff --git a/include/QtRemoteObjects/QRemoteObjectNode b/include/QtRemoteObjects/QRemoteObjectNode new file mode 100644 index 0000000..789612f --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectNode @@ -0,0 +1 @@ +#include "qremoteobjectnode.h" diff --git a/include/QtRemoteObjects/QRemoteObjectPendingCall b/include/QtRemoteObjects/QRemoteObjectPendingCall new file mode 100644 index 0000000..c2db880 --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectPendingCall @@ -0,0 +1 @@ +#include "qremoteobjectpendingcall.h" diff --git a/include/QtRemoteObjects/QRemoteObjectPendingCallWatcher b/include/QtRemoteObjects/QRemoteObjectPendingCallWatcher new file mode 100644 index 0000000..c2db880 --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectPendingCallWatcher @@ -0,0 +1 @@ +#include "qremoteobjectpendingcall.h" diff --git a/include/QtRemoteObjects/QRemoteObjectPendingReply b/include/QtRemoteObjects/QRemoteObjectPendingReply new file mode 100644 index 0000000..c2db880 --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectPendingReply @@ -0,0 +1 @@ +#include "qremoteobjectpendingcall.h" diff --git a/include/QtRemoteObjects/QRemoteObjectRegistry b/include/QtRemoteObjects/QRemoteObjectRegistry new file mode 100644 index 0000000..5a3cc82 --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectRegistry @@ -0,0 +1 @@ +#include "qremoteobjectregistry.h" diff --git a/include/QtRemoteObjects/QRemoteObjectRegistryHost b/include/QtRemoteObjects/QRemoteObjectRegistryHost new file mode 100644 index 0000000..789612f --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectRegistryHost @@ -0,0 +1 @@ +#include "qremoteobjectnode.h" diff --git a/include/QtRemoteObjects/QRemoteObjectReplica b/include/QtRemoteObjects/QRemoteObjectReplica new file mode 100644 index 0000000..62475a9 --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectReplica @@ -0,0 +1 @@ +#include "qremoteobjectreplica.h" diff --git a/include/QtRemoteObjects/QRemoteObjectSettingsStore b/include/QtRemoteObjects/QRemoteObjectSettingsStore new file mode 100644 index 0000000..6a38595 --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectSettingsStore @@ -0,0 +1 @@ +#include "qremoteobjectsettingsstore.h" diff --git a/include/QtRemoteObjects/QRemoteObjectSourceLocation b/include/QtRemoteObjects/QRemoteObjectSourceLocation new file mode 100644 index 0000000..46b3e1b --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectSourceLocation @@ -0,0 +1 @@ +#include "qtremoteobjectglobal.h" diff --git a/include/QtRemoteObjects/QRemoteObjectSourceLocationInfo b/include/QtRemoteObjects/QRemoteObjectSourceLocationInfo new file mode 100644 index 0000000..46b3e1b --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectSourceLocationInfo @@ -0,0 +1 @@ +#include "qtremoteobjectglobal.h" diff --git a/include/QtRemoteObjects/QRemoteObjectSourceLocations b/include/QtRemoteObjects/QRemoteObjectSourceLocations new file mode 100644 index 0000000..46b3e1b --- /dev/null +++ b/include/QtRemoteObjects/QRemoteObjectSourceLocations @@ -0,0 +1 @@ +#include "qtremoteobjectglobal.h" diff --git a/include/QtRemoteObjects/QtROClientFactory b/include/QtRemoteObjects/QtROClientFactory new file mode 100644 index 0000000..9306cd4 --- /dev/null +++ b/include/QtRemoteObjects/QtROClientFactory @@ -0,0 +1 @@ +#include "qconnectionfactories.h" diff --git a/include/QtRemoteObjects/QtROClientIoDevice b/include/QtRemoteObjects/QtROClientIoDevice new file mode 100644 index 0000000..9306cd4 --- /dev/null +++ b/include/QtRemoteObjects/QtROClientIoDevice @@ -0,0 +1 @@ +#include "qconnectionfactories.h" diff --git a/include/QtRemoteObjects/QtROIoDeviceBase b/include/QtRemoteObjects/QtROIoDeviceBase new file mode 100644 index 0000000..9306cd4 --- /dev/null +++ b/include/QtRemoteObjects/QtROIoDeviceBase @@ -0,0 +1 @@ +#include "qconnectionfactories.h" diff --git a/include/QtRemoteObjects/QtROServerFactory b/include/QtRemoteObjects/QtROServerFactory new file mode 100644 index 0000000..9306cd4 --- /dev/null +++ b/include/QtRemoteObjects/QtROServerFactory @@ -0,0 +1 @@ +#include "qconnectionfactories.h" diff --git a/include/QtRemoteObjects/QtROServerIoDevice b/include/QtRemoteObjects/QtROServerIoDevice new file mode 100644 index 0000000..9306cd4 --- /dev/null +++ b/include/QtRemoteObjects/QtROServerIoDevice @@ -0,0 +1 @@ +#include "qconnectionfactories.h" diff --git a/include/QtRemoteObjects/QtRemoteObjects b/include/QtRemoteObjects/QtRemoteObjects new file mode 100644 index 0000000..e5053ec --- /dev/null +++ b/include/QtRemoteObjects/QtRemoteObjects @@ -0,0 +1,15 @@ +#ifndef QT_QTREMOTEOBJECTS_MODULE_H +#define QT_QTREMOTEOBJECTS_MODULE_H +#include +#include "qtremoteobjectglobal.h" +#include "qconnectionfactories.h" +#include "qremoteobjectabstractitemmodelreplica.h" +#include "qremoteobjectdynamicreplica.h" +#include "qremoteobjectnode.h" +#include "qremoteobjectpendingcall.h" +#include "qremoteobjectregistry.h" +#include "qremoteobjectreplica.h" +#include "qremoteobjectsettingsstore.h" +#include "qremoteobjectsource.h" +#include "qtremoteobjectsversion.h" +#endif diff --git a/include/QtRemoteObjects/QtRemoteObjectsVersion b/include/QtRemoteObjects/QtRemoteObjectsVersion new file mode 100644 index 0000000..7cce23e --- /dev/null +++ b/include/QtRemoteObjects/QtRemoteObjectsVersion @@ -0,0 +1 @@ +#include "qtremoteobjectsversion.h" diff --git a/include/QtRemoteObjects/headers.pri b/include/QtRemoteObjects/headers.pri new file mode 100644 index 0000000..0da051c --- /dev/null +++ b/include/QtRemoteObjects/headers.pri @@ -0,0 +1,6 @@ +SYNCQT.HEADER_FILES = qconnection_qnx_qiodevices.h qconnection_qnx_server.h qconnectionfactories.h qremoteobjectabstractitemmodelreplica.h qremoteobjectdynamicreplica.h qremoteobjectnode.h qremoteobjectpendingcall.h qremoteobjectregistry.h qremoteobjectreplica.h qremoteobjectsettingsstore.h qremoteobjectsource.h qtremoteobjectglobal.h +SYNCQT.GENERATED_HEADER_FILES = QQnxNativeIo QIOQnxSource QQnxNativeServer QtROIoDeviceBase QtROServerIoDevice QConnectionAbstractServer QtROClientIoDevice QtROServerFactory QtROClientFactory QAbstractItemModelReplica QRemoteObjectDynamicReplica QRemoteObjectAbstractPersistedStore QRemoteObjectNode QRemoteObjectHostBase QRemoteObjectHost QRemoteObjectRegistryHost QRemoteObjectPendingCall QRemoteObjectPendingCallWatcher QRemoteObjectPendingReply QRemoteObjectRegistry QRemoteObjectReplica QRemoteObjectSettingsStore QRemoteObjectSourceLocationInfo QRemoteObjectSourceLocation QRemoteObjectSourceLocations QIntHash qtremoteobjectsversion.h QtRemoteObjectsVersion QtRemoteObjects +SYNCQT.PRIVATE_HEADER_FILES = qconnection_local_backend_p.h qconnection_qnx_backend_p.h qconnection_qnx_global_p.h qconnection_qnx_qiodevices_p.h qconnection_qnx_server_p.h qconnection_tcpip_backend_p.h qconnectionfactories_p.h qremoteobjectabstractitemmodeladapter_p.h qremoteobjectabstractitemmodelreplica_p.h qremoteobjectabstractitemmodeltypes_p.h qremoteobjectcontainers_p.h qremoteobjectnode_p.h qremoteobjectpacket_p.h qremoteobjectpendingcall_p.h qremoteobjectregistrysource_p.h qremoteobjectreplica_p.h qremoteobjectsource_p.h qremoteobjectsourceio_p.h +SYNCQT.QPA_HEADER_FILES = +SYNCQT.CLEAN_HEADER_FILES = qconnection_qnx_qiodevices.h qconnection_qnx_server.h qconnectionfactories.h qremoteobjectabstractitemmodelreplica.h qremoteobjectdynamicreplica.h qremoteobjectnode.h qremoteobjectpendingcall.h qremoteobjectregistry.h qremoteobjectreplica.h qremoteobjectsettingsstore.h qremoteobjectsource.h qtremoteobjectglobal.h +SYNCQT.INJECTIONS = diff --git a/include/QtRemoteObjects/qconnection_qnx_qiodevices.h b/include/QtRemoteObjects/qconnection_qnx_qiodevices.h new file mode 100644 index 0000000..8b42dad --- /dev/null +++ b/include/QtRemoteObjects/qconnection_qnx_qiodevices.h @@ -0,0 +1 @@ +#include "../../src/remoteobjects/qconnection_qnx_qiodevices.h" diff --git a/include/QtRemoteObjects/qconnection_qnx_server.h b/include/QtRemoteObjects/qconnection_qnx_server.h new file mode 100644 index 0000000..5a13ab4 --- /dev/null +++ b/include/QtRemoteObjects/qconnection_qnx_server.h @@ -0,0 +1 @@ +#include "../../src/remoteobjects/qconnection_qnx_server.h" diff --git a/include/QtRemoteObjects/qconnectionfactories.h b/include/QtRemoteObjects/qconnectionfactories.h new file mode 100644 index 0000000..e6e32f6 --- /dev/null +++ b/include/QtRemoteObjects/qconnectionfactories.h @@ -0,0 +1 @@ +#include "../../src/remoteobjects/qconnectionfactories.h" diff --git a/include/QtRemoteObjects/qremoteobjectabstractitemmodelreplica.h b/include/QtRemoteObjects/qremoteobjectabstractitemmodelreplica.h new file mode 100644 index 0000000..1498e88 --- /dev/null +++ b/include/QtRemoteObjects/qremoteobjectabstractitemmodelreplica.h @@ -0,0 +1 @@ +#include "../../src/remoteobjects/qremoteobjectabstractitemmodelreplica.h" diff --git a/include/QtRemoteObjects/qremoteobjectdynamicreplica.h b/include/QtRemoteObjects/qremoteobjectdynamicreplica.h new file mode 100644 index 0000000..b889d05 --- /dev/null +++ b/include/QtRemoteObjects/qremoteobjectdynamicreplica.h @@ -0,0 +1 @@ +#include "../../src/remoteobjects/qremoteobjectdynamicreplica.h" diff --git a/include/QtRemoteObjects/qremoteobjectnode.h b/include/QtRemoteObjects/qremoteobjectnode.h new file mode 100644 index 0000000..14ce150 --- /dev/null +++ b/include/QtRemoteObjects/qremoteobjectnode.h @@ -0,0 +1 @@ +#include "../../src/remoteobjects/qremoteobjectnode.h" diff --git a/include/QtRemoteObjects/qremoteobjectpendingcall.h b/include/QtRemoteObjects/qremoteobjectpendingcall.h new file mode 100644 index 0000000..aaf584d --- /dev/null +++ b/include/QtRemoteObjects/qremoteobjectpendingcall.h @@ -0,0 +1 @@ +#include "../../src/remoteobjects/qremoteobjectpendingcall.h" diff --git a/include/QtRemoteObjects/qremoteobjectregistry.h b/include/QtRemoteObjects/qremoteobjectregistry.h new file mode 100644 index 0000000..6b95543 --- /dev/null +++ b/include/QtRemoteObjects/qremoteobjectregistry.h @@ -0,0 +1 @@ +#include "../../src/remoteobjects/qremoteobjectregistry.h" diff --git a/include/QtRemoteObjects/qremoteobjectreplica.h b/include/QtRemoteObjects/qremoteobjectreplica.h new file mode 100644 index 0000000..52bdfa8 --- /dev/null +++ b/include/QtRemoteObjects/qremoteobjectreplica.h @@ -0,0 +1 @@ +#include "../../src/remoteobjects/qremoteobjectreplica.h" diff --git a/include/QtRemoteObjects/qremoteobjectsettingsstore.h b/include/QtRemoteObjects/qremoteobjectsettingsstore.h new file mode 100644 index 0000000..aa40e8c --- /dev/null +++ b/include/QtRemoteObjects/qremoteobjectsettingsstore.h @@ -0,0 +1 @@ +#include "../../src/remoteobjects/qremoteobjectsettingsstore.h" diff --git a/include/QtRemoteObjects/qremoteobjectsource.h b/include/QtRemoteObjects/qremoteobjectsource.h new file mode 100644 index 0000000..4cff76a --- /dev/null +++ b/include/QtRemoteObjects/qremoteobjectsource.h @@ -0,0 +1 @@ +#include "../../src/remoteobjects/qremoteobjectsource.h" diff --git a/include/QtRemoteObjects/qtremoteobjectglobal.h b/include/QtRemoteObjects/qtremoteobjectglobal.h new file mode 100644 index 0000000..79db2d9 --- /dev/null +++ b/include/QtRemoteObjects/qtremoteobjectglobal.h @@ -0,0 +1 @@ +#include "../../src/remoteobjects/qtremoteobjectglobal.h" diff --git a/include/QtRemoteObjects/qtremoteobjectsversion.h b/include/QtRemoteObjects/qtremoteobjectsversion.h new file mode 100644 index 0000000..f10e86c --- /dev/null +++ b/include/QtRemoteObjects/qtremoteobjectsversion.h @@ -0,0 +1,9 @@ +/* This file was generated by syncqt. */ +#ifndef QT_QTREMOTEOBJECTS_VERSION_H +#define QT_QTREMOTEOBJECTS_VERSION_H + +#define QTREMOTEOBJECTS_VERSION_STR "6.4.2" + +#define QTREMOTEOBJECTS_VERSION 0x060402 + +#endif // QT_QTREMOTEOBJECTS_VERSION_H diff --git a/include/QtRemoteObjectsQml/6.4.2/QtRemoteObjectsQml/private/qremoteobjectsqml_p.h b/include/QtRemoteObjectsQml/6.4.2/QtRemoteObjectsQml/private/qremoteobjectsqml_p.h new file mode 100644 index 0000000..ab64b5b --- /dev/null +++ b/include/QtRemoteObjectsQml/6.4.2/QtRemoteObjectsQml/private/qremoteobjectsqml_p.h @@ -0,0 +1 @@ +#include "../../../../../src/remoteobjectsqml/qremoteobjectsqml_p.h" diff --git a/include/QtRemoteObjectsQml/QtRemoteObjectsQml b/include/QtRemoteObjectsQml/QtRemoteObjectsQml new file mode 100644 index 0000000..32f9e54 --- /dev/null +++ b/include/QtRemoteObjectsQml/QtRemoteObjectsQml @@ -0,0 +1,5 @@ +#ifndef QT_QTREMOTEOBJECTSQML_MODULE_H +#define QT_QTREMOTEOBJECTSQML_MODULE_H +#include +#include "qtremoteobjectsqmlversion.h" +#endif diff --git a/include/QtRemoteObjectsQml/QtRemoteObjectsQmlVersion b/include/QtRemoteObjectsQml/QtRemoteObjectsQmlVersion new file mode 100644 index 0000000..a8394c8 --- /dev/null +++ b/include/QtRemoteObjectsQml/QtRemoteObjectsQmlVersion @@ -0,0 +1 @@ +#include "qtremoteobjectsqmlversion.h" diff --git a/include/QtRemoteObjectsQml/headers.pri b/include/QtRemoteObjectsQml/headers.pri new file mode 100644 index 0000000..2a30a82 --- /dev/null +++ b/include/QtRemoteObjectsQml/headers.pri @@ -0,0 +1,6 @@ +SYNCQT.HEADER_FILES = +SYNCQT.GENERATED_HEADER_FILES = qtremoteobjectsqmlversion.h QtRemoteObjectsQmlVersion QtRemoteObjectsQml +SYNCQT.PRIVATE_HEADER_FILES = qremoteobjectsqml_p.h +SYNCQT.QPA_HEADER_FILES = +SYNCQT.CLEAN_HEADER_FILES = +SYNCQT.INJECTIONS = diff --git a/include/QtRemoteObjectsQml/qtremoteobjectsqmlversion.h b/include/QtRemoteObjectsQml/qtremoteobjectsqmlversion.h new file mode 100644 index 0000000..730115a --- /dev/null +++ b/include/QtRemoteObjectsQml/qtremoteobjectsqmlversion.h @@ -0,0 +1,9 @@ +/* This file was generated by syncqt. */ +#ifndef QT_QTREMOTEOBJECTSQML_VERSION_H +#define QT_QTREMOTEOBJECTSQML_VERSION_H + +#define QTREMOTEOBJECTSQML_VERSION_STR "6.4.2" + +#define QTREMOTEOBJECTSQML_VERSION 0x060402 + +#endif // QT_QTREMOTEOBJECTSQML_VERSION_H diff --git a/include/QtRepParser/QRegexParser b/include/QtRepParser/QRegexParser new file mode 100644 index 0000000..53e35e0 --- /dev/null +++ b/include/QtRepParser/QRegexParser @@ -0,0 +1 @@ +#include "qregexparser.h" diff --git a/include/QtRepParser/QtRepParser b/include/QtRepParser/QtRepParser new file mode 100644 index 0000000..e9bfb36 --- /dev/null +++ b/include/QtRepParser/QtRepParser @@ -0,0 +1,6 @@ +#ifndef QT_QTREPPARSER_MODULE_H +#define QT_QTREPPARSER_MODULE_H +#include +#include "qregexparser.h" +#include "qtrepparserversion.h" +#endif diff --git a/include/QtRepParser/QtRepParserVersion b/include/QtRepParser/QtRepParserVersion new file mode 100644 index 0000000..a896408 --- /dev/null +++ b/include/QtRepParser/QtRepParserVersion @@ -0,0 +1 @@ +#include "qtrepparserversion.h" diff --git a/include/QtRepParser/headers.pri b/include/QtRepParser/headers.pri new file mode 100644 index 0000000..70bd16d --- /dev/null +++ b/include/QtRepParser/headers.pri @@ -0,0 +1,6 @@ +SYNCQT.HEADER_FILES = qregexparser.h +SYNCQT.GENERATED_HEADER_FILES = QRegexParser qtrepparserversion.h QtRepParserVersion QtRepParser +SYNCQT.PRIVATE_HEADER_FILES = +SYNCQT.QPA_HEADER_FILES = +SYNCQT.CLEAN_HEADER_FILES = qregexparser.h +SYNCQT.INJECTIONS = diff --git a/include/QtRepParser/qregexparser.h b/include/QtRepParser/qregexparser.h new file mode 100644 index 0000000..b0ffb5c --- /dev/null +++ b/include/QtRepParser/qregexparser.h @@ -0,0 +1 @@ +#include "../../src/repparser/qregexparser.h" diff --git a/include/QtRepParser/qtrepparserversion.h b/include/QtRepParser/qtrepparserversion.h new file mode 100644 index 0000000..578e6cd --- /dev/null +++ b/include/QtRepParser/qtrepparserversion.h @@ -0,0 +1,9 @@ +/* This file was generated by syncqt. */ +#ifndef QT_QTREPPARSER_VERSION_H +#define QT_QTREPPARSER_VERSION_H + +#define QTREPPARSER_VERSION_STR "6.4.2" + +#define QTREPPARSER_VERSION 0x060402 + +#endif // QT_QTREPPARSER_VERSION_H diff --git a/mkspecs/features/features.pro b/mkspecs/features/features.pro new file mode 100644 index 0000000..572235f --- /dev/null +++ b/mkspecs/features/features.pro @@ -0,0 +1,18 @@ +TEMPLATE = aux + +prf.files = remoteobjects_repc.prf repcclient.pri repcserver.pri repcmerged.pri repccommon.pri repparser.prf +prf.path = $$[QT_HOST_DATA]/mkspecs/features +INSTALLS += prf + +# Ensure files are copied to qtbase mkspecs for non-prefixed builds +!force_independent:if(!debug_and_release|!build_all|CONFIG(release, debug|release)) { + defineReplace(stripSrcDir) { + return($$relative_path($$1, $$_PRO_FILE_PWD_)) + } + prffiles2build.input = prf.files + prffiles2build.output = $$[QT_HOST_DATA]/mkspecs/features/${QMAKE_FUNC_FILE_IN_stripSrcDir} + prffiles2build.commands = $$QMAKE_COPY ${QMAKE_FILE_IN} ${QMAKE_FILE_OUT} + prffiles2build.name = COPY ${QMAKE_FILE_IN} + prffiles2build.CONFIG = no_link target_predeps + QMAKE_EXTRA_COMPILERS += prffiles2build +} diff --git a/mkspecs/features/remoteobjects_repc.prf b/mkspecs/features/remoteobjects_repc.prf new file mode 100644 index 0000000..8c79ed5 --- /dev/null +++ b/mkspecs/features/remoteobjects_repc.prf @@ -0,0 +1,26 @@ +include(repcclient.pri) +include(repcserver.pri) +include(repcmerged.pri) + +!isEmpty(QOBJECT_REP) { + qtPrepareLibExecTool(QMAKE_REPC, repc) + + for (path, QT.remoteobjects.includes) { + REPC_INCLUDEPATH += -I $$shell_quote($$path) + } + + qtPrepareLibExecTool(MOC_CREATE_JSON, moc) + moc_json.output = ${QMAKE_FILE_BASE}.json + moc_json.CONFIG = no_link moc_verify + moc_json.commands = $$MOC_CREATE_JSON -o ${QMAKE_FILE_BASE} ${QMAKE_FILE_NAME} --output-json + moc_json.depends = ${QMAKE_FILE_NAME} + moc_json.input = QOBJECT_REP + moc_json.variable_out = MOC_JSON + + source2rep.input = MOC_JSON + source2rep.output = ${QMAKE_FILE_BASE}.rep + source2rep.commands = $$QMAKE_REPC $$REPC_INCLUDEPATH -o rep -i json ${QMAKE_FILE_NAME} ${QMAKE_FILE_OUT} + source2rep.depends = ${QMAKE_FILE_NAME} $$QT_TOOL.repc.binary + source2rep.CONFIG += target_predeps no_link + QMAKE_EXTRA_COMPILERS += moc_json source2rep +} diff --git a/mkspecs/features/repcclient.pri b/mkspecs/features/repcclient.pri new file mode 100644 index 0000000..9e8bad4 --- /dev/null +++ b/mkspecs/features/repcclient.pri @@ -0,0 +1,4 @@ +repc_type = replica +repc_option = -o replica + +include(repccommon.pri) diff --git a/mkspecs/features/repccommon.pri b/mkspecs/features/repccommon.pri new file mode 100644 index 0000000..d014bc8 --- /dev/null +++ b/mkspecs/features/repccommon.pri @@ -0,0 +1,75 @@ +# Detect repc when QtRO is installed into non-Qt prefix +cmd = $${QT.remoteobjects.bins}/repc +contains(QMAKE_HOST.os, Windows) { + cmd = $$system_path($${cmd}.exe) +} +exists($$cmd): QT_TOOL.repc.binary = $$cmd + +# qtPrepareLibExecTool honors QT_TOOL.repc.binary if set +qtPrepareLibExecTool(QMAKE_REPC, repc) + +REPC_INCLUDEPATHES = $$QT.remoteobjects.includes +for (path, REPC_INCLUDEPATHES) { + REPC_INCLUDEPATH += -I $$path +} + +isEmpty(QMAKE_MOD_REPC):QMAKE_MOD_REPC = rep_ + +repc_TYPE = $$upper($$repc_type) + +load(moc) + +groups = +for(entry, REPC_$$repc_TYPE) { + files = $$eval($${entry}.files) + isEmpty(files) { + files = $$entry + group = repc_$${repc_type} + } else { + group = $${entry}_repc_$${repc_type} + } + groups *= $$group + + input_list = $$upper($$group)_LIST + json_list = $$upper($$group)_JSONLIST + for(subent, $$list($$unique(files))) { + if (contains(subent, .*\\.h$)|contains(subent, .*\\.hpp$)) { + $$json_list += $$subent + } else { + $$input_list += $$subent + } + + # Add directory of *.rep file to include path + file_path = $$_PRO_FILE_PWD_/$$subent + INCLUDEPATH *= $$dirname(file_path) + } +} + +for(group, groups) { + GROUP = $$upper($$group) + input_list = $${GROUP}_LIST + json_list = $${GROUP}_JSONLIST + + qtPrepareLibExecTool(MOC_CREATE_JSON, moc) + $${group}_moc_json.output = ${QMAKE_FILE_BASE}.json + $${group}_moc_json.CONFIG = no_link moc_verify + $${group}_moc_json.commands = $$MOC_CREATE_JSON -o ${QMAKE_FILE_BASE} ${QMAKE_FILE_NAME} --output-json + $${group}_moc_json.depends = ${QMAKE_FILE_NAME} + $${group}_moc_json.input = $$json_list + $${group}_moc_json.variable_out = MOC_JSON + + $${group}_header.output = $$QMAKE_MOD_REPC${QMAKE_FILE_BASE}_$${repc_type}.h + $${group}_header.commands = $$QMAKE_REPC $$repc_option $$REPC_INCLUDEPATH ${QMAKE_FILE_NAME} ${QMAKE_FILE_OUT} + $${group}_header.depends = ${QMAKE_FILE_NAME} $$QT_TOOL.repc.binary + $${group}_header.variable_out = $${GROUP}_HEADERS + $${group}_header.input = $$input_list MOC_JSON + + $${group}_moc.commands = $$moc_header.commands $$REPC_INCLUDEPATH + $${group}_moc.output = $$moc_header.output + $${group}_moc.input = $${GROUP}_HEADERS + $${group}_moc.variable_out = GENERATED_SOURCES + !contains(TEMPLATE, vc.*): \ + $${group}_moc.name = $$moc_header.name + + QMAKE_EXTRA_COMPILERS += $${group}_moc_json $${group}_header $${group}_moc +} diff --git a/mkspecs/features/repcmerged.pri b/mkspecs/features/repcmerged.pri new file mode 100644 index 0000000..0f672d2 --- /dev/null +++ b/mkspecs/features/repcmerged.pri @@ -0,0 +1,4 @@ +repc_type = merged +repc_option = -o merged + +include(repccommon.pri) diff --git a/mkspecs/features/repcserver.pri b/mkspecs/features/repcserver.pri new file mode 100644 index 0000000..035467a --- /dev/null +++ b/mkspecs/features/repcserver.pri @@ -0,0 +1,4 @@ +repc_type = source +repc_option = -o source + +include(repccommon.pri) diff --git a/mkspecs/features/repparser.prf b/mkspecs/features/repparser.prf new file mode 100644 index 0000000..cb5ae6b --- /dev/null +++ b/mkspecs/features/repparser.prf @@ -0,0 +1,9 @@ +CONFIG += qlalr +INCLUDEPATH *= $$QT.repparser.includes + +for (include, INCLUDEPATH) { + exists($${include}/parser.g) { + msvc: QMAKE_CXXFLAGS += /wd4129 + QLALRSOURCES += $${include}/parser.g + } +} diff --git a/mkspecs/mkspecs.pro b/mkspecs/mkspecs.pro new file mode 100644 index 0000000..95262a0 --- /dev/null +++ b/mkspecs/mkspecs.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs + +SUBDIRS += features diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..22babd4 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_subdirectory(repparser) +if(QT_FEATURE_localserver) + add_subdirectory(remoteobjects) +endif() +if(TARGET Qt::Quick) + add_subdirectory(remoteobjectsqml) +endif() diff --git a/src/remoteobjects/CMakeLists.txt b/src/remoteobjects/CMakeLists.txt new file mode 100644 index 0000000..cd2619f --- /dev/null +++ b/src/remoteobjects/CMakeLists.txt @@ -0,0 +1,69 @@ + +##################################################################### +## RemoteObjects Module: +##################################################################### + +qt_internal_add_module(RemoteObjects + QMAKE_MODULE_CONFIG remoteobjects_repc + SOURCES + qconnection_local_backend.cpp qconnection_local_backend_p.h + qconnection_tcpip_backend.cpp qconnection_tcpip_backend_p.h + qconnectionfactories.cpp qconnectionfactories.h qconnectionfactories_p.h + qremoteobjectabstractitemmodeladapter.cpp qremoteobjectabstractitemmodeladapter_p.h + qremoteobjectabstractitemmodelreplica.cpp qremoteobjectabstractitemmodelreplica.h qremoteobjectabstractitemmodelreplica_p.h + qremoteobjectabstractitemmodeltypes_p.h + qremoteobjectcontainers.cpp qremoteobjectcontainers_p.h + qremoteobjectdynamicreplica.cpp qremoteobjectdynamicreplica.h + qremoteobjectnode.cpp qremoteobjectnode.h qremoteobjectnode_p.h + qremoteobjectpacket.cpp qremoteobjectpacket_p.h + qremoteobjectpendingcall.cpp qremoteobjectpendingcall.h qremoteobjectpendingcall_p.h + qremoteobjectregistry.cpp qremoteobjectregistry.h + qremoteobjectregistrysource.cpp qremoteobjectregistrysource_p.h + qremoteobjectreplica.cpp qremoteobjectreplica.h qremoteobjectreplica_p.h + qremoteobjectsettingsstore.cpp qremoteobjectsettingsstore.h + qremoteobjectsource.cpp qremoteobjectsource.h qremoteobjectsource_p.h + qremoteobjectsourceio.cpp qremoteobjectsourceio_p.h + qtremoteobjectglobal.cpp qtremoteobjectglobal.h + DEFINES + QT_BUILD_REMOTEOBJECTS_LIB + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_FROM_BYTEARRAY + QT_NO_CAST_TO_ASCII + QT_NO_URL_CAST_FROM_STRING + INCLUDE_DIRECTORIES + . + LIBRARIES + Qt::CorePrivate + PUBLIC_LIBRARIES + Qt::Core + Qt::Network + PRIVATE_MODULE_INTERFACE + Qt::CorePrivate + GENERATE_CPP_EXPORTS +) + +#### Keys ignored in scope 1:.:.:remoteobjects.pro:: +# MODULE = "remoteobjects" +# MODULE_CONFIG = "remoteobjects_repc" +# OTHER_FILES = "doc/qtremoteobjects.qdocconf" "doc/src/remoteobjects-cpp.qdoc" "doc/src/remoteobjects-index.qdoc" "doc/src/remoteobjects-overview.qdoc" "doc/src/remoteobjects-repc.qdoc" + +## Scopes: +##################################################################### + +qt_internal_extend_target(RemoteObjects CONDITION QNX + SOURCES + qconnection_qnx_backend.cpp qconnection_qnx_backend_p.h + qconnection_qnx_global_p.h + qconnection_qnx_qiodevices.cpp qconnection_qnx_qiodevices.h qconnection_qnx_qiodevices_p.h + qconnection_qnx_server.cpp qconnection_qnx_server.h qconnection_qnx_server_p.h +) + +qt_internal_extend_target(RemoteObjects CONDITION QNX AND QT_FEATURE_use_ham + PUBLIC_LIBRARIES + ham +) +qt_internal_add_docs(RemoteObjects + doc/qtremoteobjects.qdocconf +) + +include(Qt6RemoteObjectsMacros.cmake) diff --git a/src/remoteobjects/Qt5RemoteObjectsConfigExtras.cmake.in b/src/remoteobjects/Qt5RemoteObjectsConfigExtras.cmake.in new file mode 100644 index 0000000..ea42536 --- /dev/null +++ b/src/remoteobjects/Qt5RemoteObjectsConfigExtras.cmake.in @@ -0,0 +1,27 @@ +# Copyright (C) 2015 Ford Motor Company +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + add_executable(Qt5::repc IMPORTED) + +!!IF isEmpty(CMAKE_BIN_DIR_IS_ABSOLUTE) + set(imported_location \"${_qt5RemoteObjects_install_prefix}/$${CMAKE_BIN_DIR}repc$$CMAKE_BIN_SUFFIX\") +!!ELSE + set(imported_location \"$${CMAKE_BIN_DIR}repc$$CMAKE_BIN_SUFFIX\") +!!ENDIF + _qt5_RemoteObjects_check_file_exists(${imported_location}) + + set_target_properties(Qt5::repc PROPERTIES + IMPORTED_LOCATION ${imported_location} + ) + get_target_property(Qt5RemoteObjects_REPC_EXECUTABLE Qt5::repc LOCATION) +endif() + +# Create versionless tool targets. +foreach(__qt_tool repc) + if(NOT \"${QT_NO_CREATE_VERSIONLESS_TARGETS}\" AND NOT TARGET Qt::${__qt_tool} + AND TARGET Qt5::${__qt_tool}) + add_executable(Qt::${__qt_tool} IMPORTED) + get_target_property(__qt_imported_location Qt5::${__qt_tool} IMPORTED_LOCATION) + set_target_properties(Qt::${__qt_tool} + PROPERTIES IMPORTED_LOCATION \"${__qt_imported_location}\") + endif() +endforeach() diff --git a/src/remoteobjects/Qt6RemoteObjectsMacros.cmake b/src/remoteobjects/Qt6RemoteObjectsMacros.cmake new file mode 100644 index 0000000..0ac9f6b --- /dev/null +++ b/src/remoteobjects/Qt6RemoteObjectsMacros.cmake @@ -0,0 +1,174 @@ +# Copyright (C) 2019 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +# framework style includes like #include +# Extract the location of the Qt frameworks by querying the imported location of QtRemoteObjects +# framework parent directory. +function(_qt_internal_get_remote_objects_framework_path out_var) + set(value "") + if(APPLE AND QT_FEATURE_framework) + get_target_property(ro_path ${QT_CMAKE_EXPORT_NAMESPACE}::RemoteObjects IMPORTED_LOCATION) + string(REGEX REPLACE "(.*)/Qt[^/]+\\.framework.*" "\\1" ro_fw_path "${ro_path}") + if(ro_fw_path) + set(value "${ro_fw_path}") + endif() + endif() + set(${out_var} "${value}" PARENT_SCOPE) +endfunction() + +function(_qt_internal_get_remote_objects_framework_path_moc_options out_var) + _qt_internal_get_remote_objects_framework_path(ro_fw_path) + if(ro_fw_path) + set(${out_var} "OPTIONS" "-F${ro_fw_path}" PARENT_SCOPE) + else() + set(${out_var} "" PARENT_SCOPE) + endif() +endfunction() + +function(_qt_internal_add_repc_files type target) + set(options) + set(oneValueArgs) + set(multiValueArgs SOURCES) + + cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(outfiles) + if(QT_REPC_DEBUG_MODE) + set(debug "-d") + else() + set(debug "") + endif() + set(repc_incpath) ########### TODO + + _qt_internal_get_remote_objects_framework_path_moc_options(extra_moc_options) + _qt_internal_get_tool_wrapper_script_path(tool_wrapper) + set(repc_command + COMMAND + "${tool_wrapper}" + "$" + ) + foreach(it ${ARGS_SOURCES}) + get_filename_component(outfilename ${it} NAME_WE) + get_filename_component(extension ${it} EXT) + if ("${extension}" STREQUAL ".h" OR "${extension}" STREQUAL ".hpp") + # This calls moc on an existing header file to extract metatypes json information + # which is then passed to the tool to generate another header. + qt6_wrap_cpp(qtro_moc_files "${it}" + __QT_INTERNAL_OUTPUT_MOC_JSON_FILES json_list + TARGET "${target}" + ${extra_moc_options}) + + # Pass the generated metatypes .json file to the tool. + set(infile ${json_list}) + set_source_files_properties(${qtro_moc_files} PROPERTIES HEADER_FILE_ONLY ON) + list(APPEND outfiles ${qtro_moc_files}) + else() + # Pass the .rep file to the tool. + get_filename_component(infile ${it} ABSOLUTE) + endif() + set(outfile ${CMAKE_CURRENT_BINARY_DIR}/rep_${outfilename}_${type}.h) + + add_custom_command( + OUTPUT ${outfile} + ${repc_command} ${debug} -o ${type} ${repc_incpath} ${infile} ${outfile} + MAIN_DEPENDENCY ${infile} + DEPENDS ${QT_CMAKE_EXPORT_NAMESPACE}::repc + VERBATIM + ) + list(APPEND outfiles ${outfile}) + + # The generated header file needs to be manually moc'ed (without using AUTOMOC) and then + # added as source to compile into the target. + qt6_wrap_cpp(qtro_moc_files "${outfile}" TARGET "${target}" ${extra_moc_options}) + set_source_files_properties("${outfile}" PROPERTIES + GENERATED TRUE + SKIP_AUTOGEN ON) + list(APPEND outfiles ${qtro_moc_files}) + endforeach() + target_sources(${target} PRIVATE ${outfiles}) +endfunction() + +# Add .rep source files to a target to generate source header files +function(qt6_add_repc_sources target) + list(POP_FRONT ARGV) + _qt_internal_add_repc_files(source ${target} SOURCES ${ARGV}) +endfunction() + +# Add .rep source files to a target to generate replica header files +function(qt6_add_repc_replicas target) + list(POP_FRONT ARGV) + _qt_internal_add_repc_files(replica ${target} SOURCES ${ARGV}) +endfunction() + +# Add .rep source files to a target to generate combined (source and replica) header files +function(qt6_add_repc_merged target) + list(POP_FRONT ARGV) + _qt_internal_add_repc_files(merged ${target} SOURCES ${ARGV}) +endfunction() + +# Create .rep interface file from QObject header +function(qt6_reps_from_headers target) + list(POP_FRONT ARGV) + _qt_internal_get_remote_objects_framework_path_moc_options(extra_moc_options) + _qt_internal_get_tool_wrapper_script_path(tool_wrapper) + set(repc_command + COMMAND + "${tool_wrapper}" + "$" + ) + + foreach(it ${ARGV}) + get_filename_component(outfilename ${it} NAME_WE) + # This calls moc on an existing header file to extract metatypes json information + # which is then passed to the tool to generate a .rep file. + qt6_wrap_cpp(qtro_moc_files "${it}" + __QT_INTERNAL_OUTPUT_MOC_JSON_FILES json_list + TARGET "${target}" + ${extra_moc_options}) + set(infile ${json_list}) + set_source_files_properties(${qtro_moc_files} PROPERTIES HEADER_FILE_ONLY ON) + list(APPEND outfiles ${qtro_moc_files}) + set(outfile ${CMAKE_CURRENT_BINARY_DIR}/${outfilename}.rep) + add_custom_command(OUTPUT ${outfile} + ${repc_command} -o rep ${infile} ${outfile} + MAIN_DEPENDENCY ${infile} + DEPENDS ${QT_CMAKE_EXPORT_NAMESPACE}::repc + VERBATIM) + list(APPEND outfiles ${outfile}) + endforeach() + target_sources(${target} PRIVATE ${outfiles}) +endfunction() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + function(qt_add_repc_sources) + if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + qt6_add_repc_sources(${ARGN}) + else() + message(FATAL_ERROR "qt_add_repc_sources() is only available in Qt 6. " + "Please check the repc documentation for alternatives.") + endif() + endfunction() + function(qt_add_repc_replicas) + if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + qt6_add_repc_replicas(${ARGN}) + else() + message(FATAL_ERROR "qt_add_repc_replicas() is only available in Qt 6. " + "Please check the repc documentation for alternatives.") + endif() + endfunction() + function(qt_add_repc_merged) + if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + qt6_add_repc_merged(${ARGN}) + else() + message(FATAL_ERROR "qt_add_repc_merged() is only available in Qt 6. " + "Please check the repc documentation for alternatives.") + endif() + endfunction() + function(qt_reps_from_headers) + if(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + qt6_reps_from_headers(${ARGN}) + else() + message(FATAL_ERROR "qt_reps_from_headers() is only available in Qt 6. " + "Please check the repc documentation for alternatives.") + endif() + endfunction() +endif() diff --git a/src/remoteobjects/configure.cmake b/src/remoteobjects/configure.cmake new file mode 100644 index 0000000..b5b3687 --- /dev/null +++ b/src/remoteobjects/configure.cmake @@ -0,0 +1,26 @@ + + +#### Inputs + + + +#### Libraries + + + +#### Tests + + + +#### Features + +qt_feature("use_ham" PUBLIC + LABEL "High Availability Manager (ham)" + PURPOSE "Use QNX's High Availability Manager (ham) library" + AUTODETECT OFF + CONDITION QNX +) +qt_feature_definition("use_ham" "QT_NO_USE_HAM" NEGATE VALUE "1") +qt_configure_add_summary_section(NAME "Qt Remote Objects") +qt_configure_add_summary_entry(ARGS "use_ham") +qt_configure_end_summary_section() # end of "Qt Remote Objects" section diff --git a/src/remoteobjects/doc/images/DirectConnectClientServerOutput.png b/src/remoteobjects/doc/images/DirectConnectClientServerOutput.png new file mode 100644 index 0000000..3b3166d Binary files /dev/null and b/src/remoteobjects/doc/images/DirectConnectClientServerOutput.png differ diff --git a/src/remoteobjects/doc/images/DirectConnectServerOutput.png b/src/remoteobjects/doc/images/DirectConnectServerOutput.png new file mode 100644 index 0000000..a82c272 Binary files /dev/null and b/src/remoteobjects/doc/images/DirectConnectServerOutput.png differ diff --git a/src/remoteobjects/doc/images/README b/src/remoteobjects/doc/images/README new file mode 100644 index 0000000..aec41a2 --- /dev/null +++ b/src/remoteobjects/doc/images/README @@ -0,0 +1 @@ +Put all documentation images into this folder. diff --git a/src/remoteobjects/doc/images/RemoteObjectsHighLevel.png b/src/remoteobjects/doc/images/RemoteObjectsHighLevel.png new file mode 100644 index 0000000..54cbebb Binary files /dev/null and b/src/remoteobjects/doc/images/RemoteObjectsHighLevel.png differ diff --git a/src/remoteobjects/doc/qtremoteobjects.qdocconf b/src/remoteobjects/doc/qtremoteobjects.qdocconf new file mode 100644 index 0000000..89bfc79 --- /dev/null +++ b/src/remoteobjects/doc/qtremoteobjects.qdocconf @@ -0,0 +1,54 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) +include($QT_INSTALL_DOCS/config/exampleurl-qtremoteobjects.qdocconf) + +project = QtRemoteObjects +description = Qt Remote Objects Reference Documentation +version = $QT_VERSION + +qhp.projects = QtRemoteObjects + +qhp.QtRemoteObjects.file = qtremoteobjects.qhp +qhp.QtRemoteObjects.namespace = org.qt-project.qtremoteobjects.$QT_VERSION_TAG +qhp.QtRemoteObjects.virtualFolder = remoteobjects +qhp.QtRemoteObjects.indexTitle = Qt Remote Objects +qhp.QtRemoteObjects.indexRoot = + +qhp.QtRemoteObjects.subprojects = overviews classes repc +qhp.QtRemoteObjects.subprojects.overviews.title = Getting Started +qhp.QtRemoteObjects.subprojects.overviews.indexTitle = Getting Started with Qt Remote Objects +qhp.QtRemoteObjects.subprojects.overviews.selectors = fake:page,group,module +qhp.QtRemoteObjects.subprojects.classes.title = C++ Classes +qhp.QtRemoteObjects.subprojects.classes.indexTitle = Qt Remote Objects C++ Classes +qhp.QtRemoteObjects.subprojects.classes.selectors = class fake:headerfile +qhp.QtRemoteObjects.subprojects.classes.sortPages = true + +depends += qtcore \ + qtnetwork \ + qtdoc \ + qtcmake \ + qmake + +tagfile = ../../../doc/qtremoteobjects/qtremoteobjects.tags + +headerdirs += .. \ + ../../remoteobjects + +sourcedirs += .. \ + ../../remoteobjects + +exampledirs += ../../../examples/remoteobjects \ + snippets/ + +#add thumbnail images for the example docs that does not have an image. +manifestmeta.thumbnail.names += "QtRemoteObjects/Model*" \ + "QtRemoteObjects/QML*" \ + "QtRemoteObjects/QtRemote*" + + +examplesinstallpath = remoteobjects + +imagedirs += images + +navigation.landingpage = "Qt Remote Objects" +navigation.cppclassespage = "Qt Remote Objects C++ Classes" +navigation.qmltypespage = "Qt Remote Objects QML Types" diff --git a/src/remoteobjects/doc/snippets/README b/src/remoteobjects/doc/snippets/README new file mode 100644 index 0000000..72ad410 --- /dev/null +++ b/src/remoteobjects/doc/snippets/README @@ -0,0 +1 @@ +Put all snipplets into this folder. diff --git a/src/remoteobjects/doc/snippets/cmake-macros/CMakeLists.txt b/src/remoteobjects/doc/snippets/cmake-macros/CMakeLists.txt new file mode 100644 index 0000000..f43125c --- /dev/null +++ b/src/remoteobjects/doc/snippets/cmake-macros/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.16) + +project(directconnectserver) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +if(CMAKE_VERSION VERSION_LESS "3.7.0") + set(CMAKE_INCLUDE_CURRENT_DIR ON) +endif() + +#! [remote_objects_cmake] +find_package(Qt6 REQUIRED COMPONENTS RemoteObjects) +#! [remote_objects_cmake] + +set(SOURCES + main.cpp + simpleswitch.cpp +) + +#! [simpleSwitch_cmake_add_repc_source] +qt6_add_repc_sources(directconnectserver + simpleswitch.rep +) +#! [simpleSwitch_cmake_add_repc_source] + +#! [simpleSwitch_cmake_add_repc_replica] +qt6_add_repc_replicas(directconnectclient + simpleswitch.rep +) +#! [simpleSwitch_cmake_add_repc_replica] + +#! [simpleSwitch_cmake_add_repc_merged] +qt6_add_repc_merged(directconnectexample + simpleswitch.rep +) +#! [simpleSwitch_cmake_add_repc_merged] + +add_executable(directconnectserver ${SOURCES}) +#! [remote_objects_cmake_link] +target_link_libraries(directconnectserver PRIVATE Qt6::RemoteObjects) +#! [remote_objects_cmake_link] + +#! [simpleSwitch_cmake_rep_from_header] +qt6_reps_from_headers(directconnectexample + simpleswitch.h +) +#! [simpleSwitch_cmake_rep_from_header] diff --git a/src/remoteobjects/doc/snippets/cmake-macros/main.cpp b/src/remoteobjects/doc/snippets/cmake-macros/main.cpp new file mode 100644 index 0000000..bc1af59 --- /dev/null +++ b/src/remoteobjects/doc/snippets/cmake-macros/main.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include "simpleswitch.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + SimpleSwitch srcSwitch; // create simple switch + + QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:replica"))); // create host node without Registry + srcNode.enableRemoting(&srcSwitch); // enable remoting/Sharing + + return a.exec(); +} diff --git a/src/remoteobjects/doc/snippets/cmake-macros/simpleswitch.cpp b/src/remoteobjects/doc/snippets/cmake-macros/simpleswitch.cpp new file mode 100644 index 0000000..4226633 --- /dev/null +++ b/src/remoteobjects/doc/snippets/cmake-macros/simpleswitch.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "simpleswitch.h" + +// constructor +SimpleSwitch::SimpleSwitch(QObject *parent) : SimpleSwitchSimpleSource(parent) +{ + stateChangeTimer = new QTimer(this); // Initialize timer + QObject::connect(stateChangeTimer, &SimpleSwitch::timeout, this, &SimpleSwitch::timeout_slot); // connect timeout() signal from stateChangeTimer to timeout_slot() of simpleSwitch + stateChangeTimer->start(2000); // Start timer and set timout to 2 seconds + qDebug() << "Source Node Started"; +} + +//destructor +SimpleSwitch::~SimpleSwitch() +{ + stateChangeTimer->stop(); +} + +void SimpleSwitch::server_slot(bool clientState) +{ + qDebug() << "Replica state is " << clientState; // print switch state echoed back by client +} + +void SimpleSwitch::timeout_slot(void) +{ + // slot called on timer timeout + if (currState()) // check if current state is true, currState() is defined in repc generated rep_simpleswitch_source.h + setCurrState(false); // set state to false + else + setCurrState(true); // set state to true + qDebug() << "Source State is "< + +class SimpleSwitch +{ + PROP(bool currState=false); + SLOT(void server_slot(bool clientState)); +}; diff --git a/src/remoteobjects/doc/snippets/doc_src_remoteobjects.h b/src/remoteobjects/doc/snippets/doc_src_remoteobjects.h new file mode 100644 index 0000000..d11ad68 --- /dev/null +++ b/src/remoteobjects/doc/snippets/doc_src_remoteobjects.h @@ -0,0 +1,34 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +//! [qtremoteobject_include] +#include +//! [qtremoteobject_include] + +//! [implementing_source] +#include "rep_TimeModel_source.h" + +class MinuteTimer : public MinuteTimerSource +{ +Q_OBJECT +public: + MinuteTimer(QObject *parent = nullptr); + virtual ~MinuteTimer(); + +public slots: + virtual void SetTimeZone(int zn) { //this is a pure virtual method in MinuteTimerSource + qDebug()<<"SetTimeZone"<start(2000); // Start timer and set timout to 2 seconds + qDebug() << "Source Node Started"; +} + +//destructor +SimpleSwitch::~SimpleSwitch() +{ + stateChangeTimer->stop(); +} + +void SimpleSwitch::server_slot(bool clientState) +{ + qDebug() << "Replica state is " << clientState; // print switch state echoed back by client +} + +void SimpleSwitch::timeout_slot() +{ + // slot called on timer timeout + if (currState()) // check if current state is true, currState() is defined in repc generated rep_simpleswitch_source.h + setCurrState(false); // set state to false + else + setCurrState(true); // set state to true + qDebug() << "Source State is "< +#include "simpleswitch.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + SimpleSwitch srcSwitch; // create simple switch + + QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:switch"))); // create host node without Registry + srcNode.enableRemoting(&srcSwitch); // enable remoting/sharing + + return a.exec(); +} +//! [simpleSwitch_servermaincpp_example1] + +//! [simpleSwitch_clientrep_example1] +REPC_REPLICA = simpleswitch.rep +//! [simpleSwitch_clientrep_example1] + +//! [simpleSwitch_clientremotenode_example1] +QRemoteObjectNode repNode; // create remote object node +repNode.connectToNode(QUrl(QStringLiteral("local:switch"))); // connect with remote host node + //! [simpleSwitch_clientremotenode_example1] + + //! [simpleSwitch_clientacquirereplica_example1] +QSharedPointer ptr; +ptr.reset(repNode.acquire()); // acquire replica of source from host node +//! [simpleSwitch_clientacquirereplica_example1] + +//! [simpleSwitch_clientheader_example1] +#ifndef _CLIENT_H +#define _CLIENT_H + +#include +#include + +#include "rep_simpleswitch_replica.h" + +class Client : public QObject +{ + Q_OBJECT +public: + Client(QSharedPointer ptr); + ~Client(); + void initConnections();// Function to connect signals and slots of source and client + +Q_SIGNALS: + void echoSwitchState(bool switchState);// this signal is connected with server_slot(..) on the source object and echoes back switch state received from source + +public Q_SLOTS: + void recSwitchState_slot(); // slot to receive source state +private: + bool clientSwitchState; // holds received server switch state + QSharedPointer reptr;// holds reference to replica + + }; + +#endif +//! [simpleSwitch_clientheader_example1] + +//! [simpleSwitch_clientcpp_example1] +#include "client.h" + +// constructor +Client::Client(QSharedPointer ptr) : + QObject(nullptr),reptr(ptr) +{ + initConnections(); + //We can connect to SimpleSwitchReplica Signals/Slots + //directly because our Replica was generated by repc. +} + +//destructor +Client::~Client() +{ +} + +void Client::initConnections() +{ + // initialize connections between signals and slots + + // connect source replica signal currStateChanged() with client's recSwitchState() slot to receive source's current state + QObject::connect(reptr.data(), &SimpleSwitchReplica::currStateChanged, this, &Client::recSwitchState_slot); + // connect client's echoSwitchState(..) signal with replica's server_slot(..) to echo back received state + QObject::connect(this, &Client::echoSwitchState, reptr.data(), &SimpleSwitchReplica::server_slot); +} + +void Client::recSwitchState_slot(bool value) +{ + qDebug() << "Received source state "<< value << reptr.data()->currState(); + clientSwitchState = reptr.data()->currState(); + Q_EMIT echoSwitchState(clientSwitchState); // Emit signal to echo received state back to server +} +//! [simpleSwitch_clientcpp_example1] + +//! [simpleSwitch_clientmain_example1] +#include +#include "client.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + + QSharedPointer ptr; // shared pointer to hold source replica + + QRemoteObjectNode repNode; // create remote object node + repNode.connectToNode(QUrl(QStringLiteral("local:switch"))); // connect with remote host node + + ptr.reset(repNode.acquire()); // acquire replica of source from host node + + Client rswitch(ptr); // create client switch object and pass reference of replica to it + + return a.exec(); +} +//! [simpleSwitch_clientmain_example1] + +//! [simpleSwitch_dynamicclientnode_example2] +QRemoteObjectNode repNode; // create remote object node +repNode.connectToNode(QUrl(QStringLiteral("local:switch"))); // connect with remote host node +//! [simpleSwitch_dynamicclientnode_example2] + +//! [simpleSwitch_dynamicclientacquirereplica_example2] +QSharedPointer ptr; // shared pointer to hold replica +ptr.reset(repNode.acquire("SimpleSwitch")); // acquire replica of source from host node +//! [simpleSwitch_dynamicclientacquirereplica_example2] + +//! [simpleSwitch_dynamicclientheader_example2] +#ifndef _DYNAMICCLIENT_H +#define _DYNAMICCLIENT_H + +#include +#include + +#include +#include + +class DynamicClient : public QObject +{ + Q_OBJECT +public: + DynamicClient(QSharedPointer ptr); + ~DynamicClient(); + +Q_SIGNALS: + void echoSwitchState(bool switchState);// this signal is connected with server_slot(..) slot of source object and echoes back switch state received from source + +public Q_SLOTS: + void recSwitchState_slot(); // Slot to receive source state + void initConnection_slot(); //Slot to connect signals/slot on replica initialization + +private: + bool clientSwitchState; // holds received server switch state + QSharedPointer reptr;// holds reference to replica + }; + +#endif +//! [simpleSwitch_dynamicclientheader_example2] + +//! [simpleSwitch_dynamicclientcpp_example2] +#include "dynamicclient.h" + +// constructor +DynamicClient::DynamicClient(QSharedPointer ptr) : + QObject(nullptr), reptr(ptr) +{ + + //connect signal for replica valid changed with signal slot initialization + QObject::connect(reptr.data(), &QRemoteObjectDynamicReplica::initialized, this, + &DynamicClient::initConnection_slot); +} + +//destructor +DynamicClient::~DynamicClient() +{ +} + +// Function to initialize connections between slots and signals +void DynamicClient::initConnection_slot() +{ + + // connect source replica signal currStateChanged() with client's recSwitchState() slot to receive source's current state + QObject::connect(reptr.data(), SIGNAL(currStateChanged()), this, SLOT(recSwitchState_slot())); + // connect client's echoSwitchState(..) signal with replica's server_slot(..) to echo back received state + QObject::connect(this, SIGNAL(echoSwitchState(bool)),reptr.data(), SLOT(server_slot(bool))); +} + +void DynamicClient::recSwitchState_slot() +{ + clientSwitchState = reptr->property("currState").toBool(); // use replica property to get currState from source + qDebug() << "Received source state " << clientSwitchState; + Q_EMIT echoSwitchState(clientSwitchState); // Emit signal to echo received state back to server +} + +//! [simpleSwitch_dynamicclientcpp_example2] + +//! [simpleSwitch_dynamicclientmaincpp_example2] +#include + +#include "dynamicclient.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + QSharedPointer ptr; // shared pointer to hold replica + + QRemoteObjectNode repNode; + repNode.connectToNode(QUrl(QStringLiteral("local:switch"))); + + ptr.reset(repNode.acquireDynamic("SimpleSwitch")); // acquire replica of source from host node + + DynamicClient rswitch(ptr); // create client switch object and pass replica reference to it +} +//! [simpleSwitch_dynamicclientmaincpp_example2] + +//! [simpleSwitch_registrymaincpp_example3] +#include +#include "simpleswitch.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + SimpleSwitch srcSwitch; // create SimpleSwitch + + QRemoteObjectRegistryHost regNode(QUrl(QStringLiteral("local:registry"))); // create node that hosts registry + QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:switch")), QUrl(QStringLiteral("local:registry"))); // create node that will host source and connect to registry + //Note, you can add srcSwitch directly to regNode if desired. + //We use two Nodes here, as the regNode could easily be in a third process. + + srcNode.enableRemoting(&srcSwitch); // enable remoting of source object + + return a.exec(); +} +//! [simpleSwitch_registrymaincpp_example3] + +//! [simpleSwitch_registrydynamicclientmaincpp_example3] + QRemoteObjectNode repNode(QUrl(QStringLiteral("local:registry"))); +//! [simpleSwitch_registrydynamicclientmaincpp_example3] diff --git a/src/remoteobjects/doc/src/cmake-macros.qdoc b/src/remoteobjects/doc/src/cmake-macros.qdoc new file mode 100644 index 0000000..23a933d --- /dev/null +++ b/src/remoteobjects/doc/src/cmake-macros.qdoc @@ -0,0 +1,133 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page qtremoteobjects-cmake-qt-add-repc-replicas.html +\ingroup cmake-macros-qtremoteobjects + +\title qt_add_repc_replicas +\target qt6_add_repc_replicas + +\brief Creates C++ header files for replica types from the Qt Remote Objects .rep files. + +\cmakecommandsince 6.2 + +\section1 Synopsis + +\badcode +qt_add_repc_replicas( rep_files) + +qt6_add_repc_replicas( rep_files) +\endcode + +\section1 Description + +Runs \l repc and generates \l {Qt Remote Objects Replica} header files based +on the given \c rep_files. Adds the generated files to \c{}. + +\section1 Examples + +\snippet cmake-macros/CMakeLists.txt simpleSwitch_cmake_add_repc_replica + +The generated file(s) will be of the form \c {rep__replica.h}, +in this case \c rep_simpleswitch_replica.h +*/ + +/*! +\page qtremoteobjects-cmake-qt-add-repc-sources.html +\ingroup cmake-macros-qtremoteobjects + +\title qt_add_repc_sources +\target qt6_add_repc_sources + +\brief Creates C++ header files for source types from the Qt Remote Objects .rep files. + +\cmakecommandsince 6.2 + +\section1 Synopsis + +\badcode +qt_add_repc_sources( rep_files) + +qt6_add_repc_sources( rep_files) +\endcode + +\section1 Description + +Runs \l repc and generates \l {Qt Remote Objects Source} header files based +on the given \c rep_files. Adds the generated files to \c{}. + +\section1 Examples + +\snippet cmake-macros/CMakeLists.txt simpleSwitch_cmake_add_repc_source + +The generated file(s) will be of the form \c {rep__source.h}, +in this case \c rep_simpleswitch_source.h +*/ + +/*! +\page qtremoteobjects-cmake-qt-add-repc-merged.html +\ingroup cmake-macros-qtremoteobjects + +\title qt_add_repc_merged +\target qt6_add_repc_merged + +\brief Creates C++ header files for source and replica types from the Qt Remote Objects .rep files. + +\cmakecommandsince 6.2 + +\section1 Synopsis + +\badcode +qt_add_repc_merged( rep_files) + +qt6_add_repc_merged( rep_files) +\endcode + +\section1 Description + +Runs \l repc and generates \l {Qt Remote Objects Replica} and \l {Qt Remote Objects Source} +header files based on the given \c rep_files combined in a single header. Adds the +generated files to \c{}. + +\section1 Examples + +\snippet cmake-macros/CMakeLists.txt simpleSwitch_cmake_add_repc_merged + +The generated file(s) will be of the form \c {rep__merged.h}, +in this case \c rep_simpleswitch_merged.h + +\note Typically sources and replicas live in separate processes or devices, so +this function is not commonly used. +*/ + +/*! +\page qtremoteobjects-cmake-qt-rep-from-headers.html +\ingroup cmake-macros-qtremoteobjects + +\title qt_reps_from_headers +\target qt6_reps_from_headers + +\brief Creates .rep files from the QObject header files. + +\cmakecommandsince 6.2 + +\section1 Synopsis + +\badcode +qt_reps_from_headers( header_files) + +qt6_reps_from_headers( header_files) +\endcode + +\section1 Description + +Generates corresponding \c .rep files from the existing QObject \c header_files. + +\section1 Examples + +\snippet cmake-macros/CMakeLists.txt simpleSwitch_cmake_rep_from_header + +This will generate an interface file called \c simpleswitch.rep in the build +directory. +*/ diff --git a/src/remoteobjects/doc/src/qt6-changes.qdoc b/src/remoteobjects/doc/src/qt6-changes.qdoc new file mode 100644 index 0000000..846c2c3 --- /dev/null +++ b/src/remoteobjects/doc/src/qt6-changes.qdoc @@ -0,0 +1,77 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page remoteobjects-changes-qt6.html + \title Changes to Qt Remote Objects + \ingroup changes-qt-5-to-6 + \brief Migrate Qt Remote Objects to Qt 6. + + Qt 6 is a result of the conscious effort to make the framework more + efficient and easy to use. + + We try to maintain binary and source compatibility for all the public + APIs in each release. But some changes were inevitable in an effort to + make Qt a better framework. + + In this topic we summarize those changes in Qt Remote Objects, and provide + guidance to handle them. + + \section1 API changes + + \section2 Functions taking const-ref QString changing to QStringView + + QRemoteObjectHostBase::proxy, QRemoteObjectHostBase::reverseProxy and + QRemoteObjectNode::instances now accept a QStringView instead + of \c{const QString &}. The largest difference caused by this is that it + no longer accepts implicit conversion from string literals + (i.e. \c{node.instances("abc");}). Instead, you could use a UTF-16 + string literal (\c{node.instances(u"abc")}). + + \section2 Changes to classes for custom transport backend support + + The "semi-private" \c IoDeviceBase, \c ServerIoDevice, and \c ClientIoDevice + classes are now renamed to \c QtROIoDeviceBase, \c QtROServerIoDevice, and + \c QtROClientIoDevice respectively, to be consistent with naming in Qt. + They are also moved from the private \c qconnectionfactories_p.h header to + \c qconnectionfactories.h. + + \note These classes are provided to give more flexibility for implementing + custom communication protocols for Qt Remote Objects, but there are no + source or binary compatibility guarantees for them. We recommend using the + QRemoteObjectNode::addClientSideConnection() and + QRemoteObjectHostBase::addHostSideConnection() methods, if you need + support for \l {External QIODevices} {external} communication channels. + + \section1 CMake changes + + The \c cmake instructions for calling \l repc and adding the generated + \c .rep files to a CMake project have slightly changed. Instead of the + \c qt5_generate_repc macro, you should now use \c qt6_add_repc_sources, + \c qt6_add_repc_replicas and \c qt6_add_repc_merged functions. + For example, the following code: + + \code + set(SOURCES + main.cpp + simpleswitch.cpp + ) + + qt5_generate_repc(SOURCES simpleswitch.rep SOURCE) + add_executable(directconnectserver ${SOURCES}) + \endcode + + Should change to: + + \code + set(SOURCES + main.cpp + simpleswitch.cpp + ) + add_executable(directconnectserver ${SOURCES}) + qt6_add_repc_sources(directconnectserver simpleswitch.rep) + \endcode + + More detailed descriptions for these CMake functions can be found + \l {CMake functions} {here}. +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-compatibility.qdoc b/src/remoteobjects/doc/src/remoteobjects-compatibility.qdoc new file mode 100644 index 0000000..640cdb8 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-compatibility.qdoc @@ -0,0 +1,32 @@ +// Copyright (C) 2019 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page qtremoteobjects-compatibility.html +\title Qt Remote Objects Protocol Versioning + +Qt Remote Objects uses an internal protocol to pass data between processes +and/or devices. The same protocol version needs to be used by all parties: if +there is a mismatch, the connecting node will output a warning and the host +node will not send any data. + +Currently released versions: + +\table +\header + \li Protocol Version + \li Qt Version +\row + \li <1.2 + \li Used during QtRO's tech preview phase. +\row + \li 1.2 + \li 5.12.0 +\row + \li 1.3 + \li 5.12.4 +\row + \li 2.0 + \li 6.2.0 +\endtable +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-cpp.qdoc b/src/remoteobjects/doc/src/remoteobjects-cpp.qdoc new file mode 100644 index 0000000..6602f79 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-cpp.qdoc @@ -0,0 +1,23 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\module QtRemoteObjects +\title Qt Remote Objects C++ Classes +\brief Provides an easy to use mechanism for sharing a QObject's properties, signals, or slots, between processes. +\ingroup modules +\qtcmakepackage RemoteObjects +\qtvariable remoteobjects + + To link against the module using CMake, add the following lines to your \c cmake file: + + \snippet cmake-macros/CMakeLists.txt remote_objects_cmake + + \snippet cmake-macros/CMakeLists.txt remote_objects_cmake_link + + To link against the module using \c qmake, add this line to your \c{.pro} file: + + \snippet doc_src_simpleswitch.cpp simpleSwitch_remoteobjectsadd_example1 + + For more information, see \l{Getting Started with Qt Remote Objects}. +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-custom-transport.qdoc b/src/remoteobjects/doc/src/remoteobjects-custom-transport.qdoc new file mode 100644 index 0000000..4045697 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-custom-transport.qdoc @@ -0,0 +1,40 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page qtremoteobjects-custom-transport.html +\title APIs for Implementing Custom Transport Backends +\brief Gives an overview of abstract APIs for implementing custom transport +backends for Qt Remote Objects. + +Qt Remote Objects provides several abstract interfaces for implementing custom +transport backends. The concept behind these classes is that there needs to be +a host node, which has an address that can be connected to. Then there is a +client object, which can be publicly constructed, and can connect to the server. +When the server gets a connection request, it creates the server side of the +connection, which communicates directly with the client. There are thus three +abstractions, one for the server (\c QConnectionAbstractServer), one for the +client-side of the connection (\c QtROClientIoDevice), and the third for the +server-side of the connection (\c QtROServerIoDevice). The latter two inherit +from \c QtROIoDeviceBase. + +\section1 API Overview + +\list +\li \c QtROIoDeviceBase +\li \c QtROClientIoDevice +\li \c QtROServerIoDevice +\li \c QConnectionAbstractServer +\endlist + +After implementing these interfaces, you can register your custom protocol +using the qRegisterRemoteObjectsServer() and qRegisterRemoteObjectsClient() +methods. + +\note These APIs are provided to give more flexibility for implementing custom +communication protocols for Qt Remote Objects. There are no source or binary +compatibility guarantees for them, meaning that the API is only guaranteed to work +with the Qt version it was developed against. API changes will however only be made +in minor releases. (6.1, 6.2, and so on.) + +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-example-dynamic-replica.qdoc b/src/remoteobjects/doc/src/remoteobjects-example-dynamic-replica.qdoc new file mode 100644 index 0000000..a5bff33 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-example-dynamic-replica.qdoc @@ -0,0 +1,50 @@ +// Copyright (C) 2019 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page remoteobjects-example-dynamic-replica.html +\title Example 2: Direct Connection with a Dynamic Replica +\brief Describes how the Qt Remote Objects establishes a direct connection with a dynamic replica. +\target qtro-example2 + +Initially, a dynamic replica is created as a "bare" QObject - without +properties, signals or slots. Then, during initialization, QtRO returns the +API for the object, after the connection to the source is made. Thus, the API +is added to the object at runtime. + +There are no changes to be made on the source side, as a dynamic \l Replica +only impacts how the requestor node acquires the replica. So, we use the +source-side code shown in \l {qtro-example1}{Example 1}. + +\list 1 + \li Add replica generation to the project. + + Because the replica is dynamically acquired, no \c .rep file is + required unlike in \l {qtro-example1}{Example 1}. + + \li Create the remote node and connect it to the source host node. + + The code for this step is unchanged from \l {qtro-example1}{Example 1}. + \snippet doc_src_simpleswitch.cpp simpleSwitch_dynamicclientnode_example2 + + \li Acquire a replica of the remote source object. + + In \c main.cpp, we use a QSharedPointer to hold a replica of the + remote object, and then instantiate a replica requestor object: + + \snippet doc_src_simpleswitch.cpp simpleSwitch_dynamicclientmaincpp_example2 +\endlist + +The complete declaration and definition of the requestor class, +\c DynamicClient, is as follows: + +\c dynamicclient.h +\snippet doc_src_simpleswitch.cpp simpleSwitch_dynamicclientheader_example2 + +\c dynamicclient.cpp +\snippet doc_src_simpleswitch.cpp simpleSwitch_dynamicclientcpp_example2 + +When run together with the source-side example, the output is identical +to \l {qtro-example1}{Example 1}. + +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-example-registry.qdoc b/src/remoteobjects/doc/src/remoteobjects-example-registry.qdoc new file mode 100644 index 0000000..40178a4 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-example-registry.qdoc @@ -0,0 +1,42 @@ +// Copyright (C) 2019 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + +\page remoteobjects-example-registry.html +\title Example 3: Connections to Remote Nodes using a Registry +\brief Describes how the Qt Remote Objects registry establishes connections between nodes. +\target qtro-example3 + +This example illustrates the use of a \l {Registry} to build the node topology. +For simple networks, we use a QUrl to create a direct connection between two +nodes. For complex networks, we use a registry, where you use a different QUrl +to point both the host and replica nodes to the registry. For only two nodes, +the benefits of using a registry are minimal. But, as the network grows, using +a registry means that all nodes only need to connect to the registry via a single +QUrl. In comparison, with direct connections, nodes would have to maintain a +list of QUrls for every single node that they link to. + +\section2 Set up the Source + +The \c simpleswitch.h and \c simpleswitch.cpp sources from \l {qtro-example1} +{Example} can be used without modification. The difference is in the way a host +node is created and connected to the registry: + +\c main.cpp +\snippet doc_src_simpleswitch.cpp simpleSwitch_registrymaincpp_example3 + +\section2 Set up the Replica + +The requestor object used for this example is the dynamic replica client +discussed in \l {qtro-example2}{Example 2}. + +The only modification is in \c main.cpp: a \l {Registry} node is created +to acquire a \l {Replica}: + +\snippet doc_src_simpleswitch.cpp simpleSwitch_registrydynamicclientmaincpp_example3 + +When run together with the source-side example, the output is identical +to \l {qtro-example1}{Example 1}. + +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-example-static-source.qdoc b/src/remoteobjects/doc/src/remoteobjects-example-static-source.qdoc new file mode 100644 index 0000000..a664e51 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-example-static-source.qdoc @@ -0,0 +1,182 @@ +// Copyright (C) 2019 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page remoteobjects-example-static-source +\title Example 1: Direct Connection using a Static Source +\brief Describes how the Qt Remote Objects establishes a direct connection using a static source. +\target qtro-example1 + +In this example, the source object is a simple binary switch that toggles its +state based on a timer. When the state changes, a signal is emitted by the +source which QtRO propagates to all replicas. Since the replicas have the same +properties, signals, and slots as were exposed from the source, any slots +connected to the replica's signal will be called when the replica receives that +signal. The client process then echoes back the received switch state to the +source by emitting its own signal which is connected to a slot on the replica. + +\list 1 +\li \b {Create a source object} + +To create this \l Source object, first we create the definition file, +\c simpleswitch.rep. This file describes the properties and methods for the +object and is input to the Qt Remote Objects Compiler \l{repc}. This file only +defines interfaces that are necessary to expose to the \l{Replica}{Replicas}. + +\c simpleswitch.rep +\snippet doc_src_simpleswitch.cpp simpleSwitch_rep + +In \c simpleswitch.rep, +\list + \li \c currState holds the current state of the switch. + \li \c server_slot() allows us to interact with the source - it will be + connected to the \c echoSwitchState(bool newstate) signal. +\endlist + +For repc to process this file, add the following line to your \c cmake file: + +\snippet cmake-macros/CMakeLists.txt simpleSwitch_cmake_add_repc_source + +If you're using \c qmake: + +\snippet doc_src_simpleswitch.cpp simpleSwitch_repsource_example1 + +These instructions are only relevant for the Qt Remote Object module, +so you need to add it to your project as well. If you're using CMake, +add: + +\snippet cmake-macros/CMakeLists.txt remote_objects_cmake + +\snippet cmake-macros/CMakeLists.txt remote_objects_cmake_link + +If you're using \c qmake: + +\snippet doc_src_simpleswitch.cpp simpleSwitch_remoteobjectsadd_example1 + +repc creates the \c rep_SimpleSwitch_source.h header in the build directory +that you specify. For more information, see \l{Source}. + +repc creates three helper classes for use with QtRO. For this example, we +use the basic: \c SimpleSwitchSimpleSource. It's an abstract class, defined in +\c rep_SimpleSwitch_source.h. We derive from it to define our SimpleSwitch +implementation class as shown below: + +\c simpleswitch.h + +\snippet doc_src_simpleswitch.cpp simpleSwitch_serverheader_example1 + +In \c simpleswitch.h, +\list + \li \c stateChangeTimer is a QTimer that is used to toggle the state of our + SimpleSwitch. + \li \c timeout_slot() is connected to \c stateChangeTimer's timeout() signal. + \li \c server_slot() -- which is called automatically on the source whenever + any replica calls their version of the slot -- outputs the received value. + \li \c currStateChanged(bool), defined in the \l{repc}-generated + \c rep_SimpleSwitch_source.h, is emitted whenever \c currState toggles. + In this example, we ignore the signal on the source side, and handle it + later on the replica side. +\endlist + +The definition of our \c SwitchState class is shown below: + +\c simpleswitch.cpp + +\snippet doc_src_simpleswitch.cpp simpleSwitch_serversource_example1 + +\li \b {Create a registry} + +Because this example uses a direct connection between nodes, we can omit this +step. + +\li \b {Create a host node} + +The host node is created as shown below: + +\snippet doc_src_simpleswitch.cpp simpleSwitch_serverhostnode_example1 + +\li \b {Host source object and remoting} + +The following statements instantiate the \l {Source} object and pass it on to +the host to enable "remoting", which is the process of making an object visible +to the QtRO network: + +\snippet doc_src_simpleswitch.cpp simpleSwitch_enableremoting_example1 + +The contents of \c main.cpp file that implements the steps described above are +as follows: + +\c main.cpp + +\snippet doc_src_simpleswitch.cpp simpleSwitch_servermaincpp_example1 + +Compile and run this source-side project. The output, without any replicas +created, should look as shown below with the switch state toggling between +\c true and \c false every two seconds. + +\image DirectConnectServerOutput.png "Example 1: Server Output" + +\endlist + +The subsequent steps are for creating the replica side of the network, which in +this example gets the state of switch from the \l {Source} and echoes it back. + +\section2 Replica Code + +\list 1 +\li \b {Use repc to add a replica to your project} + +We use the same API definition file as we did on the source side, +\c SimpleSwitch.rep, to create a \l {Replica} header file using the \l {repc}. +If you're using \c cmake, include the following line in your client side +\c cmake file, specifying a \c .rep file input: + +\snippet cmake-macros/CMakeLists.txt simpleSwitch_cmake_add_repc_replica + +If you're using \c qmake, add the following line to your client side \c .pro +file: + +\snippet doc_src_simpleswitch.cpp simpleSwitch_clientrep_example1 + +The \l {repc} tool generates a \c rep_SimpleSwitch_replica.h file in the build +directory. For more information, see \l{Replica}. + +\li \b {Create a node to connect with the source's host node} + +The following code instantiates the second node on the network and connects it +with the source host node: + +\snippet doc_src_simpleswitch.cpp simpleSwitch_clientremotenode_example1 + +\li \b {Call the node's \l{QRemoteObjectNode::}{acquire()} to create a pointer + to a replica} + +First, we instantiate a replica: + +\snippet doc_src_simpleswitch.cpp simpleSwitch_clientacquirereplica_example1 + +\note \l{QRemoteObjectNode::}{acquire()} returns a pointer to the replica, but +doesn't manage its lifetime. This example shows the recommended process of +wrapping the returned pointer in a QSharedPointer or QScopedPointer to ensure +that the pointer is always deleted properly. + +\c main.cpp implements the steps described above and instantiates our object: + +\c main.cpp +\snippet doc_src_simpleswitch.cpp simpleSwitch_clientmain_example1 + +The complete declaration and definition for the \c Client class is as follows: + +\c client.h +\snippet doc_src_simpleswitch.cpp simpleSwitch_clientheader_example1 + +\c client.cpp +\snippet doc_src_simpleswitch.cpp simpleSwitch_clientcpp_example1 + +Compiling and running this example together with the source-side example +generates the following output: + +\image DirectConnectClientServerOutput.png "Direct Connect Server Client Communication output" +\endlist + +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc b/src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc new file mode 100644 index 0000000..aba36d0 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc @@ -0,0 +1,112 @@ +// Copyright (C) 2018 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page qtremoteobjects-external-schemas.html +\title Qt Remote Objects - External QIODevices +\brief Describes how Qt Remote Objects supports custom QIODevice channels. + +\section1 External QIODevices + +Qt Remote Objects supports several communications channels out-of-the-box, such +as the \l QTcpServer and \l QTcpSocket pair. Given the desired \l QUrl for tcp, +or the desired name (for the \l QLocalServer and \l QLocalSocket pair), the +code needed to listen and connect are boilerplate and handled internally by Qt. +Qt Remote Objects supports other types of \l QIODevice as well, and the \l +QRemoteObjectNode classes provide additional methods to support cases where +custom code is needed. + +A contrived example with TCP/IP is shown below. A more realistic example would +use an SSL connection, which would require configuration of certificates, etc. + +\code + // Create the server and listen outside of QtRO + QTcpServer tcpServer; + tcpServer.listen(QHostAddress(QStringLiteral("127.0.0.1")), 65213); + + // Create the host node. We don't need a hostUrl unless we want to take + // advantage of external schemas (see next example). + QRemoteObjectHost srcNode; + + // Make sure any connections are handed to QtRO + QObject::connect(&tcpServer, &QTcpServer::newConnection, &srcNode, + [&srcNode, &tcpServer]() { + srcNode.addHostSideConnection(tcpServer.nextPendingConnection()); + }); +\endcode + +The Replica side code needs to manually connect to the Host +\code + QRemoteObjectNode repNode; + QTcpSocket *socket = new QTcpSocket(&repNode); + QObject::connect(socket, &QTcpSocket::connected, &repNode, + [socket, &repNode]() { + repNode.addClientSideConnection(socket); + }); + socket->connectToHost(QHostAddress(QStringLiteral("127.0.0.1")), 65213); +\endcode + +\section1 External Schemas + +It is possible to create each side of the QIODevice and call \l +{QRemoteObjectNode::addClientSideConnection(QIODevice *ioDevice)} and \l +{QRemoteObjectHostBase::addHostSideConnection(QIODevice *ioDevice)} as shown +above. This is fully supported, but requires the client know how to establish +the connection or have a way to discover that information. This is exactly the +problem the registry was designed to solve. + +Qt Remote Objects also allows "External Schemas" to be used with the registry, +which helps with connection setup. On the \l QRemoteObjectHost side, the +user must set the hostUrl with the desired schema. + +\code + // Use standard tcp url for the registry + const QUrl registryUrl = QUrl(QStringLiteral("tcp://127.0.0.1:65212")); + // Use "exttcp" for the "external" interface + const QUrl extUrl = QUrl(QStringLiteral("exttcp://127.0.0.1:65213")); + + // Create the server and listen outside of QtRO + QTcpServer tcpServer; + tcpServer.listen(QHostAddress(extUrl.host()), extUrl.port()); + + // We need a registry for everyone to connect to + QRemoteObjectRegistryHost registry(registryUrl); + + // Finally, we create our host node and register "exttcp" as our schema. + // We need the AllowExternalRegistration parameter to prevent the node from + // setting a hostUrlInvalid error. + QRemoteObjectHost srcNode(extUrl, registryUrl, QRemoteObjectHost::AllowExternalRegistration); + // From now on, when we call enableRemoting() from this node, the registry + // will be updated to show the Source object at extUrl. +\endcode + +On the \l Replica side, the \l QRemoteObjectNode needs to register a callback +to be used when the external schema is detected. The callback must be a +\l {QRemoteObjectNode::}{RemoteObjectSchemaHandler}. + +\code + // Use standard tcp url for the registry + const QUrl registryUrl = QUrl(QStringLiteral("tcp://127.0.0.1:65212")); + + // This time create the node connected to the registry + QRemoteObjectNode repNode(registryUrl); + + // Create the RemoteObjectSchemaHandler callback + QRemoteObjectNode::RemoteObjectSchemaHandler setupTcp = [&repNode](QUrl url) { + QTcpSocket *socket = new QTcpSocket(&repNode); + connect(socket, &QTcpSocket::connected, + [socket, &repNode]() { + repNode.addClientSideConnection(socket); + }); + connect(socket, &QSslSocket::errorOccurred, + [socket](QAbstractSocket::SocketError error) { + delete socket; + }); + socket->connectToHost(url.host(), url.port()); + }; + + // Once we call registerExternalSchema, the above method will be called + // whenever the registry sees an object we are interested in on "exttcp" + repNode.registerExternalSchema(QStringLiteral("exttcp"), setupTcp); +\endcode +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-gettingstarted.qdoc b/src/remoteobjects/doc/src/remoteobjects-gettingstarted.qdoc new file mode 100644 index 0000000..4c67154 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-gettingstarted.qdoc @@ -0,0 +1,93 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page qtremoteobjects-gettingstarted.html +\title Getting Started with Qt Remote Objects +\brief Detailed information on how to use Qt Remote Objects. + +\section1 Introduction + +The Qt Remote Objects module provides an easy way to share Qt APIs between +processes and devices. For this to work, we require a data channel between +processes and devices. To establish this data channel, first, you need a +QRemoteObjectNode. + +In QtRO, a node is an endpoint for communication. In a remote objects network, +each participant, be it a process or a device, needs its own node. QtRO is a +peer-to-peer network, with connected nodes being the links in the network. + +Nodes, by themselves, don’t provide much use. But their value comes when you +add QObject classes to a node to share. Then, any peer node can request a copy +or instance of the shared object from the \e{host node}, the node that shares +it. + +Unlike when using normal class instances (with independent properties and +signal emissions), QtRO automatically synchronizes changes to the shared object +across all of its copies. With a few exceptions, these copies have the +identical Qt API as the original object, and are meant to be used exactly as if +the original object were available. + +In QtRO, the original object is called the \l{Source}. It's a fully-implemented +C++ class, with the necessary business logic to provide the required +functionality. Copies of this object are called \l{Replica}s. You don’t need +to write any C++ code for a replica; you request an instance from a node +instead. While you do need some code to use the replica, such as connecting +signals to your own slots, you don’t need to implement the internal behavior – +that's already done for you in the source. + +Because the source can be in a different process or even on a different device, +there are factors in QtRO that you need to consider, which you typically +wouldn't run into when developing without Inter-Process Communication (IPC). +Specifically, what happens if the process or device isn't there? This is where +the additions to the Replica API come in: + +\list + \li The \l{QRemoteObjectReplica::}{initialized()} signal is emitted once the + replica has received the \l{Source}{source} state from the QtRO + network. + \li Both the \l{QRemoteObjectReplica::}{isReplicaValid} property and the + \l{QRemoteObjectReplica::}{stateChanged()} signal alert you if the + connection is lost. +\endlist + +Objects shared over QtRO use the links (conduits) between nodes for all +communication. If you want to share a QObject, you must create a \e{host node} +with a URL other nodes can connect to. You can also use the \l{Registry} to +facilitate connections, but your nodes that share \l{Source}{sources} still need +to be host nodes. Each shared object is given a name (a QString), used to +identify it on the QtRO network. + +\section1 Implementation + +To illustrate the use of remote objects, on the source side, we need to: + +\list 1 + \li Create the \l {Source} object that is replicated to other nodes, with or + without using \l repc, the Qt Remote Objects Compiler. + \li Optionally, create the \l{Registry}. Otherwise, use direct connections. + \li Create a host node so that the source object can be shared. + \li Call the node's \l{QRemoteObjectHostBase::}{enableRemoting()} function + to share the source object. +\endlist + +On the replica side, we need to: + +\list 1 + \li Optionally, use \l repc to generate a \l{Replica} header for your project. + \li Create the node that will connect with the \l{Source} host node. + \li Call the node's \l{QRemoteObjectNode::}{acquire()} function to create a + pointer to a replica. +\endlist + +The following examples illustrate both \l{repc}-compiled static objects and dynamic +source objects. Additionally, they also show direct connections as well as +connections that use a \l{Registry} between nodes. + +\list + \li \l{Example 1: Direct Connection using a Static Source} + \li \l{Example 2: Direct Connection with a Dynamic Replica} + \li \l{Example 3: Connections to Remote Nodes using a Registry} +\endlist + +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-index.qdoc b/src/remoteobjects/doc/src/remoteobjects-index.qdoc new file mode 100644 index 0000000..43fdc0d --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-index.qdoc @@ -0,0 +1,130 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page qtremoteobjects-index.html +\title Qt Remote Objects +\ingroup overviews +\brief Provides APIs for inter-process communication. + +\target Qt Remote Objects +\section1 Remote Object Concepts + +Qt Remote Objects (QtRO) is an Inter-Process Communication (IPC) module +developed for Qt. This module extends Qt's existing functionalities to enable +information exchange between processes or computers, easily. + +One of Qt's key features, to enable this information exchange, is the +distinction between an object's API (defined by its properties, signals, and +slots) and the implementation of that API. QtRO's purpose is to meet that +expected API, even if the true QObject is in a different process. A slot +called on a copy of an object (the \l {Replica} in QtRO) is forwarded to the +true object (the \l {Source} in QtRO) for handling. Every Replica receives +updates to the Source, either property changes or emitted signals. + +A \l {Replica} is a light-weight proxy for the \l {Source} object, but a +Replica supports the same connections and behavior of QObjects, which makes it +usable in the same way as any other QObject that Qt provides. Behind the +scenes, QtRO handles everything that's necessary for the Replica to look like +its Source. + +Note that Remote Objects behave differently from traditional Remote Procedure +Call (RPC) implementations, for example: + +\list + \li In RPC, the client makes a request and waits for the response. + \li In RPC, the server doesn't push anything to the client unless it's in + response to a request. + \li Often, the design of RPC is such that different clients are independent + of each other: for instance, two clients can ask a mapping service for + directions and get different results. +\endlist + +While it is possible to implement this RPC-style behavior in QtRO, as Sources +without properties, and slots that have return values, QtRO hides the fact that +the processing is really remote. You let a node give you the Replica instead of +creating it yourself, possibly use the status signals +(\l {QRemoteObjectReplica::} {isReplicaValid()}), but then interact with the +object like you would with any other QObject-based type. + +\section1 Use Case: GPS + +Consider a sensor such as a Global Positioning System (GPS) receiver. In +QtRO terms: + +\list + \li The \l{Source} would be the process that directly interacts with the + GPS hardware and derives your current location. + \li The location would be exposed as QObject properties; the periodic + updates to the location would update these properties and emit property + changed signals. + \li \l{Replica}{Replicas} would be created in other processes and would + always know your current location, but wouldn't need any of the logic + to compute the location from the sensor data. + \li Connecting to the location changed signal on the Replica would work as + expected: the signal emitted from the Source would trigger the signal + emission on every Replica. +\endlist + +\section1 Use Case: Printer Access + +Consider a service that provides access to a printer. In QtRO terms: + +\list + \li The \l{Source} would be the process controlling the printer directly. + \li The ink levels and printer status would be monitored by QObject + properties. Updates to these properties would emit property changed + signals. + \li The key feature -- being able to print something -- needs to be passed + back to the printer. Incidentally, this aligns with the Qt slot mechanism, + which QtRO uses as the way for \l{Replica}{Replicas} to make calls on the + Source. In effect, properties and signals go from Source to Replicas; + slots go from Replica to Source. + \li When a print request is accepted, the printer status would change, + triggering a change in the status property. This would then be reported + to all Replicas. +\endlist + +\include module-use.qdocinc using qt module +\snippet cmake-macros/CMakeLists.txt remote_objects_cmake + +See also the \l {Build with CMake} overview. + +\include module-use.qdocinc building with qmake +\snippet doc_src_simpleswitch.cpp simpleSwitch_remoteobjectsadd_example1 + +\section1 Articles and Guides + \list + \li \l {Getting Started with Qt Remote Objects} + \li \l {Qt Remote Objects Nodes}{Nodes} + \li \l {Qt Remote Objects Source}{Sources} + \li \l {Qt Remote Objects Replica}{Replicas} + \li \l {Qt Remote Objects Registry}{Registry} + \li Using Custom Transports + \list + \li \l {Qt Remote Objects - External QIODevices}{External QIODevices} + \li \l {APIs for Implementing Custom Transport Backends} + {Implementing Custom Transport Backends} + \endlist + \li \l {Qt Remote Objects Compiler}{Compiler} + \li \l {Remote Object Interaction} + \li \l {Troubleshooting Qt Remote Objects}{Troubleshooting} + \li \l {Qt Remote Objects Protocol Versioning}{Protocol Versioning} + \endlist + +\section2 API Reference + +\list + \li \l {Qt Remote Objects C++ Classes} + \li \l {Qt Remote Objects QML Types} +\endlist + +\section1 Licenses + +Qt Remote Objects is available under commercial licenses from \l{The Qt Company}. +In addition, it is available under the +\l{GNU Lesser General Public License, version 3}, or the +\l{GNU General Public License, version 2}. +See \l{Qt Licensing} for further details. + +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-interaction.qdoc b/src/remoteobjects/doc/src/remoteobjects-interaction.qdoc new file mode 100644 index 0000000..6f1472d --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-interaction.qdoc @@ -0,0 +1,26 @@ +// Copyright (C) 2018 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only +/*! +\page qtremoteobjects-interaction.html +\brief Describes how a source and replicas interact with one another. +\section1 Remote Object Interaction + +The interaction between source and replicas is directional. Property changes +and signal emission happen on the source, and are propagated to all replicas. +If a property is writable, you can call the setter function on a replica, which +is then forwarded to the source. Afterwards, if this call results in a new +property value, that value is first changed at the source and then subsequently +forwarded to all replicas. To the replica, it is then an asynchronous call, +with latency before the change takes effect. + +While technically you can emit a signal on a replica, this is discouraged as it +may have unexpected results. It will only trigger slots connected to the +replica itself; not slots connected to the source or other replicas. Like +property setters, slot invocations on a replica are forwarded to the source to +run. + +Qt Remote Objects automatically implements the behavior described above. There +is no need to write any replica implementation code. For dynamic replicas, +this is handled automatically at runtime. For \l{repc} generated headers, this +is handled at compile time. +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-nodes.qdoc b/src/remoteobjects/doc/src/remoteobjects-nodes.qdoc new file mode 100644 index 0000000..ca96dfd --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-nodes.qdoc @@ -0,0 +1,88 @@ +// Copyright (C) 2018 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only +/*! +\page qtremoteobjects-node.html +\title Qt Remote Objects Nodes +\brief Describes how Qt Remote Objects pass data between nodes. + +In a QtRO network, information is passed between processes via +QRemoteObjectNodes ("nodes"). This peer-to-peer functionality uses a small +number of distinct packets passing the necessary data between nodes. + +Each process that participates in the network instantiates a Node-based type, +such as QRemoteObjectNode, QRemoteObjectHost, or QRemoteObjectRegistryHost. +The host types of Nodes provide additional functionality. Both +QRemoteObjectHost and QRemoteObjectRegistryHost support the +\l{QRemoteObjectHostBase::}{enableRemoting()} and the corresponding +\l{QRemoteObjectHostBase::}{disableRemoting()} methods, which are the key +methods to expose source objects to the network. To use the \l Registry +functionality, you need to have a QRemoteObjectRegistryHost on the network. +Then, all other nodes can pass the RegistryHost's URL to the Node's +\c registryAddress constructor parameter, or pass the URL to the +\l {QRemoteObjectNode::}{setRegistryUrl()} method. + +Since QtRO is a peer-to-peer network, to +\l{QRemoteObjectNode::acquire()}{acquire()} a valid \l{Replica}, the replica's +node needs to be connected to the node that hosts its \l{Source}. A host node is a +node that allows other nodes to connect to it, which is accomplished by giving +hosts unique addresses. This address is provided to the QRemoteObjectHost +constructor or set by the setHostUrl method. The node from which a replica is +requested must establish the connection to the host node, to initialize the +replica and keep it up to date. + +\section1 Connecting Nodes using QtRO URLs + +Host Nodes use custom URLs to simplify connections. Currently, QtRO supports +two types of connections: + +\list 1 + \li A TCP connection using the standard TCP/IP protocol - supports + connections between devices as well as between processes on the same + device. + \li A local connection - supports connections between processes on the same + device. This type of connection can have less overhead, depending on + the underlying Operating System features. +\endlist + +For local connections, you must use a unique name. For TCP connections, you +must provide a unique address and port number combination. + +Currently, QtRO does not include a \l {http://www.zeroconf.org/} {zeroconf} +facility. Therefore, all processes or devices must know beforehand, how to +connect to each other. A \l {QRemoteObjectRegistry} can be used to simplify +the connection process for a network with multiple Host Nodes. + +The table below summarizes the connection types available: + + \table 90% + \header + \li URL + \li Notes + \row + \li {QUrl}("local:service") + \li Uses (internally) {QLocalServer}/{QLocalSocket} classes to + communicate between nodes. + \row + \li {QUrl}("tcp://192.168.1.1:9999") + \li Uses (internally) {QTcpServer}/{QTcpSocket} classes to + communicate between nodes. + \row + \li {QUrl}("qnx:service") + \li QNX OS only. Uses a custom (named) channel for native + communication between nodes. + \row + \li {QUrl}("localabstract:service") + \li Since 6.2. Linux/Android OSes only. Uses an abstract namespace + for Unix domain sockets. This allows QLocalSocket behavior to work on + non-writable devices. + \endtable + +Nodes have a few \l{QRemoteObjectHostBase::enableRemoting()} +{enableRemoting()} methods that are used to share objects on the network. +However, if the node is not a host node, an error is returned. + +Other processes or devices that want to interact with a shared object can +use one of the \l{QRemoteObjectNode::acquire()} {node's acquire()} methods, +to instantiate a replica. + +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-qml.qdoc b/src/remoteobjects/doc/src/remoteobjects-qml.qdoc new file mode 100644 index 0000000..e576dd0 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-qml.qdoc @@ -0,0 +1,82 @@ +// Copyright (C) 2018 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\qmlmodule QtRemoteObjects 6.\QtMinorVersion +\title Qt Remote Objects QML Types +\ingroup qmlmodules +\brief Provides QML types for remote objects support. + +The QML types for \l{Qt Remote Objects} provide the helper pieces needed to build a remote objects network. +They are typically used in conjunction with custom-registered replica types that make up a specific +network. + +As an example, consider the following .rep file: +\code +class MyType { + PROP(QString myProp="Hello World") +}; +\endcode + +The generated replica can be registered as a QML type: +\code +qmlRegisterType("custom",1,0,"MyTypeReplica") +\endcode + +And then used from QML in conjunction with the base type Node: +\qml +import QtQuick +import QtRemoteObjects +import custom 1.0 + +Item { + MyTypeReplica { + id: myType + node: Node { registryUrl: "local:registry" } + } + + Text { text: myType.myProp } + + MouseArea { + anchors.fill: parent + onClicked: myType.pushMyProp("Updated Text") + } +} +\endqml + +Note that by default you cannot directly assign to a replica property, but rather use a \c push function. +This is due to the potential problems that arise from the mix of declarative programming and asynchronous updates. +Specifically, we want to avoid issues like the following: +\badcode +myType.myProp = "Updated Text" +console.log(myType.myProp) // logs "Hello World", as the new text has not yet been round-tripped +\endcode + +The QML types in this module can be imported into your application using the following import +statement in your .qml file: +\qml +import QtRemoteObjects +\endqml + +\section1 QML Types +*/ + +/*! +\qmltype QtRemoteObjects +\inqmlmodule QtRemoteObjects +\since 5.14 +\brief The QtRemoteObjects global object provides useful functions for working with remote + types in QML. +*/ + +/*! + \qmlmethod Promise QtRemoteObjects::QtRemoteObjects::watch(QRemoteObjectPendingCall reply, int timeout = 30000) + Encapsulates the return value, represented by \a reply, from a replica in a JavaScript Promise. + A timeout can be set with \a timeout. The default timeout value is 30 seconds. + + \qml + QtRemoteObjects.watch(replica.fetchDetails(identifier)) + .then(function(value) { details = value }, + function(error) { console.log("error fetching details:", error) }) + \endqml +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-registry.qdoc b/src/remoteobjects/doc/src/remoteobjects-registry.qdoc new file mode 100644 index 0000000..23355d7 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-registry.qdoc @@ -0,0 +1,33 @@ +// Copyright (C) 2018 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page qtremoteobjects-registry.html +\title Qt Remote Objects Registry +\brief Describes how the Qt Remote Objects registry establishes connections + between nodes. +\target Registry + +When you \l {QRemoteObjectNode::acquire()} a replica, the node URL is not passed +as an argument. This means you do not need to specify the host node, but it does +require you to have some other means of connecting to that host. Without the +registry, it is necessary to manually call \l {QRemoteObjectNode::connectToNode()}, +from each node, to every host node that has \l {Source} objects it should +replicate. This is fine for small or static networks, but does not scale. + +The registry provides a simpler way to establish these connections. Every node +that wants to be part of the registry's network connects to the registry. The +registry is itself a specialized source object, and thus is hosted by a node. +Connecting to the registry is simply a matter of passing the registry's URL to +the QRemoteObjectNode or QRemoteObjectHost constructor, or passing the URL to +the setRegistryUrl method. + +The registry is tightly integrated with QtRO. Whenever a \l {Source} is added +or removed, the name/URL is updated in the registry automatically. So once +your node is connected to the registry, it is not necessary to connect to any +other nodes manually. If you request an object on the network and you aren't +connected to the hosting node, the registry will know what URL to connect to +and will initiate the connection. Once connected (and the list of available +objects is passed along, including the desired \l {Source}), the initialization +process for the requested \l Replica will start automatically. +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-repc.qdoc b/src/remoteobjects/doc/src/remoteobjects-repc.qdoc new file mode 100644 index 0000000..6524fc6 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-repc.qdoc @@ -0,0 +1,410 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \externalpage https://en.wikipedia.org/wiki/Domain-specific_language + \title Domain Specific Language (DSL) +*/ + +/*! +\page qtremoteobjects-repc.html +\title Qt Remote Objects Compiler +\brief The Qt Remote Objects Compiler creates \l {Source} and \l {Replica} header files +\ingroup overviews +\keyword repc + + \section1 REPC Overview + + The \underline {Rep}lica \underline {C}ompiler (repc) generates QObject + header files based on an API definition file. The file (called a "rep" + file) uses a specific (text) syntax to describe the API. By convention, + these files are given a .rep file extension, short for Replica. When these + files are processed by repc, repc generates both \l {Source} and \l + {Replica} header files. + + The Qt Remote Objects module also includes \l {CMake functions} and + \l {qmake variables} that can be added to your project file to automatically run + repc, and add the resulting files to the list of files processed by + \l{moc}{Meta Object Compiler} during the build process, making use of Qt + Remote Objects in your projects simple. + + While Qt Remote Objects supports sharing any QObject over the network (using + enableRemoting on the Source side and acquireDynamic on the Replica side), + there are a couple of advantages to letting repc define your objects. First + of all, while \l {QRemoteObjectDynamicReplica} {DynamicReplicas} are + useful, they are more cumbersome to work with. The API is not known until + the object is initialized, and using the API from C++ requires string + lookups through QMetaObject's methods. Secondly, having the interface known + at compile time finds any issues at compile vs. at runtime. Thirdly, the rep + format supports default values, which can be handy if you are unable to + ensure the Source is available when the Replica is instantiated. + + See the documentation \l {Source} {here} for information on using + the generated files in your code. Here we will focus on the repc format and + options. + + \section1 The rep file format + + The rep file format is a simple \l {Domain Specific Language (DSL)} for + describing an interface supported over Qt Remote Objects (QtRO). Since QtRO + is an object based system, these interfaces are defined by APIs available + through objects, that is, classes with properties, signals, and slots. + + \section2 The class type + + Each class defined in a rep file becomes a QObject in the generated header + files, with the described API generated for you. + + To define a class use the \c class keyword, followed by the name you + want for your type, and then enclose your API in brackets like so + \code + class MyType + { + //PROP/CLASS/MODEL/SIGNAL/SLOT/ENUM declarations to define your API + }; + \endcode + + \section3 PROP + + Q_PROPERTY elements are created by using the PROP keyword in the rep + file. The syntax is the \c PROP keyword followed by the definition enclosed + in parentheses, where the definition is the type, the name, and (optionally) a + default value or attributes. + \code + PROP(bool simpleBool) // boolean named simpleBool + PROP(bool defaultFalseBool=false) // boolean named defaultFalseBool, with false + // as the default value + + PROP(int lifeUniverseEverything=42) // int value that defaults to 42 + PROP(QByteArray myBinaryInfo) // Qt types are fine, may need #include + // additional headers in your rep file + + PROP(QString name CONSTANT) // Property with the CONSTANT attribute + PROP(QString setable READWRITE) // Property with the READWRITE attribute + // note: Properties default to READPUSH + // (see description below) + + PROP(SomeOtherType myCustomType) // Custom types work. Needs #include for the + // appropriate header for your type, make + // sure your type is known to the metabject + // system, and make sure it supports Queued + // Connections (see Q_DECLARE_METATYPE and + // qRegisterMetaType) + \endcode + More information about creating custom types can be found \l {Creating + Custom Qt Types} {here}. + + By default, properties will have getters and a "push" slot defined, as well + as a notify signal emitted when the value is changed. Qt Remote Objects + requires the notify signal on the Source object to trigger sending updates + to the attached Replicas. In earlier versions of QtRO, properties defaulted + to being read/write, that is, having getters and setters. However, due to the + asynchronous nature of QtRO, this led to unintuitive behavior at times. + Setting the READWRITE attribute on the PROP will provide the old (getter + and setter) behavior. + \code + // In .rep file, old (setter) behavior + PROP(int myVal READWRITE) // Old behavior with setMyVal(int myVal) method + + // In code... Assume myVal is initially set to 0 in Source + int originalValue = rep->myVal(); // Will be 0 + rep->setMyVal(10); // Call setter, expecting a blocking/ + // non-asynchronous return + + if (rep->myVal() == 10) ... // Test will usually fail + \endcode + + If it is necessary to block until the value is changed, something like the + following is necessary. + \code + // In .rep file, old (setter) behavior + PROP(int myVal READWRITE) // Old behavior with setMyVal(int myVal) method + + // In code... Assume myVal is initially set to 0 in Source + bool originalValue = rep->myVal(); // Will be 0 + + // We can wait for the change using \l QSignalSpy + QSignalSpy spy(rep, SIGNAL(myValChanged(int))); + + rep->setMyVal(10); // Call setter, expecting a blocking/ + // non-asynchronous return + + spy.wait(); // spy.wait() blocks until changed signal + // is received + if (rep->myVal() == 10) ... // Test will succeed assuming + // 1. Source object is connected + // 2. Nobody else (Source or other Replica) + // sets the myVal to something else (race + // condition) + // Rather than use QSignalSpy, the event-driven practice would be to connect the + // myValChanged notify signal to a slot that responds to the changes. + \endcode + + QtRO now defaults to READPUSH, which provides an automatically generated + slot for requesting a property change. + \code + // In .rep file, defaults to READPUSH + PROP(bool myVal) // No setMyVal(int myVal) on Replica, has + // pushMyVal(int myVal) instead + + // In code... Assume myVal is initially set to 0 in Source + bool originalValue = rep->myVal(); // Will be 0 + + // We can wait for the change using \l QSignalSpy + QSignalSpy spy(rep, SIGNAL(myValChanged(int))); + + rep->pushMyVal(10); // Call push method, no expectation that change + // is applied upon method completion. + + // Some way of waiting for change to be received by the Replica is still necessary, + // but hopefully not a surprise with the new pushMyVal() Slot. + spy.wait(); // spy.wait() blocks until changed signal + // is received + if (rep->myVal() == 10) ... // Test will succeed assuming + // 1. Source object is connected + // 2. Nobody else (Source or other Replica) + // set the myVal to something else (race + // condition) + \endcode + + You can also use the \c CONSTANT, \c READONLY, \c PERSISTED, \c READWRITE, + \c READPUSH, or \c SOURCEONLYSETTER keywords in the PROP declaration, which + affects how the property is implemented. READPUSH is the default value if no value + used. + + \code + PROP(int lifeUniverseEverything=42 CONSTANT) + PROP(QString name READONLY) + \endcode + + Please note there are some subtleties here. A CONSTANT PROP has a + Q_PROPERTY declared as CONSTANT on the SOURCE side. However, replicas + cannot know the correct value until they are initialized, which means the + property value has to be allowed to change during initialization. For + READONLY, the source will have neither a setter nor a push slot, and the + replica side will not have a push slot generated. Adding the PERSISTED + trait to a PROP will have the PROP use the \l QRemoteObjectAbstractPersistedStore + instance set on a Node (if any) to save/restore PROP values. + + Another nuanced value is SOURCEONLYSETTER, which provides another way of + specifying asymmetric behavior, where the \l Source (specifically the helper + class, \c SimpleSource) will have a public getter and setter for the + property, but it will be ReadOnly (with a notify signal) on the \l Replica + side. Thus the property can be fully controlled from the \l Source side, + but only observed from the \l Replica side. SOURCEONLYSETTER is the mode + used by repc for MODEL and CLASS instances, meaning the \l Source can + change the pointed to object, but the \l Replica can not provide a new + object, as no set or push method is generated. Note, this does + not impact the behavior of the pointed to type's properties, just the + ability to change the pointer itself. + + \sa QRemoteObjectAbstractPersistedStore + + \section3 CLASS + + The CLASS keyword generates special Q_PROPERTY elements for objects derived from QObject. + These properties have the same semantics as SOURCEONLYSETTER. The syntax is the \c CLASS + keyword followed by the property name and then the type of subobject enclosed in parentheses. + \code + // In .rep file + class OtherClass + { + PROP(int value) + } + + class MainClass + { + CLASS subObject(OtherClass) + } + \endcode + + \section3 MODEL + + The MODEL keyword generates special Q_PROPERTY elements for objects derived from QAbstractItemModel. + These properties have the same semantics as SOURCEONLYSETTER. The syntax is the \c MODEL + keyword followed by the property name and then parentheses enclosing the (comma-separated) roles + that should be exposed to the replica. + \code + // In .rep file + class CdClass + { + PROP(QString title READONLY) + MODEL tracks(title, artist, length) + } + \endcode + + \section3 SIGNAL + + \l [DOC QtCore] {Signals} {Signal} methods are created by using the SIGNAL keyword in the rep file. + + Usage is to declare \c SIGNAL followed by the desired signature wrapped in + parentheses. The void return value should be skipped. + + \code + SIGNAL(test()) + SIGNAL(test(QString foo, int bar)) + SIGNAL(test(QMap foo)) + SIGNAL(test(const QString &foo)) + SIGNAL(test(QString &foo)) + \endcode + + Just like in Qt \l {Qt::ConnectionType} {queued connections}, parameters in signals that are + references will be copied when being passed to replicas. + + \section3 SLOT + + \l [DOC QtCore] {Slots} {Slot} methods are created by using the SLOT keyword in the rep file. + + Usage is to declare \c SLOT followed by the desired signature wrapped in + parentheses. The return value can be included in the declaration. If the + return value is skipped, void will be used in the generated files. + + \code + SLOT(test()) + SLOT(void test(QString foo, int bar)) + SLOT(test(QMap foo)) + SLOT(test(QMap foo, QMap bar)) + SLOT(test(QMap,int> foo)) + SLOT(test(const QString &foo)) + SLOT(test(QString &foo)) + SLOT(test(const QMap,int> &foo)) + SLOT(test(const QString &foo, int bar)) + \endcode + + Just like in Qt \l {Qt::ConnectionType} {queued connections} and QtRO + SIGNALS, parameters in slots that are references will be copied when being + passed to Replicas. + + \section3 ENUM + + Enumerations (which use a combination of C++ enum and Qt's Q_ENUM in QtRO) + are described using the ENUM keyword. + + \code + ENUM MyEnum {Foo} + ENUM MyEnum {Foo, Bar} + ENUM MyEnum {Foo, Bar = -1} + ENUM MyEnum {Foo=-1, Bar} + ENUM MyEnum {Foo=0xf, Bar} + ENUM MyEnum {Foo=1, Bar=3, Bas=5} + \endcode + + Related topics: \l {The ENUM type}, \l {USE_ENUM keyword} + + \section2 The POD type + + Plain Old Data (POD) is a term to describe a simple data collection, along + the lines of a C++ struct. For example, if you have an API for a phonebook, + you may want to use the concept of an "address" in its interface (where + address might include street, city, state, country, and postal code). You + can use the POD keyword to define objects like this, which can then be used + by in PROP/SIGNAL/SLOT definitions in your class definitions. + + Usage is to declare \c POD followed by the name for the generated type, + followed by type and name pairs separated by commas, where the type/name + pairs are wrapped in parentheses. + + \code + POD Foo(int bar) + POD Foo(int bar, double bas) + POD Foo(QMap bar) + POD Foo(QList bar) + POD Foo(QMap bar, QMap bas) + \endcode + + A full example would look like this + \code + POD Foo(QList bar) + class MyType + { + SIGNAL(sendCustom(Foo foo)); + }; + \endcode + + The code generated by repc creates a Q_GADGET class for each POD, with + corresponding Q_PROPERTY members for each type defined for the POD. + + \section2 The ENUM type + + It is often easier and cleaner to define an ENUM inside a class (see \l ENUM), + but if you need a standalone enum type, using the ENUM keyword outside of a + class definition can be helpful. This will generate a new class in your + header files that handles marshalling, etc.. The syntax is identical to \l + ENUM, with the exception that the declaration in this case is not contained + in a \c class declaration. + + Related topics: \l {ENUM}, \l {USE_ENUM keyword} + + \section2 USE_ENUM keyword + + The USE_ENUM keyword was implemented before autogeneration via the ENUM + keyword was added. It is kept for backwards compatibility. + + Related topics: \l {ENUM}, \l {The ENUM type} + + \section2 Directives + + The rep file defines an interface, but interfaces often require external + elements. In order to support this, repc will include any (single line) + directives at the top of the generated files. This allows you to, for + instance, use #include or #define directives that support the logic or + datatypes needed. + + The repc tool currently ignores everything from the "#" symbol to the + end-of-line and adds that to the generated files. So multi-line + #if/#else/#endif statements and multi-line macros are not supported. + + \section1 CMake functions + + The CMake functions for generating source and replica types are listed below. + + \annotatedlist cmake-macros-qtremoteobjects + + \section1 qmake variables + \section2 REPC_REPLICA + + Specifies the names of all rep files in the project that should be used to generate + replica header files. + + For example: + \code + REPC_REPLICA = media.rep \ + location.rep + \endcode + + The generated file(s) will be of the form \c {rep__replica.h}. + + \section2 REPC_SOURCE + + Specifies the names of all rep files in the project that should be used to generate + source header files. + + For example: + \code + REPC_SOURCE = media.rep \ + location.rep + \endcode + + The generated file(s) will be of the form \c {rep__source.h}. + + \section2 REPC_MERGED + + Specifies the names of all rep files in the project that should be used to generate + combined (source and replica) header files. + + For example: + \code + REPC_MERGED = media.rep \ + location.rep + \endcode + + The generated file(s) will be of the form \c {rep__merged.h}. + + \note Typically sources and replicas live in separate processes or devices, so this variable + is not commonly used. + + \section2 QOBJECT_REP + + Specifies the names of existing QObject header files that should be used to generate corresponding + .rep files. +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-replica.qdoc b/src/remoteobjects/doc/src/remoteobjects-replica.qdoc new file mode 100644 index 0000000..f52376a --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-replica.qdoc @@ -0,0 +1,89 @@ +// Copyright (C) 2018 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only +/*! +\page qtremoteobjects-replica.html +\title Qt Remote Objects Replica +\brief Describes the concept of a replica and how it works as a surrogate object. +\target Replica + +A QRemoteObjectReplica ("replica") is a surrogate object that has approximately +the same API as the \l {Source} QObject it replicates. Additionally, there are +a few properties and signals to make it possible to detect when the replica is +initialized or if it loses its connection to the source object. There +are a few other differences, notably, a constant property on the source cannot +be constant on the replica. The value will not be known at the time when the +replica is instantiated; it will only be known once the replica is initialized. +For more information, see \l {Remote Object Interaction}. + +A compiled replica is a \l {QRemoteObjectReplica} based type, where the derived +class definition is automatically generated by the \l {repc} compiler. When you +use \l {CMake functions} or \l {qmake variables} for running the \l repc compiler, +this makes the generation part of the build process. Although only a header is +generated, it's a complete type. There is no public constructor, so you need to +use the \l {QRemoteObjectNode::acquire} template function to create the Replica +instance. + +A \l {QRemoteObjectDynamicReplica} can be generated at runtime. To do so, you +call \l {QRemoteObjectNode::acquireDynamic()}, passing in the source name +(a QString) as an argument. Dynamic replicas are a bit more verbose to use from +C++, but they do not require compilation. Dynamic replicas do not support +initial property values, or introspection until they have been initialized. + +An important difference between these two ways of creating replicas is the +behavior before the replica is initialized. Since a dynamic replica only gets +a metaObject after initialization, it has basically no API before +initialization -- no properties, and no signals to connect slots to. + +Because metaObjects for compiled replicas are created at compile-time, their +API is available when the replica is instantiated. You can even provide default +values for properties in the template file, which are used until the replica is +initialized with current values from the source. + +\section1 Replica Initialization + +A host node will share the list of sources that it hosts with every other node +that connects to it. This host sends updates when sources are added to or +removed from the list. In this way, a connected node will always know what +sources it can attach itself to. Changes to a specific source are only +propagated to nodes that have a replica of that source. Consequently, this +avoids any unnecessary network traffic. + +When a node acquires a replica for a known source, it sends a request for that +source to the host node. Upon receiving this request, the host creates a reply +packet with the current values for all properties of that source. If the +requested replica is \l{QRemoteObjectDynamicReplica}{dynamic}, the reply packet +includes the API definition for the source. From then on, the replica's node +will be included in the list of connections that receive changes to that +source. + +If a replica is instantiated but its node is not connected to the node that +hosts the requested source -- or that object lives in a host node process, but +sharing/remoting has not been enabled for the QObject -- the Replica will still +be created, but remain uninitialized. + +If, at a later time, the replica's node gets notified that the requested source +is available from a connected node, at that point it will request the source +and start the initialization process. + +If the connection to a host node is lost, the replica will transition to the +invalid state. It will attempt to reconnect and will re-initialize if the +connection is restored; this makes sure all properties are current. + +\section1 Replica Ownership + +The acquire methods return a pointer to the replica QObject instantiated by the +node. The node has no way of knowing the replica's intended lifetime. +Consequently, when the replica is not longer needed, it is the calling +program's responsibility to delete it. + +You can instantiate multiple copies of the same replica. All replicas of the +same source from a single node will share a private data member which handles +the network communication. This means multiple instances of a replica do not +introduce additional network traffic, although there will be some additional +processing overhead. Failing to delete replicas will prevent the reference +count on this private object from reaching 0, and cause unnecessary network +communication until the calling process exits. For this reason, it is +recommended to use \l {QScopedPointer} or \l {QSharedPointer} to help track +the lifetime of replicas. + +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-source.qdoc b/src/remoteobjects/doc/src/remoteobjects-source.qdoc new file mode 100644 index 0000000..3b9c555 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-source.qdoc @@ -0,0 +1,93 @@ +// Copyright (C) 2018 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only +/*! +\page qtremoteobjects-source.html +\title Qt Remote Objects Source +\brief Describes the concept of a source and how to use the source header that the repc generates. +\target Source + +A QRemoteObjectSource ("source") is the QObject that is responsible for +implementing the exposed API. + +At a high level, you can choose to use a QObject type directly as a source; or +you can define the desired API in a \c{.rep} template for use with the +\l {repc} compiler. + +If you already have a fully defined QObject, you can use it as a source by +passing it to \l {QRemoteObjectHostBase::enableRemoting()}. This way, other +processes or devices can then create +\l{QRemoteObjectDynamicReplica}{dynamics replicas} of the object to interact +with. + +For more information, see \l{Remote Object Interaction}. + +Letting repc generate a source header file for your project, using the +\l {REPC_SOURCE} variable, provides three different options to implement the +required API. + +Suppose your class name is Foo, you then have the following options: + +\list + \li \l {TypeSimpleSource} {FooSimpleSource} inheritance + \li \l {TypeSource} {FooSource} inheritance + \li \l {TypeAPI} {FooSourceAPI} usage with your own QObject +\endlist + +For more details on how to create a \c{.rep} file, see \l{The rep file format}. + +\target TypeSimpleSource +There is a SimpleSource class defined in the header, which provides the +basic getter/setter methods for each property and implements data members of +the correct property type in the header. "" here represents the class +name from the \c{.rep} file, so if your class is of type "MyType" in the +\c{.rep} file, then the generated header file will have a MyTypeSimpleSource +class declared. This is a quick way to get started using the API. To use this +class, you need to write a class that inherits from it and implement any slots +defined, which are marked as pure virtual in the generated header file. +You need to add the necessary logic to manage the exposed properties and emit +signals, to your class as well. + +\target TypeSource +If you need to hide your implementation details, use the second class declared +in the header file, the Source class. This class' definition does not +provide data members, and also makes the getter/setter functions pure virtual. +While you may need to write more code, using this class gives you more +flexibility in your implementation. + +\target TypeAPI +The third class generated in the header is SourceAPI. This is a templated +class, for use specifically by the templated version of +\l {QRemoteObjectHostBase::enableRemoting()}, which allows you to use any +QObject that supports the required API as the source. Use this class to hide or +convert properties or signal/slot parameters. If your implementation +does not provide the correct API, there will be compile-time warnings. + +\note Replicas and sources both derive from QObject; but their QObject API is +\b never exposed. For instance, while a replica has a \l{QObject::}{destroyed} +signal, the source's \l{QObject::}{destroyed} signal is not propagated. The +source and each of its replica are unique QObjects with their own connections +and lifetimes. Instead, the API that is exposed is defined by the \c{.rep} +template used by repc. In the case of raw QObjects, all API elements are defined +in the inheritance chain from a specific ancestor. The QObject's parent is always +used, unless you define \c{Q_CLASSINFO("RemoteObject Type")} in an ancestor. If +you use \c{Q_CLASSINFO("RemoteObject Type")}, that class's API is the lowest +level of API used. + +\section1 Identifying Sources + +Because a host node can share more than one source, each source requires a name. +All repc-generated headers include a way for the node to determine the class name: +Q_CLASSINFO for replica, simplesource, or source types; or a static \c name() +function for the SourceAPI type. If you pass your own QObject type to +\l {QRemoteObjectHostBase::enableRemoting()}, the name is determined using the +following logic: +\list + \li A name can optionally be passed to \l {QRemoteObjectHostBase::enableRemoting()}. + If provided, that name is used. + \li If the object or any of its ancestors has Q_CLASSINFO of type + "RemoteObject Type" defined, that type name is used. + \li If the QObject's objectName is set, then it is used. + \li If none of the above are available, the call to + \l {QRemoteObjectHostBase::enableRemoting()} fails, returning false. +\endlist +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-troubleshooting.qdoc b/src/remoteobjects/doc/src/remoteobjects-troubleshooting.qdoc new file mode 100644 index 0000000..a97f675 --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-troubleshooting.qdoc @@ -0,0 +1,16 @@ +// Copyright (C) 2018 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only +/*! +\page qtremoteobjects-troubleshooting.html +\title Troubleshooting Qt Remote Objects +\section1 Troubleshooting + +QtRO includes a number of internal logging categories that can be used for further debugging: + +\code +qt.remoteobjects +qt.remoteobjects.io +qt.remoteobjects.models +\endcode + +*/ diff --git a/src/remoteobjects/qconnection_local_backend.cpp b/src/remoteobjects/qconnection_local_backend.cpp new file mode 100644 index 0000000..337af69 --- /dev/null +++ b/src/remoteobjects/qconnection_local_backend.cpp @@ -0,0 +1,195 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qconnection_local_backend_p.h" + +QT_BEGIN_NAMESPACE + +LocalClientIo::LocalClientIo(QObject *parent) + : QtROClientIoDevice(parent) + , m_socket(new QLocalSocket(this)) +{ + connect(m_socket, &QLocalSocket::readyRead, this, &QtROClientIoDevice::readyRead); + connect(m_socket, &QLocalSocket::errorOccurred, this, &LocalClientIo::onError); + connect(m_socket, &QLocalSocket::stateChanged, this, &LocalClientIo::onStateChanged); +} + +LocalClientIo::~LocalClientIo() +{ + close(); +} + +QIODevice *LocalClientIo::connection() const +{ + return m_socket; +} + +void LocalClientIo::doClose() +{ + if (m_socket->isOpen()) { + connect(m_socket, &QLocalSocket::disconnected, this, &QObject::deleteLater); + m_socket->disconnectFromServer(); + } else { + this->deleteLater(); + } +} + +void LocalClientIo::doDisconnectFromServer() +{ + m_socket->disconnectFromServer(); +} + +void LocalClientIo::connectToServer() +{ +#ifdef Q_OS_ANDROID + if (!m_socket->socketOptions().testFlag(QLocalSocket::AbstractNamespaceOption)) + qWarning() << "It is recommended to use 'localabstract' over 'local' on Android."; +#endif + if (!isOpen()) + m_socket->connectToServer(url().path()); +} + +bool LocalClientIo::isOpen() const +{ + return !isClosing() && (m_socket->state() == QLocalSocket::ConnectedState + || m_socket->state() == QLocalSocket::ConnectingState); +} + +void LocalClientIo::onError(QLocalSocket::LocalSocketError error) +{ + qCDebug(QT_REMOTEOBJECT) << "onError" << error << m_socket->serverName(); + + switch (error) { + case QLocalSocket::ServerNotFoundError: + case QLocalSocket::UnknownSocketError: + case QLocalSocket::PeerClosedError: + //Host not there, wait and try again + emit shouldReconnect(this); + break; + case QLocalSocket::ConnectionError: + case QLocalSocket::ConnectionRefusedError: + //... TODO error reporting +#ifdef Q_OS_UNIX + emit shouldReconnect(this); +#endif + break; + default: + break; + } +} + +void LocalClientIo::onStateChanged(QLocalSocket::LocalSocketState state) +{ + if (state == QLocalSocket::ClosingState && !isClosing()) { + m_socket->abort(); + emit shouldReconnect(this); + } + if (state == QLocalSocket::ConnectedState) + initializeDataStream(); +} + +LocalServerIo::LocalServerIo(QLocalSocket *conn, QObject *parent) + : QtROServerIoDevice(parent), m_connection(conn) +{ + m_connection->setParent(this); + connect(conn, &QIODevice::readyRead, this, &QtROServerIoDevice::readyRead); + connect(conn, &QLocalSocket::disconnected, this, &QtROServerIoDevice::disconnected); +} + +QIODevice *LocalServerIo::connection() const +{ + return m_connection; +} + +void LocalServerIo::doClose() +{ + m_connection->disconnectFromServer(); +} + +LocalServerImpl::LocalServerImpl(QObject *parent) + : QConnectionAbstractServer(parent) +{ + connect(&m_server, &QLocalServer::newConnection, this, &QConnectionAbstractServer::newConnection); +} + +LocalServerImpl::~LocalServerImpl() +{ + m_server.close(); +} + +QtROServerIoDevice *LocalServerImpl::configureNewConnection() +{ + if (!m_server.isListening()) + return nullptr; + + return new LocalServerIo(m_server.nextPendingConnection(), this); +} + +bool LocalServerImpl::hasPendingConnections() const +{ + return m_server.hasPendingConnections(); +} + +QUrl LocalServerImpl::address() const +{ + QUrl result; + result.setPath(m_server.serverName()); + result.setScheme(QRemoteObjectStringLiterals::local()); + + return result; +} + +bool LocalServerImpl::listen(const QUrl &address) +{ +#ifdef Q_OS_ANDROID + if (!m_server.socketOptions().testFlag(QLocalServer::AbstractNamespaceOption)) + qWarning() << "It is recommended to use 'localabstract' over 'local' on Android."; +#endif +#ifdef Q_OS_UNIX + bool res = m_server.listen(address.path()); + if (!res) { + QLocalServer::removeServer(address.path()); + res = m_server.listen(address.path()); + } + return res; +#else + return m_server.listen(address.path()); +#endif +} + +QAbstractSocket::SocketError LocalServerImpl::serverError() const +{ + return m_server.serverError(); +} + +void LocalServerImpl::close() +{ + m_server.close(); +} + +#ifdef Q_OS_LINUX + +AbstractLocalClientIo::AbstractLocalClientIo(QObject *parent) + : LocalClientIo(parent) +{ + m_socket->setSocketOptions(QLocalSocket::AbstractNamespaceOption); +} + +AbstractLocalServerImpl::AbstractLocalServerImpl(QObject *parent) + : LocalServerImpl(parent) +{ + m_server.setSocketOptions(QLocalServer::AbstractNamespaceOption); +} + +QUrl AbstractLocalServerImpl::address() const +{ + QUrl result; + result.setPath(m_server.serverName()); + result.setScheme(QRemoteObjectStringLiterals::localabstract()); + + return result; +} + +#endif // Q_OS_LINUX + +QT_END_NAMESPACE diff --git a/src/remoteobjects/qconnection_local_backend_p.h b/src/remoteobjects/qconnection_local_backend_p.h new file mode 100644 index 0000000..3a2cbc4 --- /dev/null +++ b/src/remoteobjects/qconnection_local_backend_p.h @@ -0,0 +1,108 @@ +// Copyright (C) 2017-2015 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QCONNECTIONCLIENTFACTORY_P_H +#define QCONNECTIONCLIENTFACTORY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qconnectionfactories_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class LocalClientIo : public QtROClientIoDevice +{ + Q_OBJECT + +public: + explicit LocalClientIo(QObject *parent = nullptr); + ~LocalClientIo() override; + + QIODevice *connection() const override; + void connectToServer() override; + bool isOpen() const override; + +public Q_SLOTS: + void onError(QLocalSocket::LocalSocketError error); + void onStateChanged(QLocalSocket::LocalSocketState state); + +protected: + void doClose() override; + void doDisconnectFromServer() override; + QLocalSocket* m_socket; +}; + +#ifdef Q_OS_LINUX + +class AbstractLocalClientIo final : public LocalClientIo +{ + Q_OBJECT + +public: + explicit AbstractLocalClientIo(QObject *parent = nullptr); +}; + +#endif // Q_OS_LINUX + +class LocalServerIo final : public QtROServerIoDevice +{ + Q_OBJECT +public: + explicit LocalServerIo(QLocalSocket *conn, QObject *parent = nullptr); + + QIODevice *connection() const override; +protected: + void doClose() override; + +private: + QLocalSocket *m_connection; +}; + +class LocalServerImpl : public QConnectionAbstractServer +{ + Q_OBJECT + Q_DISABLE_COPY(LocalServerImpl) + +public: + explicit LocalServerImpl(QObject *parent); + ~LocalServerImpl() override; + + bool hasPendingConnections() const override; + QtROServerIoDevice *configureNewConnection() override; + QUrl address() const override; + bool listen(const QUrl &address) override; + QAbstractSocket::SocketError serverError() const override; + void close() override; + +protected: + QLocalServer m_server; +}; + +#ifdef Q_OS_LINUX + +class AbstractLocalServerImpl final : public LocalServerImpl +{ + Q_OBJECT + +public: + explicit AbstractLocalServerImpl(QObject *parent); + QUrl address() const override; +}; + +#endif // Q_OS_LINUX + +QT_END_NAMESPACE + +#endif diff --git a/src/remoteobjects/qconnection_qnx_backend.cpp b/src/remoteobjects/qconnection_qnx_backend.cpp new file mode 100644 index 0000000..4249ab0 --- /dev/null +++ b/src/remoteobjects/qconnection_qnx_backend.cpp @@ -0,0 +1,156 @@ +// Copyright (C) 2017-2016 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qconnection_qnx_backend_p.h" + +QT_BEGIN_NAMESPACE + +QnxClientIo::QnxClientIo(QObject *parent) + : QtROClientIoDevice(parent) + , m_socket(new QQnxNativeIo(this)) +{ + connect(m_socket, &QQnxNativeIo::readyRead, this, &QtROClientIoDevice::readyRead); + connect(m_socket, + static_cast(&QQnxNativeIo::error), + this, + &QnxClientIo::onError); + connect(m_socket, &QQnxNativeIo::stateChanged, this, &QnxClientIo::onStateChanged); +} + +QnxClientIo::~QnxClientIo() +{ + close(); +} + +QIODevice *QnxClientIo::connection() const +{ + return m_socket; +} + +void QnxClientIo::doClose() +{ + if (m_socket->isOpen()) { + connect(m_socket, &QQnxNativeIo::disconnected, this, &QObject::deleteLater); + m_socket->disconnectFromServer(); + } else { + deleteLater(); + } +} + +void QnxClientIo::doDisconnectFromServer() +{ + m_socket->disconnectFromServer(); +} + +void QnxClientIo::connectToServer() +{ + if (!isOpen()) + m_socket->connectToServer(url().path()); +} + +bool QnxClientIo::isOpen() const +{ + return !isClosing() && (m_socket->state() == QAbstractSocket::ConnectedState + || m_socket->state() == QAbstractSocket::ConnectingState); +} + +void QnxClientIo::onError(QAbstractSocket::SocketError error) +{ + qCDebug(QT_REMOTEOBJECT) << "onError" << error << m_socket->serverName(); + + switch (error) { + case QAbstractSocket::RemoteHostClosedError: + m_socket->close(); + qCWarning(QT_REMOTEOBJECT) << "RemoteHostClosedError"; + Q_FALLTHROUGH(); + case QAbstractSocket::HostNotFoundError: //Host not there, wait and try again + case QAbstractSocket::AddressInUseError: + case QAbstractSocket::ConnectionRefusedError: + //... TODO error reporting + emit shouldReconnect(this); + break; + default: + break; + } +} + +void QnxClientIo::onStateChanged(QAbstractSocket::SocketState state) +{ + if (state == QAbstractSocket::ClosingState && !isClosing()) { + m_socket->abort(); + emit shouldReconnect(this); + } else if (state == QAbstractSocket::ConnectedState) + initializeDataStream(); +} + +QnxServerIo::QnxServerIo(QSharedPointer conn, QObject *parent) + : QtROServerIoDevice(parent), m_connection(conn) +{ + connect(conn.data(), &QIODevice::readyRead, this, &QtROServerIoDevice::readyRead); + connect(conn.data(), &QIOQnxSource::disconnected, this, &QtROServerIoDevice::disconnected); +} + +QIODevice *QnxServerIo::connection() const +{ + return m_connection.data(); +} + +void QnxServerIo::doClose() +{ + m_connection->close(); +} + +QnxServerImpl::QnxServerImpl(QObject *parent) + : QConnectionAbstractServer(parent) +{ + connect(&m_server, + &QQnxNativeServer::newConnection, + this, + &QConnectionAbstractServer::newConnection); +} + +QnxServerImpl::~QnxServerImpl() +{ + m_server.close(); +} + +QtROServerIoDevice *QnxServerImpl::configureNewConnection() +{ + if (!m_server.isListening()) + return nullptr; + + return new QnxServerIo(m_server.nextPendingConnection(), this); +} + +bool QnxServerImpl::hasPendingConnections() const +{ + return m_server.hasPendingConnections(); +} + +QUrl QnxServerImpl::address() const +{ + QUrl result; + result.setPath(m_server.serverName()); + result.setScheme(QStringLiteral("qnx")); + + return result; +} + +bool QnxServerImpl::listen(const QUrl &address) +{ + return m_server.listen(address.path()); +} + +QAbstractSocket::SocketError QnxServerImpl::serverError() const +{ + //TODO implement on QQnxNativeServer and here + //return m_server.serverError(); + return QAbstractSocket::AddressInUseError; +} + +void QnxServerImpl::close() +{ + close(); +} + +QT_END_NAMESPACE diff --git a/src/remoteobjects/qconnection_qnx_backend_p.h b/src/remoteobjects/qconnection_qnx_backend_p.h new file mode 100644 index 0000000..024da5c --- /dev/null +++ b/src/remoteobjects/qconnection_qnx_backend_p.h @@ -0,0 +1,119 @@ +// Copyright (C) 2017-2016 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QCONNECTIONQNXBACKEND_P_H +#define QCONNECTIONQNXBACKEND_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qconnectionfactories_p.h" +#include "qconnection_qnx_qiodevices.h" +#include "qconnection_qnx_server.h" + +QT_BEGIN_NAMESPACE + +/*! + QtRO provides QtROClientIoDevice, QtROServerIoDevice and QConnectionAbstractServer + as abstract interfaces to allow different backends to be used by QtRO. The + concept behind these classes is that there needs to be a Host node, which + has an address that can be connected to. Then there is a client object, + which can be publicly constructed, and can connect to the server. When the + server gets a connection request, it creates the server side of the + connection, which communicates directly with the client. There are thus + three abstractions, one for the server, one for the client-side of the + connection, and the third for the server-side of the connection. The later + two need to inherit from QIODevice. + + Creating a backend for something that is already implemented in Qt is a + matter of creating the three needed abstractions. In the case of creating a + QNX backend using QNX's Native Messaging, the backend needs to create the + Server (which has an address for accepting connections), the client + QIODevice, and the server side QIODevice. Since Native Messaging is one + way, and recommends using pulses to support two-way communication, the + logic for the client-side and server-side QIODevice are very different. + Thus, three additional backend classes are needed as well. + + QnxClientIo implements the QtRO QtROClientIoDevice wrapper around the QNX + specific QQnxNativeIo QIODevice (the client-side QIODevice). + + QnxServerIo implements the QtRO QtROServerIoDevice wrapper around the QNX + specific QIOQnxSource QIODevice (the server-side QIODevice). + + QnxServerImpl implements the QtRO QConnectionAbstractServer wrapper around + the QNX specific QQnxNativeServer, which is the server object listening for + connections. + + Not sure if it is of interest to the Qt community, but it seems like + QQnxNativeIo, QIOQnxSource and QQnxNativeServer could used as an optimized + QLocalServer/QLocalSocket QPA for QNX. +*/ + +class QnxClientIo final : public QtROClientIoDevice +{ + Q_OBJECT + +public: + explicit QnxClientIo(QObject *parent = nullptr); + ~QnxClientIo() override; + + QIODevice *connection() const override; + void connectToServer() override; + bool isOpen() const override; + +public Q_SLOTS: + void onError(QAbstractSocket::SocketError error); + void onStateChanged(QAbstractSocket::SocketState state); + +protected: + void doClose() override; + void doDisconnectFromServer() override; +private: + QQnxNativeIo *m_socket; +}; + +class QnxServerIo final : public QtROServerIoDevice +{ +public: + explicit QnxServerIo(QSharedPointer conn, QObject *parent = nullptr); + + QIODevice *connection() const override; +protected: + void doClose() override; + +private: + //TODO Source or Replica + QSharedPointer m_connection; +}; + +class QnxServerImpl final : public QConnectionAbstractServer +{ + Q_OBJECT + +public: + explicit QnxServerImpl(QObject *parent); + ~QnxServerImpl() override; + + bool hasPendingConnections() const override; + QtROServerIoDevice *configureNewConnection() override; + QUrl address() const override; + bool listen(const QUrl &address) override; + QAbstractSocket::SocketError serverError() const override; + void close() override; + +private: + QQnxNativeServer m_server; +}; + +QT_END_NAMESPACE + +#endif // QCONNECTIONQNXBACKEND_P_H + diff --git a/src/remoteobjects/qconnection_qnx_global_p.h b/src/remoteobjects/qconnection_qnx_global_p.h new file mode 100644 index 0000000..e2bcb6e --- /dev/null +++ b/src/remoteobjects/qconnection_qnx_global_p.h @@ -0,0 +1,83 @@ +// Copyright (C) 2017-2016 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QNXIPCPRIVATE_GLOBAL_H +#define QNXIPCPRIVATE_GLOBAL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include // provides SETIOV +#include //FOR ND_LOCAL_NODE +#include +#include +#include +#ifdef USE_HAM +# include +#endif + +#define WARNING(cmd) qCWarning(QT_REMOTEOBJECT) << "Warning " #cmd << strerror(errno) \ + << Q_FUNC_INFO << __FILE__ << __LINE__; + +#define WARN_ON_ERROR(cmd, ...) if (cmd(__VA_ARGS__) == -1) qCWarning(QT_REMOTEOBJECT) \ + << "Error " #cmd << strerror(errno) << Q_FUNC_INFO << __FILE__ << __LINE__; + +#define WARN_AND_RETURN_ON_ERROR(cmd, retval, ...) if (cmd(__VA_ARGS__) == -1) \ + { qCWarning(QT_REMOTEOBJECT) << "Error " #cmd << strerror(errno) \ + << Q_FUNC_INFO << __FILE__ << __LINE__; return (retval); } + +#define FATAL_ON_ERROR(cmd, ...) if (cmd(__VA_ARGS__) == -1) qFatal("Error %s: %s %s %s %d", \ + #cmd, strerror(errno), Q_FUNC_INFO, __FILE__, __LINE__); + +QT_BEGIN_NAMESPACE + +const int MAX_RETRIES = 3; + +enum MsgType : uint16_t { REPLICA_INIT = _IO_MAX+100, + REPLICA_TX_RECV, + SOURCE_TX_RESP, + SOURCE_TX_RESP_REPEAT, + }; +enum PulseType : uint8_t { SOURCE_TX_RQ = _PULSE_CODE_MINAVAIL+42, + REPLICA_WRITE, + TERMINATE, + NODE_DEATH + }; +union recv_msgs +{ + struct _pulse pulse; + uint16_t type; +}; + +template +class Thread : public QThread +{ +public: + Thread(T *obj, const QString &name = QString()) : QThread(), m_obj(obj) + { + if (!name.isEmpty()) + setObjectName(name); + } + void run() override + { + m_obj->thread_func(); + } +private: + T *m_obj; +}; + +QT_END_NAMESPACE + +#endif // QNXIPCPRIVATE_GLOBAL_H + diff --git a/src/remoteobjects/qconnection_qnx_qiodevices.cpp b/src/remoteobjects/qconnection_qnx_qiodevices.cpp new file mode 100644 index 0000000..a411deb --- /dev/null +++ b/src/remoteobjects/qconnection_qnx_qiodevices.cpp @@ -0,0 +1,619 @@ +// Copyright (C) 2017-2016 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qconnection_qnx_global_p.h" +#include "qconnection_qnx_qiodevices.h" +#include "qconnection_qnx_server.h" +#include "qconnection_qnx_qiodevices_p.h" + +QT_BEGIN_NAMESPACE + +QQnxNativeIoPrivate::QQnxNativeIoPrivate() + : QIODevicePrivate() + , serverId(-1) + , channelId(-1) + , connectionId(-1) + , state(QAbstractSocket::UnconnectedState) + , obuffer(new QRingBuffer) + , thread(this, QStringLiteral("NativeIo")) +{ + //This lets us set msgType before any MsgSend + //and have the value sent as the header/type. + SETIOV(tx_iov + 0, &msgType, sizeof(msgType)); + SIGEV_NONE_INIT(&tx_pulse); +} + +QQnxNativeIoPrivate::~QQnxNativeIoPrivate() +{ + teardownConnection(); +} + +bool QQnxNativeIoPrivate::establishConnection() +{ + //On the client side, we need to create the channel/connection + //to listen for the server's send pulse. + if (channelId == -1) { + const int channel = ChannelCreate(0); + if (channel == -1) { + WARNING(ChannelCreate) + return false; + } + channelId = channel; + } + + const int connection = ConnectAttach(ND_LOCAL_NODE, 0, channelId, _NTO_SIDE_CHANNEL, 0); + if (connection == -1) { + WARNING(ConnectAttach) + teardownConnection(); + return false; + } + connectionId = connection; + + SIGEV_PULSE_INIT(&tx_pulse, connection, SIGEV_PULSE_PRIO_INHERIT, SOURCE_TX_RQ, 0); + SIGEV_MAKE_UPDATEABLE(&tx_pulse); + qCDebug(QT_REMOTEOBJECT) << "in establish" << tx_pulse.sigev_code << SOURCE_TX_RQ; + + if (MsgRegisterEvent(&tx_pulse, connectionId) == -1) { + qCWarning(QT_REMOTEOBJECT) << "Unable to register event for server" << serverName; + teardownConnection(); + return false; + } + + const int id = name_open(qPrintable(serverName), 0); + if (id == -1) { + qCWarning(QT_REMOTEOBJECT) << "Unable to connect to server" << serverName; + teardownConnection(); + return false; + } + serverId = id; + + thread.start(); + + Q_Q(QQnxNativeIo); + qCDebug(QT_REMOTEOBJECT) << "Before INIT: ServerId" << id << "name" << serverName << q; + SETIOV(tx_iov + 1, &tx_pulse, sizeof(tx_pulse)); + SETIOV(tx_iov + 2, &channelId, sizeof(channelId)); + + //Send our registration message to the server + msgType = MsgType::REPLICA_INIT; + //We send tx_pulse along. When the server want to + //transmit data, it sends the pulse back to us. + //When we see that (in receive_thread) we send a + //message and get the server's tx data in the reply. + // + //We transmit the channelId as well - it is only used + //if HAM is enabled, but this will prevent a possible + //mismatch between code compiled with vs. without HAM + //(which could happen if we ever use QCONN between + //devices) + if (MsgSendv(serverId, tx_iov, 3, nullptr, 0) == -1) { + WARNING(MsgSendv) + teardownConnection(); + return false; + } + + state = QAbstractSocket::ConnectedState; + emit q->stateChanged(state); + + return true; +} + +void QQnxNativeIoPrivate::teardownConnection() +{ + Q_Q(QQnxNativeIo); + state = QAbstractSocket::ClosingState; + emit q->stateChanged(state); + + stopThread(); + + state = QAbstractSocket::UnconnectedState; + emit q->stateChanged(state); +} + +void QQnxNativeIoPrivate::stopThread() +{ + if (thread.isRunning()) { + for (int count = 0; count < MAX_RETRIES; ++count) { + if (MsgSendPulse(connectionId, -1, TERMINATE, 0) == -1) { + if (errno == EAGAIN) { + usleep(5000 + (rand() % 10) * 10000); //5 to 95 msec + qCWarning(QT_REMOTEOBJECT) << "Retrying terminate pulse"; + continue; + } + qFatal("MsgSendPulse failed on terminate pulse. Error = %s", strerror(errno)); + } + thread.wait(); + return; + } + if (errno == EAGAIN) + qFatal("MsgSendPulse failed on terminate pulse (max retries)"); + } +} + +// method (run in a thread) to watch for connections and handle receiving data +void QQnxNativeIoPrivate::thread_func() +{ + Q_Q(QQnxNativeIo); + + _pulse pulse; + + qCDebug(QT_REMOTEOBJECT) << "Client thread_func running"; + + bool running = true; + int nTxRequestToIgnore = 0; + while (running) { + int rcvid = MsgReceivePulse(channelId, &pulse, sizeof(pulse), nullptr); + if (rcvid == -1) + continue; + + qCDebug(QT_REMOTEOBJECT) << "MsgReceivePulse unblocked, code =" << pulse.code; + + switch (pulse.code) { + case SOURCE_TX_RQ: //The Source object wants to send us data + { + const int len = pulse.value.sival_int; + qCDebug(QT_REMOTEOBJECT, "TX request with len = %d, tx ignore = %d", len, nTxRequestToIgnore); + if (nTxRequestToIgnore) { + --nTxRequestToIgnore; + break; + } + + int bytesLeft; + + msgType = MsgType::SOURCE_TX_RESP; + ibLock.lockForWrite(); + iov_t reply_vector[2]; + SETIOV(reply_vector, &bytesLeft, sizeof(bytesLeft)); + SETIOV(reply_vector+1, buffer.reserve(len), size_t(len)); + const int res = MsgSendv(serverId, tx_iov, 1, reply_vector, 2); + + if (res == -1) { + buffer.chop(len); + ibLock.unlock(); + WARNING(MsgSendv); + break; + } + ibLock.unlock(); + + qCDebug(QT_REMOTEOBJECT) << "Reply said bytesLeft =" << bytesLeft; + + if (bytesLeft) { + msgType = MsgType::SOURCE_TX_RESP_REPEAT; + ibLock.lockForWrite(); + SETIOV(reply_vector, &nTxRequestToIgnore, sizeof(nTxRequestToIgnore)); + SETIOV(reply_vector+1, buffer.reserve(bytesLeft), size_t(bytesLeft)); + const int res = MsgSendv(serverId, tx_iov, 1, reply_vector, 2); + if (res == -1) { + buffer.chop(bytesLeft); + ibLock.unlock(); + WARNING(MsgSendv); + break; + } + ibLock.unlock(); + qCDebug(QT_REMOTEOBJECT) << "Reply2 said nTxRequestToIgnore =" << nTxRequestToIgnore; + } + + QMetaObject::invokeMethod(q, "readyRead", Qt::QueuedConnection); + } + break; + case REPLICA_WRITE: //Our node has data to send + { + const int len = pulse.value.sival_int; + obLock.lockForWrite(); //NAR (Not-An-Error) + const QByteArray payload = obuffer->read(); + obLock.unlock(); + Q_ASSERT(len == payload.length()); + + msgType = MsgType::REPLICA_TX_RECV; + SETIOV(tx_iov + 1, payload.constData(), size_t(len)); + if (MsgSendvs(serverId, tx_iov, 2, nullptr, 0) == -1) { + WARNING(MsgSendvs); + obLock.lockForWrite(); + if (obuffer->isEmpty()) { + obuffer->append(payload); + } else { + //Since QRingBuffer just holds a QList of + //QByteArray, copying the QByteArrays to + //another container is cheap. + QRingBuffer *newBuffer = new QRingBuffer; + newBuffer->append(payload); + while (!obuffer->isEmpty()) + newBuffer->append(obuffer->read()); + obuffer.reset(newBuffer); + } + obLock.unlock(); + WARNING(MsgSendvs); + } + } + break; + case TERMINATE: + running = false; + continue; + case NODE_DEATH: + qCWarning(QT_REMOTEOBJECT) << "Host node died"; + running = false; + emit q->error(QAbstractSocket::RemoteHostClosedError); + continue; + default: + /* some other unexpected message */ + qCWarning(QT_REMOTEOBJECT) << "unexpected pulse type:" << pulse.type << __FILE__ << __LINE__; + WARN_ON_ERROR(MsgError, rcvid, ENOSYS) + break; + } + } + + if (serverId >= 0) { + WARN_ON_ERROR(name_close, serverId) + serverId = -1; + } + + if (tx_pulse.sigev_notify & SIGEV_FLAG_HANDLE) { + WARN_ON_ERROR(MsgUnregisterEvent, &tx_pulse); + SIGEV_NONE_INIT(&tx_pulse); + } + + if (connectionId >= 0) { + WARN_ON_ERROR(ConnectDetach, connectionId) + connectionId = -1; + } + + if (channelId >= 0) { + WARN_ON_ERROR(ChannelDestroy, channelId) + channelId = -1; + } + + qCDebug(QT_REMOTEOBJECT) << "Client thread_func stopped"; +} + +QQnxNativeIo::QQnxNativeIo(QObject *parent) + : QIODevice(*new QQnxNativeIoPrivate, parent) +{ +} + +QQnxNativeIo::~QQnxNativeIo() +{ + close(); +} + +bool QQnxNativeIo::connectToServer(QIODevice::OpenMode openMode) +{ + Q_D(QQnxNativeIo); + Q_UNUSED(openMode) + + if (state() == QAbstractSocket::ConnectedState || + state() == QAbstractSocket::ConnectingState) { + setErrorString(QStringLiteral("Already connected")); + emit error(QAbstractSocket::OperationError); + return false; + } + + const int omMask = QIODevice::Append | QIODevice::Truncate | QIODevice::Text; + if (openMode & omMask) + qCWarning(QT_REMOTEOBJECT, "Tried to open using unsupported open mode flags."); + + d->errorString.clear(); + d->state = QAbstractSocket::ConnectingState; + emit stateChanged(d->state); + + if (d->serverName.isEmpty()) { + setErrorString(QStringLiteral("serverName not set")); + emit error(QAbstractSocket::HostNotFoundError); + return false; + } + + QIODevice::open(openMode & (~omMask)); + + if (!d->establishConnection()) { + QIODevice::close(); + qCWarning(QT_REMOTEOBJECT, "Failed to connect to server"); + emit error(QAbstractSocket::UnknownSocketError); + return false; + } + + emit stateChanged(d->state); + + return true; +} + +bool QQnxNativeIo::connectToServer(const QString &name, QIODevice::OpenMode openMode) +{ + setServerName(name); + return connectToServer(openMode); +} + +void QQnxNativeIo::disconnectFromServer() +{ + close(); +} + +void QQnxNativeIo::setServerName(const QString &name) +{ + Q_D(QQnxNativeIo); + + if (d->state != QAbstractSocket::UnconnectedState) { + qCWarning(QT_REMOTEOBJECT) << "QQnxNativeIo::setServerName() called while not unconnected"; + return; + } + + d->serverName = name; +} + +QString QQnxNativeIo::serverName() const +{ + Q_D(const QQnxNativeIo); + return d->serverName; +} + +void QQnxNativeIo::abort() +{ + Q_D(QQnxNativeIo); + + d->stopThread(); + //Don't need mutex since thread is stopped + d->obuffer->clear(); + d->buffer.clear(); + + d->state = QAbstractSocket::UnconnectedState; + emit stateChanged(d->state); + QIODevice::close(); +} + +bool QQnxNativeIo::isSequential() const +{ + return true; +} + +qint64 QQnxNativeIo::bytesAvailable() const +{ + Q_D(const QQnxNativeIo); + + d->ibLock.lockForRead(); + qint64 size = d->buffer.size(); + d->ibLock.unlock(); + + return size; +} + +qint64 QQnxNativeIo::bytesToWrite() const +{ + Q_D(const QQnxNativeIo); + + d->obLock.lockForRead(); + qint64 size = d->obuffer->size(); + d->obLock.unlock(); + + return size; +} + +bool QQnxNativeIo::open(QIODevice::OpenMode openMode) +{ + const int omMask = QIODevice::Append | QIODevice::Truncate | QIODevice::Text; + if (openMode & omMask) + qCWarning(QT_REMOTEOBJECT, "Tried to open using unsupported open mode flags."); + + return connectToServer(openMode & (~omMask)); +} + +void QQnxNativeIo::close() +{ + Q_D(QQnxNativeIo); + + if (!isOpen()) + return; + + d->teardownConnection(); + + d->obuffer->clear(); + d->buffer.clear(); + QIODevice::close(); +} + +QAbstractSocket::SocketState QQnxNativeIo::state() const +{ + Q_D(const QQnxNativeIo); + return d->state; +} + +bool QQnxNativeIo::waitForBytesWritten(int msecs) +{ + //TODO - This method isn't used by Qt Remote Objects, but would + //need to be implemented before this class could be used as a + //generic QIODevice. + Q_UNUSED(msecs) + Q_ASSERT(false); + return false; +} + +bool QQnxNativeIo::waitForReadyRead(int msecs) +{ + //TODO - This method isn't used by Qt Remote Objects, but would + //need to be implemented before this class could be used as a + //generic QIODevice. + Q_UNUSED(msecs) + Q_ASSERT(false); + return false; +} + +qint64 QQnxNativeIo::readData(char *data, qint64 size) +{ + Q_D(QQnxNativeIo); + qint64 read; + + if (!isReadable()) + return 0; + + d->ibLock.lockForWrite(); //NAR (Not-An-Error) + read = d->buffer.read(data, size); + d->ibLock.unlock(); + + return read; +} + +qint64 QQnxNativeIo::writeData(const char *data, qint64 size) +{ + Q_D(QQnxNativeIo); + + if (!isWritable()) + return 0; + + if (size < 0 || size > INT_MAX) { + qCWarning(QT_REMOTEOBJECT) << "Invalid size (" << size << ") passed to QtRO QNX backend writeData()."; + return -1; + } + + int isize = static_cast(size); + + d->obLock.lockForWrite(); + d->obuffer->append(QByteArray(data, isize)); + d->obLock.unlock(); + + WARN_AND_RETURN_ON_ERROR(MsgSendPulse, -1, d->connectionId, -1, PulseType::REPLICA_WRITE, isize) + + return size; +} + +/* QIOQnxSource ***************************************************************/ + +QIOQnxSourcePrivate::QIOQnxSourcePrivate(int _rcvid) + : QIODevicePrivate() + , rcvid(_rcvid) + , state(QAbstractSocket::ConnectedState) +{ +} + +QIOQnxSource::QIOQnxSource(int rcvid, QObject *parent) + : QIODevice(*new QIOQnxSourcePrivate(rcvid), parent) +{ + setOpenMode(QIODevice::ReadWrite); +} + +QIOQnxSource::~QIOQnxSource() +{ + close(); +} + +bool QIOQnxSource::isSequential() const +{ + return true; +} + +qint64 QIOQnxSource::bytesAvailable() const +{ + Q_D(const QIOQnxSource); + + d->ibLock.lockForRead(); + qint64 size = d->buffer.size(); + d->ibLock.unlock(); + + return size; +} + +qint64 QIOQnxSource::bytesToWrite() const +{ + Q_D(const QIOQnxSource); + + d->obLock.lockForRead(); + qint64 size = d->obuffer.size(); + d->obLock.unlock(); + + return size; +} + +bool QIOQnxSource::open(QIODevice::OpenMode openMode) +{ + Q_UNUSED(openMode) + return false; +} + +void QIOQnxSource::onDisconnected() +{ + close(); + emit disconnected(); +} + +void QIOQnxSource::close() +{ + Q_D(QIOQnxSource); + + if (!isOpen()) + return; + + d->state = QAbstractSocket::ClosingState; + emit stateChanged(d->state); + + d->state = QAbstractSocket::UnconnectedState; + emit stateChanged(d->state); + + d->obuffer.clear(); + d->buffer.clear(); + QIODevice::close(); +} + +QAbstractSocket::SocketState QIOQnxSource::state() const +{ + Q_D(const QIOQnxSource); + return d->state; +} + +bool QIOQnxSource::waitForBytesWritten(int msecs) +{ + //TODO - This method isn't used by Qt Remote Objects, but would + //need to be implemented before this class could be used as a + //generic QIODevice. + Q_UNUSED(msecs) + Q_ASSERT(false); + return false; +} + +bool QIOQnxSource::waitForReadyRead(int msecs) +{ + //TODO - This method isn't used by Qt Remote Objects, but would + //need to be implemented before this class could be used as a + //generic QIODevice. + Q_UNUSED(msecs) + Q_ASSERT(false); + return false; +} + +qint64 QIOQnxSource::readData(char *data, qint64 size) +{ + Q_D(QIOQnxSource); + qint64 read; + + if (!isReadable()) + return 0; + + d->ibLock.lockForWrite(); //NAR (Not-An-Error) + read = d->buffer.read(data, size); + d->ibLock.unlock(); + + return read; +} + +qint64 QIOQnxSource::writeData(const char *data, qint64 size) +{ + Q_D(QIOQnxSource); + + if (!isWritable()) + return 0; + + if (size < 0 || size > INT_MAX) { + qCWarning(QT_REMOTEOBJECT) << "Invalid size (" << size << ") passed to QtRO QNX backend writeData()."; + return -1; + } + + int isize = static_cast(size); + + d->obLock.lockForWrite(); + d->obuffer.append(QByteArray(data, isize)); + d->obLock.unlock(); + + if (!d->m_serverClosing.loadRelaxed()) { + d->m_event.sigev_value.sival_int = isize; + WARN_ON_ERROR(MsgDeliverEvent, d->rcvid, &(d->m_event)) + } + + return size; +} + +QT_END_NAMESPACE diff --git a/src/remoteobjects/qconnection_qnx_qiodevices.h b/src/remoteobjects/qconnection_qnx_qiodevices.h new file mode 100644 index 0000000..642e4d1 --- /dev/null +++ b/src/remoteobjects/qconnection_qnx_qiodevices.h @@ -0,0 +1,122 @@ +// Copyright (C) 2017-2016 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQNXNATIVEIO_H +#define QQNXNATIVEIO_H + +#include +#include + +QT_BEGIN_NAMESPACE + +/* + * The implementation of the Source and Replica + * side QIODevice look like they will be fairly + * different, including different APIs. So + * creating a 2nd derived type for the source. + * + * TODO: revisit if these can be combined into a + * single type. + * + * With two types, QQnxNativeIo will need to get + * Source or Replica added. Not sure what intuitive + * names are yet. So for now, QQnxNativeIo is the + * Replica side, QIOQnxSourcePrivate is the Source + * side. Revisit the name as this matures. + * +*/ +class QQnxNativeIoPrivate; +class QIOQnxSourcePrivate; + +class Q_REMOTEOBJECTS_EXPORT QQnxNativeIo : public QIODevice +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQnxNativeIo) + +public: + explicit QQnxNativeIo(QObject *parent = nullptr); + ~QQnxNativeIo() override; + + bool connectToServer(OpenMode openMode = ReadWrite); + bool connectToServer(const QString &name, OpenMode openMode = ReadWrite); + void disconnectFromServer(); + + void setServerName(const QString &name); + QString serverName() const; + + void abort(); + bool isSequential() const override; + qint64 bytesAvailable() const override; + qint64 bytesToWrite() const override; + bool open(OpenMode openMode = ReadWrite) override; + void close() override; + QAbstractSocket::SocketError error() const; + bool flush(); + bool isValid() const; + + QAbstractSocket::SocketState state() const; + bool waitForBytesWritten(int msecs = 30000) override; + bool waitForConnected(int msecs = 30000); + bool waitForDisconnected(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000) override; + +Q_SIGNALS: + void connected(); + void disconnected(); + void error(QAbstractSocket::SocketError socketError); + void stateChanged(QAbstractSocket::SocketState socketState); + +protected: + qint64 readData(char*, qint64) override; + qint64 writeData(const char*, qint64) override; + +private: + Q_DISABLE_COPY(QQnxNativeIo) +}; +Q_DECLARE_TYPEINFO(QQnxNativeIo, Q_RELOCATABLE_TYPE); + +class Q_REMOTEOBJECTS_EXPORT QIOQnxSource : public QIODevice +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QIOQnxSource) + +public: + explicit QIOQnxSource(int rcvid, QObject *parent = nullptr); + ~QIOQnxSource() override; + + bool isSequential() const override; + qint64 bytesAvailable() const override; + qint64 bytesToWrite() const override; + void close() override; + QAbstractSocket::SocketError error() const; + bool isValid() const; + + QAbstractSocket::SocketState state() const; + bool waitForBytesWritten(int msecs = 30000) override; + bool waitForConnected(int msecs = 30000); + bool waitForDisconnected(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000) override; + +Q_SIGNALS: + void disconnected(); + void error(QAbstractSocket::SocketError socketError); + void stateChanged(QAbstractSocket::SocketState socketState); + +protected: + qint64 readData(char*, qint64) override; + qint64 writeData(const char*, qint64) override; + bool open(OpenMode openMode) override; + +private Q_SLOTS: + void onDisconnected(); + +private: + Q_DISABLE_COPY(QIOQnxSource) + friend class QQnxNativeServerPrivate; + friend class QnxServerIo; +}; +Q_DECLARE_TYPEINFO(QIOQnxSource, Q_RELOCATABLE_TYPE); + +QT_END_NAMESPACE + +#endif // QQNXNATIVEIO_H diff --git a/src/remoteobjects/qconnection_qnx_qiodevices_p.h b/src/remoteobjects/qconnection_qnx_qiodevices_p.h new file mode 100644 index 0000000..c281b21 --- /dev/null +++ b/src/remoteobjects/qconnection_qnx_qiodevices_p.h @@ -0,0 +1,75 @@ +// Copyright (C) 2017-2016 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQNXNATIVEIO_P_H +#define QQNXNATIVEIO_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qconnection_qnx_qiodevices.h" +#include "qconnection_qnx_global_p.h" + +#include +#include + +#include "private/qiodevice_p.h" +#include "private/qringbuffer_p.h" + +QT_BEGIN_NAMESPACE + +class QQnxNativeIoPrivate : public QIODevicePrivate +{ + Q_DECLARE_PUBLIC(QQnxNativeIo) + + mutable QReadWriteLock ibLock; + mutable QReadWriteLock obLock; + +public: + QQnxNativeIoPrivate(); + ~QQnxNativeIoPrivate(); + void thread_func(); + bool establishConnection(); + void teardownConnection(); + void stopThread(); + QString serverName; + int serverId, channelId, connectionId; + sigevent tx_pulse; + QAbstractSocket::SocketState state; + QScopedPointer obuffer; + MsgType msgType; + iov_t tx_iov[3], rx_iov[2]; + Thread thread; +}; + +class QIOQnxSourcePrivate : public QIODevicePrivate +{ + Q_DECLARE_PUBLIC(QIOQnxSource) + + friend class QQnxNativeServerPrivate; + + mutable QReadWriteLock ibLock; + mutable QReadWriteLock obLock; + +public: + QIOQnxSourcePrivate(int _rcvid); + int rcvid; + QAbstractSocket::SocketState state; + MsgType msgType; + sigevent m_event; + QRingBuffer obuffer; + QAtomicInt m_serverClosing; +}; + +QT_END_NAMESPACE + +#endif // QQNXNATIVEIO_P_H + diff --git a/src/remoteobjects/qconnection_qnx_server.cpp b/src/remoteobjects/qconnection_qnx_server.cpp new file mode 100644 index 0000000..aa7929f --- /dev/null +++ b/src/remoteobjects/qconnection_qnx_server.cpp @@ -0,0 +1,504 @@ +// Copyright (C) 2017-2016 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qconnection_qnx_global_p.h" +#include "qconnection_qnx_qiodevices_p.h" +#include "qconnection_qnx_server_p.h" + +QT_BEGIN_NAMESPACE + +QQnxNativeServer::QQnxNativeServer(QObject *parent) + : QObject(*new QQnxNativeServerPrivate, parent) +{ +} + +QQnxNativeServer::~QQnxNativeServer() +{ +} + +void QQnxNativeServer::close() +{ + Q_D(QQnxNativeServer); + d->teardownServer(); +} + +bool QQnxNativeServer::hasPendingConnections() const +{ + Q_D(const QQnxNativeServer); + d->mutex.lock(); + const int len = d->pending.length(); + d->mutex.unlock(); + return len > 0; +} + +bool QQnxNativeServer::isListening() const +{ + Q_D(const QQnxNativeServer); + return !(d->serverName.isEmpty()); +} + +bool QQnxNativeServer::listen(const QString &name) +{ + Q_D(QQnxNativeServer); + if (isListening()) { + qCWarning(QT_REMOTEOBJECT) << "QQnxNativeServer::listen() called when already listening"; + return false; + } + + if (name.isEmpty()) { + d->error = QAbstractSocket::HostNotFoundError; + QString function = QLatin1String("QQnxNativeServer::listen"); + d->errorString = tr("%1: Name error").arg(function); + return false; + } + + if (!d->listen(name)) { + d->serverName.clear(); + return false; + } + + d->serverName = name; + return true; +} + +QSharedPointer QQnxNativeServer::nextPendingConnection() +{ + Q_D(QQnxNativeServer); + d->mutex.lock(); + Q_ASSERT(d->pending.length() > 0); + auto io = d->pending.takeFirst(); + d->mutex.unlock(); + return io; +} + +QString QQnxNativeServer::serverName() const +{ + Q_D(const QQnxNativeServer); + return d->serverName; +} + +bool QQnxNativeServer::waitForNewConnection(int msec, bool *timedOut) +{ + //TODO - This method isn't used by Qt Remote Objects, but would + //need to be implemented before this class could be used as a + //connection server (like QTcpServer or QLocalServer). + Q_UNUSED(msec) + Q_UNUSED(timedOut) + Q_ASSERT(false); + return false; +} + +void QQnxNativeServer::onSourceClosed() +{ + Q_D(QQnxNativeServer); + QIOQnxSource *conn = qobject_cast(sender()); + Q_ASSERT(conn); + + d->cleanupIOSource(conn); +} + +QQnxNativeServerPrivate::QQnxNativeServerPrivate() + : error(QAbstractSocket::UnknownSocketError) + , thread(this, QStringLiteral("NativeServer")) +{ +} + +QQnxNativeServerPrivate::~QQnxNativeServerPrivate() +{ + if (thread.isRunning()) + teardownServer(); +} + +// method (run in a thread) to watch for connections and handle receiving data +void QQnxNativeServerPrivate::thread_func() +{ + struct _msg_info msg_info; + recv_msgs recv_buf; + QList resp_repeat_iov(5); + + qCDebug(QT_REMOTEOBJECT) << "Server thread_func running"; + + while (running.loadRelaxed()) { + // wait for messages and pulses + int rcvid = MsgReceive(attachStruct->chid, &recv_buf, sizeof(_pulse), &msg_info); + qCDebug(QT_REMOTEOBJECT) << "MsgReceive unblocked. Rcvid" << rcvid << " Scoid" << msg_info.scoid; + if (rcvid == -1) { + if (errno != ESRCH) // ESRCH means the channel closed + WARNING(MsgReceive) + break; + } + + if (0 == rcvid) { + qCDebug(QT_REMOTEOBJECT) << "Pulse" << recv_buf.pulse.code; + /* we received a pulse + */ + switch (recv_buf.pulse.code) { + case _PULSE_CODE_DISCONNECT: + { + /* a client has disconnected. Verify that it is + * our client, and if so, clean up our saved state + */ + const int scoid = recv_buf.pulse.scoid; + const QSet coids = connections.take(scoid); + for (int coid : coids) + { + const uint64_t uid = static_cast(scoid) << 32 | static_cast(coid); + QSharedPointer io; + mutex.lock(); + if (sources.contains(uid)) + io = sources.take(uid); + mutex.unlock(); +#ifdef USE_HAM + ham_action_t *action = hamActions.take(uid); + ham_action_remove(action, 0); + ham_action_handle_free(action); +#endif + + if (!io.isNull()) { + io->d_func()->m_serverClosing.ref(); + QMetaObject::invokeMethod(io.data(),"onDisconnected",Qt::QueuedConnection); + } + } + + /* always do the ConnectDetach() */ + //Don't care about return value + ConnectDetach(recv_buf.pulse.scoid); + qCDebug(QT_REMOTEOBJECT) << "disconnect from client" << recv_buf.pulse.scoid; + } + break; + case _PULSE_CODE_COIDDEATH: + { + /* a connection has gone away. Verify that it is + * our client, and if so, clean up our saved state + */ + const int coid = recv_buf.pulse.value.sival_int; + + if (ConnectServerInfo(0, coid, nullptr) != coid) { + const int scoid = recv_buf.pulse.scoid; + if (connections.value(scoid).contains(coid)) + connections[scoid].remove(coid); + QSharedPointer io; + const uint64_t uid = static_cast(scoid) << 32 | static_cast(coid); + mutex.lock(); + if (sources.contains(uid)) + io = sources.take(uid); + mutex.unlock(); +#ifdef USE_HAM + ham_action_t *action = hamActions.take(uid); + ham_action_remove(action, 0); + ham_action_handle_free(action); +#endif + + if (!io.isNull()) { + io->d_func()->m_serverClosing.ref(); + QMetaObject::invokeMethod(io.data(),"onDisconnected",Qt::QueuedConnection); + } + qCDebug(QT_REMOTEOBJECT) << "Connection dropped" << coid; + } + break; + } + break; + case _PULSE_CODE_UNBLOCK: + if (running.loadRelaxed()) { + // did we forget to Reply to our client? + qCWarning(QT_REMOTEOBJECT) << "got an unblock pulse, did you forget to reply to your client?"; + WARN_ON_ERROR(MsgError, recv_buf.pulse.value.sival_int, EINTR) + } + break; + default: + qCWarning(QT_REMOTEOBJECT) << "unexpected pulse code: " << recv_buf.pulse.code << __FILE__ << __LINE__; + break; + } + continue; + } + + /* not an error, not a pulse, therefore a message */ + switch (recv_buf.type) + { + qCDebug(QT_REMOTEOBJECT) << "Msg Received" << recv_buf.type; + + case _IO_CONNECT: + /* _IO_CONNECT because someone did a name_open() to us and we are + * in the network case (gns running). We must EOK it. */ + if (connections.contains(msg_info.scoid) + && connections.value(msg_info.scoid).contains(msg_info.coid)) + { + qCWarning(QT_REMOTEOBJECT) << "Already connected rcvid seen" << connections << rcvid; + FATAL_ON_ERROR(MsgError, rcvid, EADDRINUSE) + } else { + if (!connections.contains(msg_info.scoid)) + connections.insert(msg_info.scoid, QSet()); + connections[msg_info.scoid] << msg_info.coid; + qCDebug(QT_REMOTEOBJECT) << "New connection (qns)" << msg_info.coid << msg_info.scoid; + const uint64_t uid = static_cast(msg_info.scoid) << 32 | static_cast(msg_info.coid); + createSource(rcvid, uid, msg_info.pid); // Reads more and then calls MsgReply + } + break; + + case MsgType::REPLICA_INIT: + qCDebug(QT_REMOTEOBJECT) << "MsgType::INIT received" << rcvid << msg_info.scoid << msg_info.tid << msg_info.chid << msg_info.coid; + //Check if this is a new connection (not gns) + if (connections.contains(msg_info.scoid) + && connections.value(msg_info.scoid).contains(msg_info.coid)) + { + qCWarning(QT_REMOTEOBJECT) << "Already connected rcvid seen" << connections << rcvid; + FATAL_ON_ERROR(MsgError, rcvid, EADDRINUSE) + } else { + if (!connections.contains(msg_info.scoid)) + connections.insert(msg_info.scoid, QSet()); + connections[msg_info.scoid] << msg_info.coid; + qCDebug(QT_REMOTEOBJECT) << "New connection (non-gns)" << rcvid << msg_info.coid << msg_info.scoid; + const uint64_t uid = static_cast(msg_info.scoid) << 32 | static_cast(msg_info.coid); + createSource(rcvid, uid, msg_info.pid); // Reads more and then calls MsgReply + } + + break; + case MsgType::SOURCE_TX_RESP: + { + WARN_ON_ERROR(MsgInfo, rcvid, &msg_info); + const uint64_t uid = static_cast(msg_info.scoid) << 32 | static_cast(msg_info.coid); + mutex.lock(); + Q_ASSERT(sources.contains(uid)); + auto io = sources.value(uid); + mutex.unlock(); + + io->d_func()->obLock.lockForWrite(); //NAR (Not-An-Error) + const QByteArray data = io->d_func()->obuffer.read(); + const int bytesLeft = io->d_func()->obuffer.size(); + io->d_func()->obLock.unlock(); + + qCDebug(QT_REMOTEOBJECT) << "server received SOURCE_TX_RESP with length" << msg_info.dstmsglen << "/" << data.length() << "Available:" << bytesLeft; + Q_ASSERT(data.length() == static_cast(msg_info.dstmsglen - sizeof(bytesLeft))); + + iov_t reply_vector[2]; + SETIOV(reply_vector, &bytesLeft, sizeof(bytesLeft)); + SETIOV(reply_vector+1, data.data(), size_t(data.length())); + + FATAL_ON_ERROR(MsgReplyv, rcvid, EOK, reply_vector, 2) + } + break; + case MsgType::SOURCE_TX_RESP_REPEAT: + { + WARN_ON_ERROR(MsgInfo, rcvid, &msg_info); + const uint64_t uid = static_cast(msg_info.scoid) << 32 | static_cast(msg_info.coid); + mutex.lock(); + Q_ASSERT(sources.contains(uid)); + auto io = sources.value(uid); + mutex.unlock(); + + int len_taken = 0; + const int to_send = msg_info.dstmsglen - sizeof(int); //Exclude the buffer count + QByteArrayList qba_array; + io->d_func()->obLock.lockForWrite(); //NAR (Not-An-Error) + qCDebug(QT_REMOTEOBJECT) << "server received SOURCE_TX_RESP_REPEAT with length" << msg_info.dstmsglen << "Available:" << io->d_func()->obuffer.size(); + while (len_taken != to_send) + { + QByteArray data = io->d_func()->obuffer.read(); + qba_array << data; + len_taken += data.length(); + if (data.length() == 0) { // We somehow reached the end + qCWarning(QT_REMOTEOBJECT) << "Reached the end of the buffer before getting the requested amount of data." << Q_FUNC_INFO << __FILE__ << __LINE__; + break; + } + } + qCDebug(QT_REMOTEOBJECT) << "grabbing more data" << len_taken << to_send << qba_array.length(); + io->d_func()->obLock.unlock(); + + Q_ASSERT(len_taken == to_send); + const int buffers_taken = qba_array.length(); + + resp_repeat_iov.resize(buffers_taken+1); + SETIOV(&resp_repeat_iov[0], &buffers_taken, sizeof(buffers_taken)); + for (int i = 1; i <= buffers_taken; ++i) + SETIOV(&resp_repeat_iov[i], qba_array.at(i-1).constData(), size_t(qba_array.at(i-1).length())); + FATAL_ON_ERROR(MsgReplyv, rcvid, EOK, resp_repeat_iov.data(), buffers_taken+1) + } + break; + case MsgType::REPLICA_TX_RECV: + { + FATAL_ON_ERROR(MsgInfo,rcvid, &msg_info) + const uint64_t uid = static_cast(msg_info.scoid) << 32 | static_cast(msg_info.coid); + mutex.lock(); + Q_ASSERT(sources.contains(uid)); + auto io = sources.value(uid); + mutex.unlock(); + + //Long-lock, use buffer+memcpy if we run into trouble + io->d_func()->ibLock.lockForWrite(); + qint32 toRead = msg_info.msglen-sizeof(MsgType); + char *payload = io->d_func()->buffer.reserve(toRead); + + const int res = MsgRead(rcvid, payload, toRead, sizeof(MsgType)); + if (-1 == res) { + io->d_func()->buffer.chop(toRead); + io->d_func()->ibLock.unlock(); + qFatal("MsgRead"); + } + Q_ASSERT(res == toRead); + + if (res < toRead) + io->d_func()->buffer.chop(toRead - res); + io->d_func()->ibLock.unlock(); + + FATAL_ON_ERROR(MsgReply, rcvid, EOK, nullptr, 0) + + qCDebug(QT_REMOTEOBJECT) << "server received REPLICA_TX_RECV" << payload << toRead; + + emit io->readyRead(); + } + break; + default: + /* some other unexpected message */ + qCWarning(QT_REMOTEOBJECT) << "unexpected message type" << recv_buf.type << __FILE__ << __LINE__; + WARN_ON_ERROR(MsgError, rcvid, ENOSYS) + break; + } + } + mutex.lock(); + for (auto io: sources) + io->d_func()->m_serverClosing.ref(); + mutex.unlock(); + name_detach(attachStruct, 0); + attachStruct = nullptr; + qCDebug(QT_REMOTEOBJECT) << "Server thread_func stopped"; +} + +bool QQnxNativeServerPrivate::listen(const QString &name) +{ + attachStruct = name_attach(nullptr, qPrintable(name), 0); + if (attachStruct == nullptr) { + qCDebug(QT_REMOTEOBJECT, "name_attach call failed"); + return false; + } + terminateCoid = ConnectAttach(ND_LOCAL_NODE, 0, attachStruct->chid, _NTO_SIDE_CHANNEL, 0); + if (terminateCoid == -1) { + qCDebug(QT_REMOTEOBJECT, "ConnectAttach failed"); + return false; + } + + running.ref(); + thread.start(); + + return true; +} + +void QQnxNativeServerPrivate::cleanupIOSource(QIOQnxSource *conn) +{ + QSharedPointer io; + mutex.lock(); + for (auto i = sources.begin(), end = sources.end(); i != end; ++i) { + if (i.value().data() == conn) { + io = std::move(i.value()); + sources.erase(i); + break; + } + } + mutex.unlock(); + if (!io.isNull()) { + io->d_func()->m_serverClosing.ref(); + io->close(); + } +} + +void QQnxNativeServerPrivate::teardownServer() +{ + if (attachStruct == nullptr) + return; + + running.deref(); + MsgSendPulse(terminateCoid, SIGEV_PULSE_PRIO_INHERIT, _PULSE_CODE_UNBLOCK, 0); + ConnectDetach(terminateCoid); + thread.wait(); + + //Existing QIOQnxSources will be deleted along with object + //threads gone, don't need to use mutex + sources.clear(); +#ifdef USE_HAM + if (hamAvailable) + closeHamResources(); +#endif +} + +void QQnxNativeServerPrivate::createSource(int rcvid, uint64_t uid, pid_t toPid) +{ + Q_Q(QQnxNativeServer); +#ifndef USE_HAM + Q_UNUSED(toPid) +#endif + auto io = QSharedPointer(new QIOQnxSource(rcvid)); + io->moveToThread(q->thread()); + QObject::connect(io.data(), &QIOQnxSource::aboutToClose, + q, &QQnxNativeServer::onSourceClosed); + + QIOQnxSourcePrivate *iop = io->d_func(); + FATAL_ON_ERROR(MsgRead, rcvid, &(iop->m_event), sizeof(sigevent), sizeof(MsgType)) + int sentChannelId; + FATAL_ON_ERROR(MsgRead, rcvid, &sentChannelId, sizeof(int), sizeof(MsgType)+sizeof(sigevent)) + FATAL_ON_ERROR(MsgReply, rcvid, EOK, nullptr, 0) + + mutex.lock(); + sources.insert(uid, io); + pending.append(io); + mutex.unlock(); + + //push an event into the main threads eventloop to emit newConnection. + QMetaObject::invokeMethod(q,"newConnection",Qt::QueuedConnection); +#ifdef USE_HAM + if (!hamInitialized) { + hamInitialized = true; + hamAvailable = initializeHam(); + } + if (hamAvailable) + configureHamDeath(sentChannelId, toPid, uid); +#endif +} + +#ifdef USE_HAM +bool QQnxNativeServerPrivate::initializeHam() +{ + if (access("/proc/ham",F_OK) != 0) { + WARNING(access(/proc/ham)) + return false; + } + ham_connect(0); + pid_t pid = getpid(); + hamEntityHandle = ham_attach(qPrintable(serverName), ND_LOCAL_NODE, pid, NULL, 0); + if (!hamEntityHandle) { + WARNING(ham_attach) + ham_disconnect(0); + return false; + } + hamConditionHandle = ham_condition(hamEntityHandle, CONDDEATH, "death", 0); + if (!hamConditionHandle) { + WARNING(ham_condition) + ham_detach(hamEntityHandle, 0); + ham_entity_handle_free(hamEntityHandle); + ham_disconnect(0); + return false; + } + qCDebug(QT_REMOTEOBJECT, "HAM initialized for %s (pid = %d)", qPrintable(serverName), pid); + return true; +} + +void QQnxNativeServerPrivate::configureHamDeath(int sentChannelId, pid_t toPid, uint64_t uid) +{ + char identifier[25]; + snprintf(identifier, 25, "%d_%d", toPid, sentChannelId); + ham_action_t *a = ham_action_notify_pulse(hamConditionHandle, identifier, + ND_LOCAL_NODE, toPid, + sentChannelId, NODE_DEATH, 0, 0); + if (!a) + WARNING(ham_action_notify_pulse) + else + hamActions.insert(uid, a); +} + +void QQnxNativeServerPrivate::closeHamResources() +{ + ham_entity_handle_free(hamEntityHandle); + ham_condition_handle_free(hamConditionHandle); + ham_disconnect(0); +} +#endif + +QT_END_NAMESPACE diff --git a/src/remoteobjects/qconnection_qnx_server.h b/src/remoteobjects/qconnection_qnx_server.h new file mode 100644 index 0000000..26c266e --- /dev/null +++ b/src/remoteobjects/qconnection_qnx_server.h @@ -0,0 +1,46 @@ +// Copyright (C) 2017-2016 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQNXNATIVESERVER_H +#define QQNXNATIVESERVER_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QQnxNativeServerPrivate; +class QQnxNativeIo; +class QIOQnxSource; + +class Q_REMOTEOBJECTS_EXPORT QQnxNativeServer : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQnxNativeServer) + +Q_SIGNALS: + void newConnection(); + +public: + explicit QQnxNativeServer(QObject *parent = nullptr); + ~QQnxNativeServer(); + + void close(); + bool hasPendingConnections() const; + bool isListening() const; + bool listen(const QString &name); + QSharedPointer nextPendingConnection(); + QString serverName() const; + bool waitForNewConnection(int msec = 0, bool *timedOut = nullptr); + +private Q_SLOTS: + void onSourceClosed(); + +private: + Q_DISABLE_COPY(QQnxNativeServer) + friend class QIOQnxSource; +}; + +QT_END_NAMESPACE + +#endif // QQNXNATIVESERVER_H diff --git a/src/remoteobjects/qconnection_qnx_server_p.h b/src/remoteobjects/qconnection_qnx_server_p.h new file mode 100644 index 0000000..3a479e0 --- /dev/null +++ b/src/remoteobjects/qconnection_qnx_server_p.h @@ -0,0 +1,72 @@ +// Copyright (C) 2017-2016 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQNXNATIVESERVER_P_H +#define QQNXNATIVESERVER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qobject_p.h" +#include "qconnection_qnx_server.h" +#include "qconnection_qnx_global_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QQnxNativeServerPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQnxNativeServer) + +public: + QQnxNativeServerPrivate(); + + ~QQnxNativeServerPrivate(); + + void thread_func(); + + void cleanupIOSource(QIOQnxSource *conn); + void teardownServer(); + void createSource(int rcvid, uint64_t uid, pid_t toPid); +#ifdef USE_HAM + bool initializeHam(); + void configureHamDeath(int sentChannelId, pid_t toPid, uint64_t uid); + void closeHamResources(); +#endif + + bool listen(const QString &name); + QString errorString; + QAbstractSocket::SocketError error; + QString serverName; + name_attach_t *attachStruct; + QHash > connections; + QHash> sources; + QList> pending; + QAtomicInt running; + Thread thread; + mutable QMutex mutex; + int terminateCoid; +#ifdef USE_HAM + ham_entity_t *hamEntityHandle; + ham_condition_t *hamConditionHandle; + QHash hamActions; + bool hamAvailable = false; + bool hamInitialized = false; +#endif +}; + +QT_END_NAMESPACE + +#endif // QQNXNATIVESERVER_P_H + diff --git a/src/remoteobjects/qconnection_tcpip_backend.cpp b/src/remoteobjects/qconnection_tcpip_backend.cpp new file mode 100644 index 0000000..b8f584d --- /dev/null +++ b/src/remoteobjects/qconnection_tcpip_backend.cpp @@ -0,0 +1,177 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qconnection_tcpip_backend_p.h" + +#include + +QT_BEGIN_NAMESPACE + +TcpClientIo::TcpClientIo(QObject *parent) + : QtROClientIoDevice(parent) + , m_socket(new QTcpSocket(this)) +{ + connect(m_socket, &QTcpSocket::readyRead, this, &QtROClientIoDevice::readyRead); + connect(m_socket, &QAbstractSocket::errorOccurred, this, &TcpClientIo::onError); + connect(m_socket, &QTcpSocket::stateChanged, this, &TcpClientIo::onStateChanged); +} + +TcpClientIo::~TcpClientIo() +{ + close(); +} + +QIODevice *TcpClientIo::connection() const +{ + return m_socket; +} + +void TcpClientIo::doClose() +{ + if (m_socket->isOpen()) { + connect(m_socket, &QTcpSocket::disconnected, this, &QObject::deleteLater); + m_socket->disconnectFromHost(); + } else { + this->deleteLater(); + } +} + +void TcpClientIo::doDisconnectFromServer() +{ + m_socket->disconnectFromHost(); +} + +void TcpClientIo::connectToServer() +{ + if (isOpen()) + return; + QHostAddress address(url().host()); + if (address.isNull()) { + const QList addresses = QHostInfo::fromName(url().host()).addresses(); + Q_ASSERT_X(addresses.size() >= 1, Q_FUNC_INFO, url().toString().toLatin1().data()); + address = addresses.first(); + } + + m_socket->connectToHost(address, quint16(url().port())); +} + +bool TcpClientIo::isOpen() const +{ + return (!isClosing() && (m_socket->state() == QAbstractSocket::ConnectedState + || m_socket->state() == QAbstractSocket::ConnectingState)); +} + +void TcpClientIo::onError(QAbstractSocket::SocketError error) +{ + qCDebug(QT_REMOTEOBJECT) << "onError" << error; + + switch (error) { + case QAbstractSocket::HostNotFoundError: //Host not there, wait and try again + case QAbstractSocket::ConnectionRefusedError: + case QAbstractSocket::NetworkError: + emit shouldReconnect(this); + break; + case QAbstractSocket::AddressInUseError: + //... TODO error reporting + break; + default: + break; + } +} + +void TcpClientIo::onStateChanged(QAbstractSocket::SocketState state) +{ + if (state == QAbstractSocket::ClosingState && !isClosing()) { + m_socket->abort(); + emit shouldReconnect(this); + } + if (state == QAbstractSocket::ConnectedState) + initializeDataStream(); +} + + +TcpServerIo::TcpServerIo(QTcpSocket *conn, QObject *parent) + : QtROServerIoDevice(parent), m_connection(conn) +{ + m_connection->setParent(this); + connect(conn, &QIODevice::readyRead, this, &QtROServerIoDevice::readyRead); + connect(conn, &QAbstractSocket::disconnected, this, &QtROServerIoDevice::disconnected); +} + +QIODevice *TcpServerIo::connection() const +{ + return m_connection; +} + +void TcpServerIo::doClose() +{ + m_connection->disconnectFromHost(); +} + + + +TcpServerImpl::TcpServerImpl(QObject *parent) + : QConnectionAbstractServer(parent) +{ + connect(&m_server, &QTcpServer::newConnection, this, &QConnectionAbstractServer::newConnection); +} + +TcpServerImpl::~TcpServerImpl() +{ + close(); +} + +QtROServerIoDevice *TcpServerImpl::configureNewConnection() +{ + if (!m_server.isListening()) + return nullptr; + + return new TcpServerIo(m_server.nextPendingConnection(), this); +} + +bool TcpServerImpl::hasPendingConnections() const +{ + return m_server.hasPendingConnections(); +} + +QUrl TcpServerImpl::address() const +{ + return m_originalUrl; +} + +bool TcpServerImpl::listen(const QUrl &address) +{ + QHostAddress host(address.host()); + if (host.isNull()) { + if (address.host().isEmpty()) { + host = QHostAddress::Any; + } else { + qCWarning(QT_REMOTEOBJECT) << address.host() << " is not an IP address, trying to resolve it"; + QHostInfo info = QHostInfo::fromName(address.host()); + if (info.addresses().isEmpty()) + host = QHostAddress::Any; + else + host = info.addresses().constFirst(); + } + } + + bool ret = m_server.listen(host, quint16(address.port())); + if (ret) { + m_originalUrl.setScheme(QLatin1String("tcp")); + m_originalUrl.setHost(m_server.serverAddress().toString()); + m_originalUrl.setPort(m_server.serverPort()); + } + return ret; +} + +QAbstractSocket::SocketError TcpServerImpl::serverError() const +{ + return m_server.serverError(); +} + +void TcpServerImpl::close() +{ + m_server.close(); +} + +QT_END_NAMESPACE diff --git a/src/remoteobjects/qconnection_tcpip_backend_p.h b/src/remoteobjects/qconnection_tcpip_backend_p.h new file mode 100644 index 0000000..3c60bad --- /dev/null +++ b/src/remoteobjects/qconnection_tcpip_backend_p.h @@ -0,0 +1,85 @@ +// Copyright (C) 2017-2015 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QCONNECTIONTCPIPBACKEND_P_H +#define QCONNECTIONTCPIPBACKEND_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qconnectionfactories_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class TcpClientIo final : public QtROClientIoDevice +{ + Q_OBJECT + +public: + explicit TcpClientIo(QObject *parent = nullptr); + ~TcpClientIo() override; + + QIODevice *connection() const override; + void connectToServer() override; + bool isOpen() const override; + +public Q_SLOTS: + void onError(QAbstractSocket::SocketError error); + void onStateChanged(QAbstractSocket::SocketState state); + +protected: + void doClose() override; + void doDisconnectFromServer() override; + +private: + QTcpSocket *m_socket; +}; + +class TcpServerIo final : public QtROServerIoDevice +{ + Q_OBJECT +public: + explicit TcpServerIo(QTcpSocket *conn, QObject *parent = nullptr); + + QIODevice *connection() const override; +protected: + void doClose() override; + +private: + QTcpSocket *m_connection; +}; + +class TcpServerImpl final : public QConnectionAbstractServer +{ + Q_OBJECT + Q_DISABLE_COPY(TcpServerImpl) + +public: + explicit TcpServerImpl(QObject *parent); + ~TcpServerImpl() override; + + bool hasPendingConnections() const override; + QtROServerIoDevice *configureNewConnection() override; + QUrl address() const override; + bool listen(const QUrl &address) override; + QAbstractSocket::SocketError serverError() const override; + void close() override; + +private: + QTcpServer m_server; + QUrl m_originalUrl; // necessary because of a QHostAddress bug +}; + +QT_END_NAMESPACE +#endif // QCONNECTIONTCPIPBACKEND_P_H diff --git a/src/remoteobjects/qconnectionfactories.cpp b/src/remoteobjects/qconnectionfactories.cpp new file mode 100644 index 0000000..22a7a83 --- /dev/null +++ b/src/remoteobjects/qconnectionfactories.cpp @@ -0,0 +1,360 @@ +// Copyright (C) 2017-2015 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qconnectionfactories_p.h" +#include "qremoteobjectpacket_p.h" + +// BEGIN: Backends +#if defined(Q_OS_QNX) +#include "qconnection_qnx_backend_p.h" +#endif +#include "qconnection_local_backend_p.h" +#include "qconnection_tcpip_backend_p.h" +// END: Backends + +QT_BEGIN_NAMESPACE + +using namespace QtRemoteObjects; + +class QtROFactoryLoader +{ +public: + QtROClientFactory clientFactory; + QtROServerFactory serverFactory; +}; + +Q_GLOBAL_STATIC(QtROFactoryLoader, loader) + +inline bool fromDataStream(QDataStream &in, QRemoteObjectPacketTypeEnum &type, QString &name) +{ + quint16 _type; + in >> _type; + type = Invalid; + switch (_type) { + case Handshake: type = Handshake; break; + case InitPacket: type = InitPacket; break; + case InitDynamicPacket: type = InitDynamicPacket; break; + case AddObject: type = AddObject; break; + case RemoveObject: type = RemoveObject; break; + case InvokePacket: type = InvokePacket; break; + case InvokeReplyPacket: type = InvokeReplyPacket; break; + case PropertyChangePacket: type = PropertyChangePacket; break; + case ObjectList: type = ObjectList; break; + case Ping: type = Ping; break; + case Pong: type = Pong; break; + default: + qCWarning(QT_REMOTEOBJECT_IO) << "Invalid packet received" << _type; + } + if (type == Invalid) + return false; + if (type == ObjectList) + return true; + in >> name; + qCDebug(QT_REMOTEOBJECT_IO) << "Packet received of type" << type << "for object" << name; + return true; +} + +/*! + All communication between nodes happens through some form of QIODevice with + an associated QDataStream to handle marshalling of Qt types. QtROIoDeviceBase + is an abstract base class that provides a consistent interface to QtRO, yet + can be extended to support different types of QIODevice. + */ +QtROIoDeviceBase::QtROIoDeviceBase(QObject *parent) : QObject(*new QtROIoDeviceBasePrivate, parent) { } + +QtROIoDeviceBase::QtROIoDeviceBase(QtROIoDeviceBasePrivate &dptr, QObject *parent) : QObject(dptr, parent) { } + +QtROIoDeviceBase::~QtROIoDeviceBase() +{ +} + +bool QtROIoDeviceBase::read(QRemoteObjectPacketTypeEnum &type, QString &name) +{ + Q_D(QtROIoDeviceBase); + qCDebug(QT_REMOTEOBJECT_IO) << deviceType() << "read()" << d->m_curReadSize << bytesAvailable(); + + if (d->m_curReadSize == 0) { + if (bytesAvailable() < static_cast(sizeof(quint32))) + return false; + + d->m_dataStream >> d->m_curReadSize; + } + + qCDebug(QT_REMOTEOBJECT_IO) << deviceType() << "read()-looking for map" << d->m_curReadSize + << bytesAvailable(); + + if (bytesAvailable() < d->m_curReadSize) + return false; + + d->m_curReadSize = 0; + return fromDataStream(d->m_dataStream, type, name); +} + +void QtROIoDeviceBase::write(const QByteArray &data) +{ + Q_D(QtROIoDeviceBase); + if (connection()->isOpen() && !d->m_isClosing) + connection()->write(data); +} + +void QtROIoDeviceBase::write(const QByteArray &data, qint64 size) +{ + Q_D(QtROIoDeviceBase); + if (connection()->isOpen() && !d->m_isClosing) + connection()->write(data.data(), size); +} + +bool QtROIoDeviceBase::isOpen() const +{ + return !isClosing(); +} + +void QtROIoDeviceBase::close() +{ + Q_D(QtROIoDeviceBase); + d->m_isClosing = true; + doClose(); +} + +qint64 QtROIoDeviceBase::bytesAvailable() const +{ + return connection()->bytesAvailable(); +} + +void QtROIoDeviceBase::initializeDataStream() +{ + Q_D(QtROIoDeviceBase); + d->m_dataStream.setDevice(connection()); + d->m_dataStream.resetStatus(); +} + +bool QtROIoDeviceBase::isClosing() const +{ + Q_D(const QtROIoDeviceBase); + return d->m_isClosing; +} + +void QtROIoDeviceBase::addSource(const QString &name) +{ + Q_D(QtROIoDeviceBase); + d->m_remoteObjects.insert(name); +} + +void QtROIoDeviceBase::removeSource(const QString &name) +{ + Q_D(QtROIoDeviceBase); + d->m_remoteObjects.remove(name); +} + +QSet QtROIoDeviceBase::remoteObjects() const +{ + Q_D(const QtROIoDeviceBase); + return d->m_remoteObjects; +} + +QtROClientIoDevice::QtROClientIoDevice(QObject *parent) : QtROIoDeviceBase(*new QtROClientIoDevicePrivate, parent) +{ +} + +QtROClientIoDevice::~QtROClientIoDevice() +{ + if (!isClosing()) + close(); +} + +void QtROClientIoDevice::disconnectFromServer() +{ + doDisconnectFromServer(); + emit shouldReconnect(this); +} + +QUrl QtROClientIoDevice::url() const +{ + Q_D(const QtROClientIoDevice); + return d->m_url; +} + +QString QtROClientIoDevice::deviceType() const +{ + return QStringLiteral("QtROClientIoDevice"); +} + +void QtROClientIoDevice::setUrl(const QUrl &url) +{ + Q_D(QtROClientIoDevice); + d->m_url = url; +} + +/*! + The Qt servers create QIODevice derived classes from handleConnection. The + problem is that they behave differently, so this class adds some + consistency. + */ +QtROServerIoDevice::QtROServerIoDevice(QObject *parent) : QtROIoDeviceBase(parent) +{ +} + +QString QtROServerIoDevice::deviceType() const +{ + return QStringLiteral("QtROServerIoDevice"); +} + +QConnectionAbstractServer::QConnectionAbstractServer(QObject *parent) + : QObject(parent) +{ +} + +QConnectionAbstractServer::~QConnectionAbstractServer() +{ +} + +QtROServerIoDevice *QConnectionAbstractServer::nextPendingConnection() +{ + QtROServerIoDevice *iodevice = configureNewConnection(); + iodevice->initializeDataStream(); + return iodevice; +} + +QtROExternalIoDevice::QtROExternalIoDevice(QIODevice *device, QObject *parent) + : QtROIoDeviceBase(*new QtROExternalIoDevicePrivate(device), parent) +{ + Q_D(QtROExternalIoDevice); + initializeDataStream(); + connect(device, &QIODevice::aboutToClose, this, [d]() { d->m_isClosing = true; }); + connect(device, &QIODevice::readyRead, this, &QtROExternalIoDevice::readyRead); + auto meta = device->metaObject(); + if (-1 != meta->indexOfSignal(SIGNAL(disconnected()))) + connect(device, SIGNAL(disconnected()), this, SIGNAL(disconnected())); +} + +QIODevice *QtROExternalIoDevice::connection() const +{ + Q_D(const QtROExternalIoDevice); + return d->m_device; +} + +bool QtROExternalIoDevice::isOpen() const +{ + Q_D(const QtROExternalIoDevice); + if (!d->m_device) + return false; + return d->m_device->isOpen() && QtROIoDeviceBase::isOpen(); +} + +void QtROExternalIoDevice::doClose() +{ + Q_D(QtROExternalIoDevice); + if (isOpen()) + d->m_device->close(); +} + +QString QtROExternalIoDevice::deviceType() const +{ + return QStringLiteral("QtROExternalIoDevice"); +} + +/*! + \class QtROServerFactory + \inmodule QtRemoteObjects + \brief A class that holds information about server backends available on the Qt Remote Objects network. +*/ +QtROServerFactory::QtROServerFactory() +{ +#ifdef Q_OS_QNX + registerType(QStringLiteral("qnx")); +#endif +#ifdef Q_OS_LINUX + registerType(QStringLiteral("localabstract")); +#endif + registerType(QStringLiteral("local")); + registerType(QStringLiteral("tcp")); +} + +QtROServerFactory *QtROServerFactory::instance() +{ + return &loader->serverFactory; +} + +/*! + \class QtROClientFactory + \inmodule QtRemoteObjects + \brief A class that holds information about client backends available on the Qt Remote Objects network. +*/ +QtROClientFactory::QtROClientFactory() +{ +#ifdef Q_OS_QNX + registerType(QStringLiteral("qnx")); +#endif +#ifdef Q_OS_LINUX + registerType(QStringLiteral("localabstract")); +#endif + registerType(QStringLiteral("local")); + registerType(QStringLiteral("tcp")); +} + +QtROClientFactory *QtROClientFactory::instance() +{ + return &loader->clientFactory; +} + +/*! + \fn void qRegisterRemoteObjectsClient(const QString &id) + \relates QtROClientFactory + + Registers the Remote Objects client \a id for the type \c{T}. + + If you need a custom transport protocol for Qt Remote Objects, you need to + register the client & server implementation here. + + \note This function requires that \c{T} is a fully defined type at the point + where the function is called. + + This example registers the class \c{CustomClientIo} as \c{"myprotocol"}: + + \code + qRegisterRemoteObjectsClient(QStringLiteral("myprotocol")); + \endcode + + With this in place, you can now instantiate nodes using this new custom protocol: + + \code + QRemoteObjectNode client(QUrl(QStringLiteral("myprotocol:registry"))); + \endcode + + \sa {qRegisterRemoteObjectsServer} +*/ + +/*! + \fn void qRegisterRemoteObjectsServer(const QString &id) + \relates QtROServerFactory + + Registers the Remote Objects server \a id for the type \c{T}. + + If you need a custom transport protocol for Qt Remote Objects, you need to + register the client & server implementation here. + + \note This function requires that \c{T} is a fully defined type at the point + where the function is called. + + This example registers the class \c{CustomServerImpl} as \c{"myprotocol"}: + + \code + qRegisterRemoteObjectsServer(QStringLiteral("myprotocol")); + \endcode + + With this in place, you can now instantiate nodes using this new custom protocol: + + \code + QRemoteObjectNode client(QUrl(QStringLiteral("myprotocol:registry"))); + \endcode + + \sa {qRegisterRemoteObjectsServer} +*/ + +QtROIoDeviceBasePrivate::QtROIoDeviceBasePrivate() : QObjectPrivate() +{ + m_dataStream.setVersion(dataStreamVersion); + m_dataStream.setByteOrder(QDataStream::LittleEndian); +} + +QT_END_NAMESPACE diff --git a/src/remoteobjects/qconnectionfactories.h b/src/remoteobjects/qconnectionfactories.h new file mode 100644 index 0000000..b56a34f --- /dev/null +++ b/src/remoteobjects/qconnectionfactories.h @@ -0,0 +1,215 @@ +// Copyright (C) 2021 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QCONNECTIONFACTORIES_H +#define QCONNECTIONFACTORIES_H + +// +// W A R N I N G +// ------------- +// +// This file is part of the QtRO internal API and is not meant to be used +// in applications. It contains "internal" components, which are exported +// to allow new QIODevice channels to be added to extend QtRO. Usage of these +// APIs may make your code source and binary incompatible with future versions +// of Qt. +// + +#include + +#include + + +QT_BEGIN_NAMESPACE + +class QtROIoDeviceBasePrivate; +class QtROClientIoDevicePrivate; +class QtROExternalIoDevicePrivate; + +class Q_REMOTEOBJECTS_EXPORT QtROIoDeviceBase : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(QtROIoDeviceBase) + +public: + explicit QtROIoDeviceBase(QObject *parent = nullptr); + ~QtROIoDeviceBase() override; + + bool read(QtRemoteObjects::QRemoteObjectPacketTypeEnum &, QString &); + + virtual void write(const QByteArray &data); + virtual void write(const QByteArray &data, qint64); + virtual bool isOpen() const; + virtual void close(); + virtual qint64 bytesAvailable() const; + virtual QIODevice *connection() const = 0; + void initializeDataStream(); + bool isClosing() const; + void addSource(const QString &); + void removeSource(const QString &); + QSet remoteObjects() const; + +Q_SIGNALS: + void readyRead(); + void disconnected(); + +protected: + explicit QtROIoDeviceBase(QtROIoDeviceBasePrivate &, QObject *parent); + virtual QString deviceType() const = 0; + virtual void doClose() = 0; + +private: + Q_DECLARE_PRIVATE(QtROIoDeviceBase) + friend class QRemoteObjectNodePrivate; + friend class QConnectedReplicaImplementation; + friend class QRemoteObjectSourceIo; +}; + +class Q_REMOTEOBJECTS_EXPORT QtROServerIoDevice : public QtROIoDeviceBase +{ + Q_OBJECT + Q_DISABLE_COPY(QtROServerIoDevice) + +public: + explicit QtROServerIoDevice(QObject *parent = nullptr); + +protected: + QString deviceType() const override; +}; + +class Q_REMOTEOBJECTS_EXPORT QConnectionAbstractServer : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(QConnectionAbstractServer) + +public: + explicit QConnectionAbstractServer(QObject *parent = nullptr); + ~QConnectionAbstractServer() override; + + virtual bool hasPendingConnections() const = 0; + QtROServerIoDevice* nextPendingConnection(); + virtual QUrl address() const = 0; + virtual bool listen(const QUrl &address) = 0; + virtual QAbstractSocket::SocketError serverError() const = 0; + virtual void close() = 0; + +protected: + virtual QtROServerIoDevice* configureNewConnection() = 0; + +Q_SIGNALS: + void newConnection(); +}; + +class Q_REMOTEOBJECTS_EXPORT QtROClientIoDevice : public QtROIoDeviceBase +{ + Q_OBJECT + Q_DISABLE_COPY(QtROClientIoDevice) + +public: + explicit QtROClientIoDevice(QObject *parent = nullptr); + ~QtROClientIoDevice() override; + + void disconnectFromServer(); + virtual void connectToServer() = 0; + + QUrl url() const; + +Q_SIGNALS: + void shouldReconnect(QtROClientIoDevice*); + +protected: + virtual void doDisconnectFromServer() = 0; + QString deviceType() const override; + void setUrl(const QUrl &url); + +private: + Q_DECLARE_PRIVATE(QtROClientIoDevice) + friend class QtROClientFactory; +}; + +class QtROServerFactory +{ +public: + Q_REMOTEOBJECTS_EXPORT static QtROServerFactory *instance(); + + QConnectionAbstractServer *create(const QUrl &url, QObject *parent = nullptr) + { + auto creatorFunc = m_creatorFuncs.value(url.scheme()); + return creatorFunc ? (*creatorFunc)(parent) : nullptr; + } + + template + void registerType(const QString &id) + { + m_creatorFuncs[id] = [](QObject *parent) -> QConnectionAbstractServer * { + return new T(parent); + }; + } + + bool isValid(const QUrl &url) + { + return m_creatorFuncs.contains(url.scheme()); + } + +private: + friend class QtROFactoryLoader; + QtROServerFactory(); + + using CreatorFunc = QConnectionAbstractServer * (*)(QObject *); + QHash m_creatorFuncs; +}; + +class QtROClientFactory +{ +public: + Q_REMOTEOBJECTS_EXPORT static QtROClientFactory *instance(); + + /// creates an object from a string + QtROClientIoDevice *create(const QUrl &url, QObject *parent = nullptr) + { + auto creatorFunc = m_creatorFuncs.value(url.scheme()); + if (!creatorFunc) + return nullptr; + + QtROClientIoDevice *res = (*creatorFunc)(parent); + if (res) + res->setUrl(url); + return res; + } + + template + void registerType(const QString &id) + { + m_creatorFuncs[id] = [](QObject *parent) -> QtROClientIoDevice * { + return new T(parent); + }; + } + + bool isValid(const QUrl &url) + { + return m_creatorFuncs.contains(url.scheme()); + } + +private: + friend class QtROFactoryLoader; + QtROClientFactory(); + + using CreatorFunc = QtROClientIoDevice * (*)(QObject *); + QHash m_creatorFuncs; +}; + +template +inline void qRegisterRemoteObjectsClient(const QString &id) +{ + QtROClientFactory::instance()->registerType(id); +} + +template +inline void qRegisterRemoteObjectsServer(const QString &id) +{ + QtROServerFactory::instance()->registerType(id); +} + +QT_END_NAMESPACE + +#endif // QCONNECTIONFACTORIES_H diff --git a/src/remoteobjects/qconnectionfactories_p.h b/src/remoteobjects/qconnectionfactories_p.h new file mode 100644 index 0000000..7ea79d4 --- /dev/null +++ b/src/remoteobjects/qconnectionfactories_p.h @@ -0,0 +1,86 @@ +// Copyright (C) 2017-2015 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QCONNECTIONFACTORIES_P_H +#define QCONNECTIONFACTORIES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +#include +#include +#include "qremoteobjectpacket_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtRemoteObjects { + +static const int dataStreamVersion = QDataStream::Qt_6_2; +static const QLatin1String protocolVersion("QtRO 2.0"); + +} + +class QtROExternalIoDevice : public QtROIoDeviceBase +{ + Q_OBJECT + +public: + explicit QtROExternalIoDevice(QIODevice *device, QObject *parent=nullptr); + QIODevice *connection() const override; + bool isOpen() const override; + +protected: + void doClose() override; + QString deviceType() const override; +private: + Q_DECLARE_PRIVATE(QtROExternalIoDevice) +}; + +class QtROIoDeviceBasePrivate : public QObjectPrivate +{ +public: + QtROIoDeviceBasePrivate(); + + // TODO Remove stream() + QDataStream &stream() { return m_dataStream; } + + bool m_isClosing = false; + quint32 m_curReadSize = 0; + QDataStream m_dataStream; + QSet m_remoteObjects; + QRemoteObjectPackets::CodecBase *m_codec { nullptr }; + Q_DECLARE_PUBLIC(QtROIoDeviceBase) +}; + +class QtROClientIoDevicePrivate : public QtROIoDeviceBasePrivate +{ +public: + QtROClientIoDevicePrivate() : QtROIoDeviceBasePrivate() { } + QUrl m_url; + Q_DECLARE_PUBLIC(QtROClientIoDevice) +}; + +class QtROExternalIoDevicePrivate : public QtROIoDeviceBasePrivate +{ +public: + QtROExternalIoDevicePrivate(QIODevice *device) : QtROIoDeviceBasePrivate(), m_device(device) { } + QPointer m_device; + Q_DECLARE_PUBLIC(QtROExternalIoDevice) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/remoteobjects/qremoteobjectabstractitemmodeladapter.cpp b/src/remoteobjects/qremoteobjectabstractitemmodeladapter.cpp new file mode 100644 index 0000000..e890ce2 --- /dev/null +++ b/src/remoteobjects/qremoteobjectabstractitemmodeladapter.cpp @@ -0,0 +1,259 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qremoteobjectabstractitemmodeladapter_p.h" + +#include + +inline QList createModelRoleData(const QList &roles) +{ + QList roleData; + roleData.reserve(roles.size()); + for (int role : roles) + roleData.emplace_back(role); + return roleData; +} + +// consider evaluating performance difference with item data +inline QVariantList collectData(const QModelIndex &index, const QAbstractItemModel *model, + QModelRoleDataSpan roleDataSpan) +{ + model->multiData(index, roleDataSpan); + + QVariantList result; + result.reserve(roleDataSpan.size()); + for (auto &roleData : roleDataSpan) + result.push_back(std::move(roleData.data())); + + return result; +} + +inline QList filterRoles(const QList &roles, const QList &availableRoles) +{ + if (roles.isEmpty()) + return availableRoles; + + QList neededRoles; + for (int inRole : roles) { + for (int availableRole : availableRoles) + if (inRole == availableRole) { + neededRoles << inRole; + continue; + } + } + return neededRoles; +} + +QAbstractItemModelSourceAdapter::QAbstractItemModelSourceAdapter(QAbstractItemModel *obj, QItemSelectionModel *sel, const QList &roles) + : QObject(obj), + m_model(obj), + m_availableRoles(roles) +{ + QAbstractItemModelSourceAdapter::registerTypes(); + m_selectionModel = sel; + connect(m_model, &QAbstractItemModel::dataChanged, this, &QAbstractItemModelSourceAdapter::sourceDataChanged); + connect(m_model, &QAbstractItemModel::rowsInserted, this, &QAbstractItemModelSourceAdapter::sourceRowsInserted); + connect(m_model, &QAbstractItemModel::columnsInserted, this, &QAbstractItemModelSourceAdapter::sourceColumnsInserted); + connect(m_model, &QAbstractItemModel::rowsRemoved, this, &QAbstractItemModelSourceAdapter::sourceRowsRemoved); + connect(m_model, &QAbstractItemModel::rowsMoved, this, &QAbstractItemModelSourceAdapter::sourceRowsMoved); + connect(m_model, &QAbstractItemModel::layoutChanged, this, &QAbstractItemModelSourceAdapter::sourceLayoutChanged); + if (m_selectionModel) + connect(m_selectionModel, &QItemSelectionModel::currentChanged, this, &QAbstractItemModelSourceAdapter::sourceCurrentChanged); +} + +void QAbstractItemModelSourceAdapter::registerTypes() +{ + static bool alreadyRegistered = false; + if (alreadyRegistered) + return; + + alreadyRegistered = true; + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType>(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); +} + +QItemSelectionModel* QAbstractItemModelSourceAdapter::selectionModel() const +{ + return m_selectionModel; +} + +QSize QAbstractItemModelSourceAdapter::replicaSizeRequest(QtPrivate::IndexList parentList) +{ + QModelIndex parent = toQModelIndex(parentList, m_model); + const int rowCount = m_model->rowCount(parent); + const int columnCount = m_model->columnCount(parent); + const QSize size(columnCount, rowCount); + qCDebug(QT_REMOTEOBJECT_MODELS) << "parent" << parentList << "size=" << size; + return size; +} + +void QAbstractItemModelSourceAdapter::replicaSetData(const QtPrivate::IndexList &index, const QVariant &value, int role) +{ + const QModelIndex modelIndex = toQModelIndex(index, m_model); + Q_ASSERT(modelIndex.isValid()); + const bool result = m_model->setData(modelIndex, value, role); + Q_ASSERT(result); + Q_UNUSED(result) +} + +QtPrivate::DataEntries QAbstractItemModelSourceAdapter::replicaRowRequest(QtPrivate::IndexList start, QtPrivate::IndexList end, QList roles) +{ + qCDebug(QT_REMOTEOBJECT_MODELS) << "Requested rows" << "start=" << start << "end=" << end << "roles=" << roles; + + Q_ASSERT(start.size() == end.size()); + Q_ASSERT(!start.isEmpty()); + + if (roles.isEmpty()) + roles << m_availableRoles; + + QtPrivate::IndexList parentList = start; + Q_ASSERT(!parentList.isEmpty()); + parentList.pop_back(); + QModelIndex parent = toQModelIndex(parentList, m_model); + + const int startRow = start.last().row; + const int startColumn = start.last().column; + const int rowCount = m_model->rowCount(parent); + const int columnCount = m_model->columnCount(parent); + + QtPrivate::DataEntries entries; + if (rowCount <= 0) + return entries; + const int endRow = std::min(end.last().row, rowCount - 1); + const int endColumn = std::min(end.last().column, columnCount - 1); + Q_ASSERT_X(endRow >= 0 && endRow < rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(endRow).arg(rowCount))); + Q_ASSERT_X(endColumn >= 0 && endColumn < columnCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(endColumn).arg(columnCount))); + + auto roleData = createModelRoleData(roles); + for (int row = startRow; row <= endRow; ++row) { + for (int column = startColumn; column <= endColumn; ++column) { + const QModelIndex current = m_model->index(row, column, parent); + Q_ASSERT(current.isValid()); + const QtPrivate::IndexList currentList = QtPrivate::toModelIndexList(current, m_model); + const QVariantList data = collectData(current, m_model, roleData); + const bool hasChildren = m_model->hasChildren(current); + const Qt::ItemFlags flags = m_model->flags(current); + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "current=" << currentList << "data=" << data; + entries.data << QtPrivate::IndexValuePair(currentList, data, hasChildren, flags); + } + } + return entries; +} + +QtPrivate::MetaAndDataEntries QAbstractItemModelSourceAdapter::replicaCacheRequest(size_t size, const QList &roles) +{ + QtPrivate::MetaAndDataEntries res; + res.roles = roles.isEmpty() ? m_availableRoles : roles; + res.data = fetchTree(QModelIndex {}, size, res.roles); + const int rowCount = m_model->rowCount(QModelIndex{}); + const int columnCount = m_model->columnCount(QModelIndex{}); + res.size = QSize{columnCount, rowCount}; + return res; +} + +QVariantList QAbstractItemModelSourceAdapter::replicaHeaderRequest(QList orientations, QList sections, QList roles) +{ + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "orientations=" << orientations << "sections=" << sections << "roles=" << roles; + QVariantList data; + Q_ASSERT(roles.size() == sections.size()); + Q_ASSERT(roles.size() == orientations.size()); + for (int i = 0; i < roles.size(); ++i) { + data << m_model->headerData(sections[i], orientations[i], roles[i]); + } + return data; +} + +void QAbstractItemModelSourceAdapter::replicaSetCurrentIndex(QtPrivate::IndexList index, QItemSelectionModel::SelectionFlags command) +{ + if (m_selectionModel) + m_selectionModel->setCurrentIndex(toQModelIndex(index, m_model), command); +} + +void QAbstractItemModelSourceAdapter::sourceDataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QList & roles) const +{ + QList neededRoles = filterRoles(roles, availableRoles()); + if (neededRoles.isEmpty()) { + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "Needed roles is empty!"; + return; + } + Q_ASSERT(topLeft.isValid()); + Q_ASSERT(bottomRight.isValid()); + QtPrivate::IndexList start = QtPrivate::toModelIndexList(topLeft, m_model); + QtPrivate::IndexList end = QtPrivate::toModelIndexList(bottomRight, m_model); + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "neededRoles=" << neededRoles; + emit dataChanged(start, end, neededRoles); +} + +void QAbstractItemModelSourceAdapter::sourceRowsInserted(const QModelIndex & parent, int start, int end) +{ + QtPrivate::IndexList parentList = QtPrivate::toModelIndexList(parent, m_model); + emit rowsInserted(parentList, start, end); +} + +void QAbstractItemModelSourceAdapter::sourceColumnsInserted(const QModelIndex & parent, int start, int end) +{ + QtPrivate::IndexList parentList = QtPrivate::toModelIndexList(parent, m_model); + emit columnsInserted(parentList, start, end); +} + +void QAbstractItemModelSourceAdapter::sourceRowsRemoved(const QModelIndex & parent, int start, int end) +{ + QtPrivate::IndexList parentList = QtPrivate::toModelIndexList(parent, m_model); + emit rowsRemoved(parentList, start, end); +} + +void QAbstractItemModelSourceAdapter::sourceRowsMoved(const QModelIndex & sourceParent, int sourceRow, int count, const QModelIndex & destinationParent, int destinationChild) const +{ + emit rowsMoved(QtPrivate::toModelIndexList(sourceParent, m_model), sourceRow, count, QtPrivate::toModelIndexList(destinationParent, m_model), destinationChild); +} + +void QAbstractItemModelSourceAdapter::sourceCurrentChanged(const QModelIndex & current, const QModelIndex & previous) +{ + QtPrivate::IndexList currentIndex = QtPrivate::toModelIndexList(current, m_model); + QtPrivate::IndexList previousIndex = QtPrivate::toModelIndexList(previous, m_model); + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "current=" << currentIndex << "previous=" << previousIndex; + emit currentChanged(currentIndex, previousIndex); +} + +void QAbstractItemModelSourceAdapter::sourceLayoutChanged(const QList &parents, QAbstractItemModel::LayoutChangeHint hint) +{ + QtPrivate::IndexList indexes; + for (const QPersistentModelIndex &idx : parents) + indexes << QtPrivate::toModelIndexList((QModelIndex)idx, m_model); + emit layoutChanged(indexes, hint); +} + +QList QAbstractItemModelSourceAdapter::fetchTree(const QModelIndex &parent, size_t &size, const QList &roles) +{ + QList entries; + const int rowCount = m_model->rowCount(parent); + const int columnCount = m_model->columnCount(parent); + if (!columnCount || !rowCount) + return entries; + entries.reserve(std::min(rowCount * columnCount, int(size))); + auto roleData = createModelRoleData(roles); + for (int row = 0; row < rowCount && size > 0; ++row) + for (int column = 0; column < columnCount && size > 0; ++column) { + const auto index = m_model->index(row, column, parent); + const QtPrivate::IndexList currentList = QtPrivate::toModelIndexList(index, m_model); + const QVariantList data = collectData(index, m_model, roleData); + const bool hasChildren = m_model->hasChildren(index); + const Qt::ItemFlags flags = m_model->flags(index); + int rc = m_model->rowCount(index); + int cc = m_model->columnCount(index); + QtPrivate::IndexValuePair rowData(currentList, data, hasChildren, flags, QSize{cc, rc}); + --size; + if (hasChildren) + rowData.children = fetchTree(index, size, roles); + entries.push_back(rowData); + } + return entries; +} diff --git a/src/remoteobjects/qremoteobjectabstractitemmodeladapter_p.h b/src/remoteobjects/qremoteobjectabstractitemmodeladapter_p.h new file mode 100644 index 0000000..8033d94 --- /dev/null +++ b/src/remoteobjects/qremoteobjectabstractitemmodeladapter_p.h @@ -0,0 +1,274 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QREMOTEOBJECTS_ABSTRACT_ITEM_ADAPTER_P_H +#define QREMOTEOBJECTS_ABSTRACT_ITEM_ADAPTER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qremoteobjectabstractitemmodeltypes_p.h" +#include "qremoteobjectsource.h" + +#include + +QT_BEGIN_NAMESPACE + +class QAbstractItemModel; +class QItemSelectionModel; + +class QAbstractItemModelSourceAdapter : public QObject +{ + Q_OBJECT +public: + Q_INVOKABLE explicit QAbstractItemModelSourceAdapter(QAbstractItemModel *object, QItemSelectionModel *sel, const QList &roles = QList()); + Q_PROPERTY(QList availableRoles READ availableRoles WRITE setAvailableRoles NOTIFY availableRolesChanged) + Q_PROPERTY(QIntHash roleNames READ roleNames) + static void registerTypes(); + QItemSelectionModel* selectionModel() const; + +public Q_SLOTS: + QList availableRoles() const { return m_availableRoles; } + void setAvailableRoles(QList availableRoles) + { + if (availableRoles != m_availableRoles) + { + m_availableRoles = availableRoles; + Q_EMIT availableRolesChanged(); + } + } + + QIntHash roleNames() const {return m_model->roleNames();} + + QSize replicaSizeRequest(QtPrivate::IndexList parentList); + QtPrivate::DataEntries replicaRowRequest(QtPrivate::IndexList start, QtPrivate::IndexList end, QList roles); + QVariantList replicaHeaderRequest(QList orientations, QList sections, QList roles); + void replicaSetCurrentIndex(QtPrivate::IndexList index, QItemSelectionModel::SelectionFlags command); + void replicaSetData(const QtPrivate::IndexList &index, const QVariant &value, int role); + QtPrivate::MetaAndDataEntries replicaCacheRequest(size_t size, const QList &roles); + + void sourceDataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QList & roles = QList ()) const; + void sourceRowsInserted(const QModelIndex & parent, int start, int end); + void sourceColumnsInserted(const QModelIndex & parent, int start, int end); + void sourceRowsRemoved(const QModelIndex & parent, int start, int end); + void sourceRowsMoved(const QModelIndex & sourceParent, int sourceRow, int count, const QModelIndex & destinationParent, int destinationChild) const; + void sourceCurrentChanged(const QModelIndex & current, const QModelIndex & previous); + void sourceLayoutChanged(const QList &parents, QAbstractItemModel::LayoutChangeHint hint); +Q_SIGNALS: + void availableRolesChanged(); + void dataChanged(QtPrivate::IndexList topLeft, QtPrivate::IndexList bottomRight, QList roles) const; + void rowsInserted(QtPrivate::IndexList parent, int start, int end) const; + void rowsRemoved(QtPrivate::IndexList parent, int start, int end) const; + void rowsMoved(QtPrivate::IndexList sourceParent, int sourceRow, int count, QtPrivate::IndexList destinationParent, int destinationChild) const; + void currentChanged(QtPrivate::IndexList current, QtPrivate::IndexList previous); + void columnsInserted(QtPrivate::IndexList parent, int start, int end) const; + void layoutChanged(QtPrivate::IndexList parents, QAbstractItemModel::LayoutChangeHint hint); + +private: + QAbstractItemModelSourceAdapter(); + QList fetchTree(const QModelIndex &parent, size_t &size, const QList &roles); + + QAbstractItemModel *m_model; + QItemSelectionModel *m_selectionModel; + QList m_availableRoles; +}; + +template +struct QAbstractItemAdapterSourceAPI : public SourceApiMap +{ + QAbstractItemAdapterSourceAPI(const QString &name) + : SourceApiMap() + , m_signalArgTypes {} + , m_methodArgTypes {} + , m_name(name) + { + m_properties[0] = 2; + m_properties[1] = QtPrivate::qtro_property_index(&AdapterType::availableRoles, static_cast (QObject::*)()>(nullptr),"availableRoles"); + m_properties[2] = QtPrivate::qtro_property_index(&AdapterType::roleNames, static_cast(nullptr),"roleNames"); + m_signals[0] = 10; + m_signals[1] = QtPrivate::qtro_signal_index(&AdapterType::availableRolesChanged, static_cast(nullptr),m_signalArgCount+0,&m_signalArgTypes[0]); + m_signals[2] = QtPrivate::qtro_signal_index(&AdapterType::dataChanged, static_cast)>(nullptr),m_signalArgCount+1,&m_signalArgTypes[1]); + m_signals[3] = QtPrivate::qtro_signal_index(&AdapterType::rowsInserted, static_cast(nullptr),m_signalArgCount+2,&m_signalArgTypes[2]); + m_signals[4] = QtPrivate::qtro_signal_index(&AdapterType::rowsRemoved, static_cast(nullptr),m_signalArgCount+3,&m_signalArgTypes[3]); + m_signals[5] = QtPrivate::qtro_signal_index(&AdapterType::rowsMoved, static_cast(nullptr),m_signalArgCount+4,&m_signalArgTypes[4]); + m_signals[6] = QtPrivate::qtro_signal_index(&AdapterType::currentChanged, static_cast(nullptr),m_signalArgCount+5,&m_signalArgTypes[5]); + m_signals[7] = QtPrivate::qtro_signal_index(&ObjectType::modelReset, static_cast(nullptr),m_signalArgCount+6,&m_signalArgTypes[6]); + m_signals[8] = QtPrivate::qtro_signal_index(&ObjectType::headerDataChanged, static_cast(nullptr),m_signalArgCount+7,&m_signalArgTypes[7]); + m_signals[9] = QtPrivate::qtro_signal_index(&AdapterType::columnsInserted, static_cast(nullptr),m_signalArgCount+8,&m_signalArgTypes[8]); + m_signals[10] = QtPrivate::qtro_signal_index(&AdapterType::layoutChanged, static_cast(nullptr),m_signalArgCount+9,&m_signalArgTypes[9]); + m_methods[0] = 6; + m_methods[1] = QtPrivate::qtro_method_index(&AdapterType::replicaSizeRequest, static_cast(nullptr),"replicaSizeRequest(QtPrivate::IndexList)",m_methodArgCount+0,&m_methodArgTypes[0]); + m_methods[2] = QtPrivate::qtro_method_index(&AdapterType::replicaRowRequest, static_cast)>(nullptr),"replicaRowRequest(QtPrivate::IndexList,QtPrivate::IndexList,QList)",m_methodArgCount+1,&m_methodArgTypes[1]); + m_methods[3] = QtPrivate::qtro_method_index(&AdapterType::replicaHeaderRequest, static_cast,QList,QList)>(nullptr),"replicaHeaderRequest(QList,QList,QList)",m_methodArgCount+2,&m_methodArgTypes[2]); + m_methods[4] = QtPrivate::qtro_method_index(&AdapterType::replicaSetCurrentIndex, static_cast(nullptr),"replicaSetCurrentIndex(QtPrivate::IndexList,QItemSelectionModel::SelectionFlags)",m_methodArgCount+3,&m_methodArgTypes[3]); + m_methods[5] = QtPrivate::qtro_method_index(&AdapterType::replicaSetData, static_cast(nullptr),"replicaSetData(QtPrivate::IndexList,QVariant,int)",m_methodArgCount+4,&m_methodArgTypes[4]); + m_methods[6] = QtPrivate::qtro_method_index(&AdapterType::replicaCacheRequest, static_cast)>(nullptr),"replicaCacheRequest(size_t,QList)",m_methodArgCount+5,&m_methodArgTypes[5]); + } + + QString name() const override { return m_name; } + QString typeName() const override { return QStringLiteral("QAbstractItemModelAdapter"); } + int enumCount() const override { return 0; } + int propertyCount() const override { return m_properties[0]; } + int signalCount() const override { return m_signals[0]; } + int methodCount() const override { return m_methods[0]; } + int sourceEnumIndex(int /*index*/) const override + { + return -1; + } + int sourcePropertyIndex(int index) const override + { + if (index < 0 || index >= m_properties[0]) + return -1; + return m_properties[index+1]; + } + int sourceSignalIndex(int index) const override + { + if (index < 0 || index >= m_signals[0]) + return -1; + return m_signals[index+1]; + } + int sourceMethodIndex(int index) const override + { + if (index < 0 || index >= m_methods[0]) + return -1; + return m_methods[index+1]; + } + int signalParameterCount(int index) const override { return m_signalArgCount[index]; } + int signalParameterType(int sigIndex, int paramIndex) const override { return m_signalArgTypes[sigIndex][paramIndex]; } + int methodParameterCount(int index) const override { return m_methodArgCount[index]; } + int methodParameterType(int methodIndex, int paramIndex) const override { return m_methodArgTypes[methodIndex][paramIndex]; } + QByteArrayList signalParameterNames(int index) const override + { + QByteArrayList res; + int count = signalParameterCount(index); + while (count--) + res << QByteArray{}; + return res; + } + int propertyIndexFromSignal(int index) const override + { + switch (index) { + case 0: return m_properties[1]; + } + return -1; + } + int propertyRawIndexFromSignal(int index) const override + { + switch (index) { + case 0: return 0; + } + return -1; + } + const QByteArray signalSignature(int index) const override + { + switch (index) { + case 0: return QByteArrayLiteral("availableRolesChanged()"); + case 1: return QByteArrayLiteral("dataChanged(QtPrivate::IndexList,QtPrivate::IndexList,QList)"); + case 2: return QByteArrayLiteral("rowsInserted(QtPrivate::IndexList,int,int)"); + case 3: return QByteArrayLiteral("rowsRemoved(QtPrivate::IndexList,int,int)"); + case 4: return QByteArrayLiteral("rowsMoved(QtPrivate::IndexList,int,int,QtPrivate::IndexList,int)"); + case 5: return QByteArrayLiteral("currentChanged(QtPrivate::IndexList,QtPrivate::IndexList)"); + case 6: return QByteArrayLiteral("resetModel()"); + case 7: return QByteArrayLiteral("headerDataChanged(Qt::Orientation,int,int)"); + case 8: return QByteArrayLiteral("columnsInserted(QtPrivate::IndexList,int,int)"); + case 9: return QByteArrayLiteral("layoutChanged(QtPrivate::IndexList,QAbstractItemModel::LayoutChangeHint)"); + } + return QByteArrayLiteral(""); + } + const QByteArray methodSignature(int index) const override + { + switch (index) { + case 0: return QByteArrayLiteral("replicaSizeRequest(QtPrivate::IndexList)"); + case 1: return QByteArrayLiteral("replicaRowRequest(QtPrivate::IndexList,QtPrivate::IndexList,QList)"); + case 2: return QByteArrayLiteral("replicaHeaderRequest(QList,QList,QList)"); + case 3: return QByteArrayLiteral("replicaSetCurrentIndex(QtPrivate::IndexList,QItemSelectionModel::SelectionFlags)"); + case 4: return QByteArrayLiteral("replicaSetData(QtPrivate::IndexList,QVariant,int)"); + case 5: return QByteArrayLiteral("replicaCacheRequest(size_t,QList)"); + } + return QByteArrayLiteral(""); + } + QMetaMethod::MethodType methodType(int) const override + { + return QMetaMethod::Slot; + } + const QByteArray typeName(int index) const override + { + switch (index) { + case 0: return QByteArrayLiteral("QSize"); + case 1: return QByteArrayLiteral("QtPrivate::DataEntries"); + case 2: return QByteArrayLiteral("QVariantList"); + case 3: return QByteArrayLiteral(""); + case 5: return QByteArrayLiteral("QtPrivate::MetaAndDataEntries"); + } + return QByteArrayLiteral(""); + } + + QByteArrayList methodParameterNames(int index) const override + { + QByteArrayList res; + int count = methodParameterCount(index); + while (count--) + res << QByteArray{}; + return res; + } + + QByteArray objectSignature() const override { return QByteArray{}; } + bool isAdapterSignal(int index) const override + { + switch (index) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 8: + case 9: + return true; + } + return false; + } + bool isAdapterMethod(int index) const override + { + switch (index) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + return true; + } + return false; + } + bool isAdapterProperty(int index) const override + { + switch (index) { + case 0: + case 1: + return true; + } + return false; + } + + int m_properties[3]; + int m_signals[11]; + int m_methods[7]; + int m_signalArgCount[10]; + const int* m_signalArgTypes[10]; + int m_methodArgCount[6]; + const int* m_methodArgTypes[6]; + QString m_name; +}; + +QT_END_NAMESPACE + +#endif //QREMOTEOBJECTS_ABSTRACT_ITEM_ADAPTER_P_H diff --git a/src/remoteobjects/qremoteobjectabstractitemmodelreplica.cpp b/src/remoteobjects/qremoteobjectabstractitemmodelreplica.cpp new file mode 100644 index 0000000..a4e43c0 --- /dev/null +++ b/src/remoteobjects/qremoteobjectabstractitemmodelreplica.cpp @@ -0,0 +1,1147 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qremoteobjectabstractitemmodelreplica.h" +#include "qremoteobjectabstractitemmodelreplica_p.h" + +#include "qremoteobjectnode.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QT_IMPL_METATYPE_EXTERN_TAGGED(QtPrivate::ModelIndex, QtPrivate__ModelIndex) +QT_IMPL_METATYPE_EXTERN_TAGGED(QtPrivate::IndexList, QtPrivate__IndexList) +QT_IMPL_METATYPE_EXTERN_TAGGED(QtPrivate::DataEntries, QtPrivate__DataEntries) +QT_IMPL_METATYPE_EXTERN_TAGGED(QtPrivate::MetaAndDataEntries, QtPrivate__MetaAndDataEntries) +QT_IMPL_METATYPE_EXTERN_TAGGED(QtPrivate::IndexValuePair, QtPrivate__IndexValuePair) +QT_IMPL_METATYPE_EXTERN_TAGGED(Qt::Orientation, Qt__Orientation) +QT_IMPL_METATYPE_EXTERN_TAGGED(QItemSelectionModel::SelectionFlags, + QItemSelectionModel__SelectionFlags) + +inline QDebug operator<<(QDebug stream, const RequestedData &data) +{ + return stream.nospace() << "RequestedData[start=" << data.start << ", end=" << data.end << ", roles=" << data.roles << "]"; +} + +CacheData::CacheData(QAbstractItemModelReplicaImplementation *model, CacheData *parentItem) + : replicaModel(model) + , parent(parentItem) + , hasChildren(false) + , columnCount(0) + , rowCount(0) +{ + if (parent) + replicaModel->m_activeParents.insert(parent); +} + +CacheData::~CacheData() { + if (parent && !replicaModel->m_activeParents.empty()) + replicaModel->m_activeParents.erase(this); +} + +QAbstractItemModelReplicaImplementation::QAbstractItemModelReplicaImplementation() + : QRemoteObjectReplica() + , m_selectionModel(nullptr) + , m_rootItem(this) +{ + QAbstractItemModelReplicaImplementation::registerMetatypes(); + initializeModelConnections(); + connect(this, &QAbstractItemModelReplicaImplementation::availableRolesChanged, this, [this]{ + m_availableRoles.clear(); + }); +} + +QAbstractItemModelReplicaImplementation::QAbstractItemModelReplicaImplementation(QRemoteObjectNode *node, const QString &name) + : QRemoteObjectReplica(ConstructWithNode) + , m_selectionModel(nullptr) + , m_rootItem(this) +{ + QAbstractItemModelReplicaImplementation::registerMetatypes(); + initializeModelConnections(); + initializeNode(node, name); + connect(this, &QAbstractItemModelReplicaImplementation::availableRolesChanged, this, [this]{ + m_availableRoles.clear(); + }); +} + +QAbstractItemModelReplicaImplementation::~QAbstractItemModelReplicaImplementation() +{ + m_rootItem.clear(); + qDeleteAll(m_pendingRequests); +} + +void QAbstractItemModelReplicaImplementation::initialize() +{ + QVariantList properties; + properties << QVariant::fromValue(QList()); + properties << QVariant::fromValue(QIntHash()); + setProperties(std::move(properties)); +} + +void QAbstractItemModelReplicaImplementation::registerMetatypes() +{ + static bool alreadyRegistered = false; + if (alreadyRegistered) + return; + + alreadyRegistered = true; + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType>(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); +} + +void QAbstractItemModelReplicaImplementation::initializeModelConnections() +{ + connect(this, &QAbstractItemModelReplicaImplementation::dataChanged, this, &QAbstractItemModelReplicaImplementation::onDataChanged); + connect(this, &QAbstractItemModelReplicaImplementation::rowsInserted, this, &QAbstractItemModelReplicaImplementation::onRowsInserted); + connect(this, &QAbstractItemModelReplicaImplementation::columnsInserted, this, &QAbstractItemModelReplicaImplementation::onColumnsInserted); + connect(this, &QAbstractItemModelReplicaImplementation::rowsRemoved, this, &QAbstractItemModelReplicaImplementation::onRowsRemoved); + connect(this, &QAbstractItemModelReplicaImplementation::rowsMoved, this, &QAbstractItemModelReplicaImplementation::onRowsMoved); + connect(this, &QAbstractItemModelReplicaImplementation::currentChanged, this, &QAbstractItemModelReplicaImplementation::onCurrentChanged); + connect(this, &QAbstractItemModelReplicaImplementation::modelReset, this, &QAbstractItemModelReplicaImplementation::onModelReset); + connect(this, &QAbstractItemModelReplicaImplementation::headerDataChanged, this, &QAbstractItemModelReplicaImplementation::onHeaderDataChanged); + connect(this, &QAbstractItemModelReplicaImplementation::layoutChanged, this, &QAbstractItemModelReplicaImplementation::onLayoutChanged); + +} + +inline void removeIndexFromRow(const QModelIndex &index, const QList &roles, CachedRowEntry *entry) +{ + CachedRowEntry &entryRef = *entry; + if (index.column() < entryRef.size()) { + CacheEntry &entry = entryRef[index.column()]; + if (roles.isEmpty()) { + entry.data.clear(); + } else { + for (int role : roles) + entry.data.remove(role); + } + } +} + +void QAbstractItemModelReplicaImplementation::onReplicaCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_UNUSED(previous) + QtPrivate::IndexList currentIndex = QtPrivate::toModelIndexList(current, q); + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "current=" << currentIndex; + replicaSetCurrentIndex(currentIndex, QItemSelectionModel::Clear|QItemSelectionModel::Select|QItemSelectionModel::Current); +} + +void QAbstractItemModelReplicaImplementation::setModel(QAbstractItemModelReplica *model) +{ + q = model; + setParent(model); + m_selectionModel.reset(new QItemSelectionModel(model)); + connect(m_selectionModel.data(), &QItemSelectionModel::currentChanged, this, &QAbstractItemModelReplicaImplementation::onReplicaCurrentChanged); +} + +bool QAbstractItemModelReplicaImplementation::clearCache(const QtPrivate::IndexList &start, const QtPrivate::IndexList &end, const QList &roles = QList()) +{ + Q_ASSERT(start.size() == end.size()); + + bool ok = true; + const QModelIndex startIndex = toQModelIndex(start, q, &ok); + if (!ok) + return false; + const QModelIndex endIndex = toQModelIndex(end, q, &ok); + if (!ok) + return false; + Q_ASSERT(startIndex.isValid()); + Q_ASSERT(endIndex.isValid()); + Q_ASSERT(startIndex.parent() == endIndex.parent()); + Q_UNUSED(endIndex) + QModelIndex parentIndex = startIndex.parent(); + auto parentItem = cacheData(parentIndex); + + const int startRow = start.last().row; + const int lastRow = end.last().row; + const int startColumn = start.last().column; + const int lastColumn = end.last().column; + for (int row = startRow; row <= lastRow; ++row) { + Q_ASSERT_X(row >= 0 && row < parentItem->rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(row).arg(parentItem->rowCount))); + auto item = parentItem->children.get(row); + if (item) { + CachedRowEntry *entry = &(item->cachedRowEntry); + for (int column = startColumn; column <= lastColumn; ++column) + removeIndexFromRow(q->index(row, column, parentIndex), roles, entry); + } + } + return true; +} + +void QAbstractItemModelReplicaImplementation::onDataChanged(const QtPrivate::IndexList &start, const QtPrivate::IndexList &end, const QList &roles) +{ + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "roles=" << roles; + + // we need to clear the cache to make sure the new remote data is fetched if the new data call is happening + if (clearCache(start, end, roles)) { + bool ok = true; + const QModelIndex startIndex = toQModelIndex(start, q, &ok); + if (!ok) + return; + const QModelIndex endIndex = toQModelIndex(end, q, &ok); + if (!ok) + return; + Q_ASSERT(startIndex.parent() == endIndex.parent()); + auto parentItem = cacheData(startIndex.parent()); + int startRow = start.last().row; + int endRow = end.last().row; + bool dataChanged = false; + while (startRow <= endRow) { + for (;startRow <= endRow; startRow++) { + if (parentItem->children.exists(startRow)) + break; + } + + if (startRow > endRow) + break; + + RequestedData data; + data.roles = roles; + data.start = start; + data.start.last().row = startRow; + + while (startRow <= endRow && parentItem->children.exists(startRow)) + ++startRow; + + data.end = end; + data.end.last().row = startRow -1; + + m_requestedData.append(data); + dataChanged = true; + } + + if (dataChanged) + QMetaObject::invokeMethod(this, "fetchPendingData", Qt::QueuedConnection); + } +} + +void QAbstractItemModelReplicaImplementation::onRowsInserted(const QtPrivate::IndexList &parent, int start, int end) +{ + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "parent=" << parent; + + bool treeFullyLazyLoaded = true; + const QModelIndex parentIndex = toQModelIndex(parent, q, &treeFullyLazyLoaded, true); + if (!treeFullyLazyLoaded) + return; + + auto parentItem = cacheData(parentIndex); + q->beginInsertRows(parentIndex, start, end); + parentItem->insertChildren(start, end); + for (int i = start; i <= end; ++i) + m_headerData[1].append(CacheEntry()); + q->endInsertRows(); + if (!parentItem->hasChildren && parentItem->columnCount > 0) { + parentItem->hasChildren = true; + emit q->dataChanged(parentIndex, parentIndex); + } +} + +void QAbstractItemModelReplicaImplementation::onColumnsInserted(const QtPrivate::IndexList &parent, int start, int end) +{ + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "parent=" << parent; + + bool treeFullyLazyLoaded = true; + const QModelIndex parentIndex = toQModelIndex(parent, q, &treeFullyLazyLoaded); + if (!treeFullyLazyLoaded) + return; + + //Since we need to support QAIM and models that don't emit columnCountChanged + //check if we have a constant columnCount everywhere if thats the case don't insert + //more columns + auto parentItem = cacheData(parentIndex); + auto parentOfParent = parentItem->parent; + if (parentOfParent && parentItem != &m_rootItem) + if (parentOfParent->columnCount == parentItem->columnCount) + return; + q->beginInsertColumns(parentIndex, start, end); + parentItem->columnCount += end - start + 1; + for (int i = start; i <= end; ++i) + m_headerData[0].append(CacheEntry()); + q->endInsertColumns(); + if (!parentItem->hasChildren && parentItem->children.size() > 0) { + parentItem->hasChildren = true; + emit q->dataChanged(parentIndex, parentIndex); + } + +} + +void QAbstractItemModelReplicaImplementation::onRowsRemoved(const QtPrivate::IndexList &parent, int start, int end) +{ + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "parent=" << parent; + + bool treeFullyLazyLoaded = true; + const QModelIndex parentIndex = toQModelIndex(parent, q, &treeFullyLazyLoaded); + if (!treeFullyLazyLoaded) + return; + + auto parentItem = cacheData(parentIndex); + q->beginRemoveRows(parentIndex, start, end); + if (parentItem) + parentItem->removeChildren(start, end); + m_headerData[1].erase(m_headerData[1].begin() + start, m_headerData[1].begin() + end + 1); + q->endRemoveRows(); +} + +void QAbstractItemModelReplicaImplementation::onRowsMoved(QtPrivate::IndexList srcParent, int srcRow, int count, QtPrivate::IndexList destParent, int destRow) +{ + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO; + + const QModelIndex sourceParent = toQModelIndex(srcParent, q); + const QModelIndex destinationParent = toQModelIndex(destParent, q); + Q_ASSERT(!sourceParent.isValid()); + Q_ASSERT(!destinationParent.isValid()); + q->beginMoveRows(sourceParent, srcRow, count, destinationParent, destRow); +//TODO misses parents... + QtPrivate::IndexList start, end; + start << QtPrivate::ModelIndex(srcRow, 0); + end << QtPrivate::ModelIndex(srcRow + count, q->columnCount(sourceParent)-1); + clearCache(start, end); + QtPrivate::IndexList start2, end2; + start2 << QtPrivate::ModelIndex(destRow, 0); + end2 << QtPrivate::ModelIndex(destRow + count, q->columnCount(destinationParent)-1); + clearCache(start2, end2); + q->endMoveRows(); +} + +void QAbstractItemModelReplicaImplementation::onCurrentChanged(QtPrivate::IndexList current, QtPrivate::IndexList previous) +{ + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "current=" << current << "previous=" << previous; + Q_UNUSED(previous) + Q_ASSERT(m_selectionModel); + bool ok; + // If we have several tree models sharing a selection model, we + // can't guarantee that all Replicas have the selected cell + // available. + const QModelIndex currentIndex = toQModelIndex(current, q, &ok); + // Ignore selection if we can't find the desired cell. + if (ok) + m_selectionModel->setCurrentIndex(currentIndex, QItemSelectionModel::Clear|QItemSelectionModel::Select|QItemSelectionModel::Current); +} + +void QAbstractItemModelReplicaImplementation::handleInitDone(QRemoteObjectPendingCallWatcher *watcher) +{ + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO; + + handleModelResetDone(watcher); + m_initDone = true; + emit q->initialized(); +} + +void QAbstractItemModelReplicaImplementation::handleModelResetDone(QRemoteObjectPendingCallWatcher *watcher) +{ + QSize size; + if (m_initialAction == QtRemoteObjects::FetchRootSize) + size = watcher->returnValue().toSize(); + else { + Q_ASSERT(watcher->returnValue().canConvert()); + size = watcher->returnValue().value().size; + } + + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "size=" << size; + + q->beginResetModel(); + m_rootItem.clear(); + if (size.height() > 0) { + m_rootItem.rowCount = size.height(); + m_rootItem.hasChildren = true; + } + + m_rootItem.columnCount = size.width(); + m_headerData[0].resize(size.width()); + m_headerData[1].resize(size.height()); + { + QList &headerEntries = m_headerData[0]; + for (int i = 0; i < size.width(); ++i ) + headerEntries[i].data.clear(); + } + { + QList &headerEntries = m_headerData[1]; + for (int i = 0; i < size.height(); ++i ) + headerEntries[i].data.clear(); + } + if (m_initialAction == QtRemoteObjects::PrefetchData) { + auto entries = watcher->returnValue().value(); + for (int i = 0; i < entries.data.size(); ++i) + fillCache(entries.data[i], entries.roles); + } + q->endResetModel(); + m_pendingRequests.removeAll(watcher); + delete watcher; +} + +void QAbstractItemModelReplicaImplementation::handleSizeDone(QRemoteObjectPendingCallWatcher *watcher) +{ + SizeWatcher *sizeWatcher = static_cast(watcher); + const QSize size = sizeWatcher->returnValue().toSize(); + auto parentItem = cacheData(sizeWatcher->parentList); + const QModelIndex parent = toQModelIndex(sizeWatcher->parentList, q); + + if (size.width() != parentItem->columnCount) { + const int columnCount = std::max(0, parentItem->columnCount); + Q_ASSERT_X(size.width() >= parentItem->columnCount, __FUNCTION__, "The column count should only shrink in columnsRemoved!!"); + parentItem->columnCount = size.width(); + if (size.width() > columnCount) { + Q_ASSERT(size.width() > 0); + q->beginInsertColumns(parent, columnCount, size.width() - 1); + q->endInsertColumns(); + } else { + Q_ASSERT_X(size.width() == columnCount, __FUNCTION__, qPrintable(QString(QLatin1String("%1 != %2")).arg(size.width()).arg(columnCount))); + } + } + + Q_ASSERT_X(size.height() >= parentItem->rowCount, __FUNCTION__, "The new size and the current size should match!!"); + if (!parentItem->rowCount) { + if (size.height() > 0) { + q->beginInsertRows(parent, 0, size.height() - 1); + parentItem->rowCount = size.height(); + q->endInsertRows(); + } + } else { + Q_ASSERT_X(parentItem->rowCount == size.height(), __FUNCTION__, qPrintable(QString(QLatin1String("%1 != %2")).arg(parentItem->rowCount).arg(size.height()))); + } + m_pendingRequests.removeAll(watcher); + delete watcher; +} + +void QAbstractItemModelReplicaImplementation::init() +{ + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << this->node()->objectName(); + QRemoteObjectPendingCallWatcher *watcher = doModelReset(); + connect(watcher, &QRemoteObjectPendingCallWatcher::finished, this, &QAbstractItemModelReplicaImplementation::handleInitDone); +} + +QRemoteObjectPendingCallWatcher* QAbstractItemModelReplicaImplementation::doModelReset() +{ + qDeleteAll(m_pendingRequests); + m_pendingRequests.clear(); + QtPrivate::IndexList parentList; + QRemoteObjectPendingCallWatcher *watcher; + if (m_initialAction == QtRemoteObjects::FetchRootSize) { + auto call = replicaSizeRequest(parentList); + watcher = new SizeWatcher(parentList, call); + } else { + auto call = replicaCacheRequest(m_rootItem.children.cacheSize, m_initialFetchRolesHint); + watcher = new QRemoteObjectPendingCallWatcher(call); + } + m_pendingRequests.push_back(watcher); + return watcher; +} + +inline void fillCacheEntry(CacheEntry *entry, const QtPrivate::IndexValuePair &pair, const QList &roles) +{ + Q_ASSERT(entry); + + const QVariantList &data = pair.data; + Q_ASSERT(roles.size() == data.size()); + + entry->flags = pair.flags; + + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "data.size=" << data.size(); + for (int i = 0; i < data.size(); ++i) { + const int role = roles[i]; + const QVariant dataVal = data[i]; + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "role=" << role << "data=" << dataVal; + entry->data[role] = dataVal; + } +} + +inline void fillRow(CacheData *item, const QtPrivate::IndexValuePair &pair, const QAbstractItemModel *model, const QList &roles) +{ + CachedRowEntry &rowRef = item->cachedRowEntry; + const QModelIndex index = toQModelIndex(pair.index, model); + Q_ASSERT(index.isValid()); + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "row=" << index.row() << "column=" << index.column(); + if (index.column() == 0) + item->hasChildren = pair.hasChildren; + bool existed = false; + for (int i = 0; i < rowRef.size(); ++i) { + if (i == index.column()) { + fillCacheEntry(&rowRef[i], pair, roles); + existed = true; + } + } + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "existed=" << existed; + if (!existed) { + CacheEntry entries; + fillCacheEntry(&entries, pair, roles); + rowRef.append(entries); + } +} + +int collectEntriesForRow(QtPrivate::DataEntries* filteredEntries, int row, const QtPrivate::DataEntries &entries, int startIndex) +{ + Q_ASSERT(filteredEntries); + const int size = int(entries.data.size()); + for (int i = startIndex; i < size; ++i) + { + const QtPrivate::IndexValuePair &pair = entries.data[i]; + if (pair.index.last().row == row) + filteredEntries->data << pair; + else + return i; + } + return size; +} + +void QAbstractItemModelReplicaImplementation::fillCache(const QtPrivate::IndexValuePair &pair, const QList &roles) +{ + if (auto item = createCacheData(pair.index)) { + fillRow(item, pair, q, roles); + item->rowCount = pair.size.height(); + item->columnCount = pair.size.width(); + } + for (const auto &it : pair.children) + fillCache(it, roles); +} + +void QAbstractItemModelReplicaImplementation::requestedData(QRemoteObjectPendingCallWatcher *qobject) +{ + RowWatcher *watcher = static_cast(qobject); + Q_ASSERT(watcher); + Q_ASSERT(watcher->start.size() == watcher->end.size()); + + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << watcher->start << "end=" << watcher->end; + + QtPrivate::IndexList parentList = watcher->start; + Q_ASSERT(!parentList.isEmpty()); + parentList.pop_back(); + auto parentItem = cacheData(parentList); + QtPrivate::DataEntries entries = watcher->returnValue().value(); + + const int rowCount = parentItem->rowCount; + const int columnCount = parentItem->columnCount; + + if (rowCount < 1 || columnCount < 1) + return; + + const int startRow = std::min(watcher->start.last().row, rowCount - 1); + const int endRow = std::min(watcher->end.last().row, rowCount - 1); + const int startColumn = std::min(watcher->start.last().column, columnCount - 1); + const int endColumn = std::min(watcher->end.last().column, columnCount - 1); + Q_ASSERT_X(startRow >= 0 && startRow < parentItem->rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(startRow).arg(parentItem->rowCount))); + Q_ASSERT_X(endRow >= 0 && endRow < parentItem->rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(endRow).arg(parentItem->rowCount))); + + for (int i = 0; i < entries.data.size(); ++i) { + QtPrivate::IndexValuePair pair = entries.data[i]; + if (auto item = createCacheData(pair.index)) + fillRow(item, pair, q, watcher->roles); + } + + const QModelIndex parentIndex = toQModelIndex(parentList, q); + const QModelIndex startIndex = q->index(startRow, startColumn, parentIndex); + const QModelIndex endIndex = q->index(endRow, endColumn, parentIndex); + Q_ASSERT(startIndex.isValid()); + Q_ASSERT(endIndex.isValid()); + emit q->dataChanged(startIndex, endIndex, watcher->roles); + m_pendingRequests.removeAll(watcher); + delete watcher; +} + +void QAbstractItemModelReplicaImplementation::fetchPendingData() +{ + if (m_requestedData.isEmpty()) + return; + + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "m_requestedData.size=" << m_requestedData.size(); + + std::vector finalRequests; + RequestedData curData; + for (const RequestedData &data : qExchange(m_requestedData, {})) { + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "REQUESTED start=" << data.start << "end=" << data.end << "roles=" << data.roles; + + Q_ASSERT(!data.start.isEmpty()); + Q_ASSERT(!data.end.isEmpty()); + Q_ASSERT(data.start.size() == data.end.size()); + if (curData.start.isEmpty() || curData.start.last().row == -1 || curData.start.last().column == -1) + curData = data; + if (curData.start.size() != data.start.size()) { + finalRequests.push_back(curData); + curData = data; + } else { + if (data.start.size() > 1) { + for (int i = 0; i < data.start.size() - 1; ++i) { + if (curData.start[i].row != data.start[i].row || + curData.start[i].column != data.start[i].column) { + finalRequests.push_back(curData); + curData = data; + } + } + } + + const QtPrivate::ModelIndex curIndStart = curData.start.last(); + const QtPrivate::ModelIndex curIndEnd = curData.end.last(); + const QtPrivate::ModelIndex dataIndStart = data.start.last(); + const QtPrivate::ModelIndex dataIndEnd = data.end.last(); + const QtPrivate::ModelIndex resStart(std::min(curIndStart.row, dataIndStart.row), std::min(curIndStart.column, dataIndStart.column)); + const QtPrivate::ModelIndex resEnd(std::max(curIndEnd.row, dataIndEnd.row), std::max(curIndEnd.column, dataIndEnd.column)); + QList roles = curData.roles; + if (!curData.roles.isEmpty()) { + for (int role : data.roles) { + if (!curData.roles.contains(role)) + roles.append(role); + } + } + QRect firstRect( QPoint(curIndStart.row, curIndStart.column), QPoint(curIndEnd.row, curIndEnd.column)); + QRect secondRect( QPoint(dataIndStart.row, dataIndStart.column), QPoint(dataIndEnd.row, dataIndEnd.column)); + + const bool borders = (qAbs(curIndStart.row - dataIndStart.row) == 1) || + (qAbs(curIndStart.column - dataIndStart.column) == 1) || + (qAbs(curIndEnd.row - dataIndEnd.row) == 1) || + (qAbs(curIndEnd.column - dataIndEnd.column) == 1); + + if ((resEnd.row - resStart.row < 100) && (firstRect.intersects(secondRect) || borders)) { + QtPrivate::IndexList start = curData.start; + start.pop_back(); + start.push_back(resStart); + QtPrivate::IndexList end = curData.end; + end.pop_back(); + end.push_back(resEnd); + curData.start = start; + curData.end = end; + curData.roles = roles; + Q_ASSERT(!start.isEmpty()); + Q_ASSERT(!end.isEmpty()); + } else { + finalRequests.push_back(curData); + curData = data; + } + } + } + finalRequests.push_back(curData); + //qCDebug(QT_REMOTEOBJECT_MODELS) << "Final requests" << finalRequests; + int rows = 0; + // There is no point to eat more than can chew + for (auto it = finalRequests.rbegin(); it != finalRequests.rend() && size_t(rows) < m_rootItem.children.cacheSize; ++it) { + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "FINAL start=" << it->start << "end=" << it->end << "roles=" << it->roles; + + QRemoteObjectPendingReply reply = replicaRowRequest(it->start, it->end, it->roles); + RowWatcher *watcher = new RowWatcher(it->start, it->end, it->roles, reply); + rows += 1 + it->end.first().row - it->start.first().row; + m_pendingRequests.push_back(watcher); + connect(watcher, &RowWatcher::finished, this, &QAbstractItemModelReplicaImplementation::requestedData); + } +} + +void QAbstractItemModelReplicaImplementation::onModelReset() +{ + if (!m_initDone) + return; + + qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO; + QRemoteObjectPendingCallWatcher *watcher = doModelReset(); + connect(watcher, &QRemoteObjectPendingCallWatcher::finished, this, &QAbstractItemModelReplicaImplementation::handleModelResetDone); +} + +void QAbstractItemModelReplicaImplementation::onHeaderDataChanged(Qt::Orientation orientation, int first, int last) +{ + // TODO clean cache + const int index = orientation == Qt::Horizontal ? 0 : 1; + QList &entries = m_headerData[index]; + for (int i = first; i <= last && i < entries.size(); ++i ) + entries[i].data.clear(); + emit q->headerDataChanged(orientation, first, last); +} + +void QAbstractItemModelReplicaImplementation::fetchPendingHeaderData() +{ + QList roles; + QList sections; + QList orientations; + for (const RequestedHeaderData &data : std::as_const(m_requestedHeaderData)) { + roles.push_back(data.role); + sections.push_back(data.section); + orientations.push_back(data.orientation); + } + QRemoteObjectPendingReply reply = replicaHeaderRequest(orientations, sections, roles); + HeaderWatcher *watcher = new HeaderWatcher(orientations, sections, roles, reply); + connect(watcher, &HeaderWatcher::finished, this, &QAbstractItemModelReplicaImplementation::requestedHeaderData); + m_requestedHeaderData.clear(); + m_pendingRequests.push_back(watcher); +} + +void QAbstractItemModelReplicaImplementation::onLayoutChanged(const QtPrivate::IndexList &parents, + QAbstractItemModel::LayoutChangeHint hint) +{ + QList indexes; + for (const QtPrivate::ModelIndex &parent : std::as_const(parents)) { + const QModelIndex parentIndex = toQModelIndex(QtPrivate::IndexList{parent}, q); + indexes << QPersistentModelIndex(parentIndex); + } + QRemoteObjectPendingCallWatcher *watcher; + auto call = replicaCacheRequest(m_rootItem.children.cacheSize, m_initialFetchRolesHint); + watcher = new QRemoteObjectPendingCallWatcher(call); + m_pendingRequests.push_back(watcher); + connect(watcher, &QRemoteObjectPendingCallWatcher::finished, this, [this, watcher, indexes, hint]() { + Q_ASSERT(watcher->returnValue().canConvert()); + const QSize size = watcher->returnValue().value().size; + + q->layoutAboutToBeChanged(indexes, hint); + m_rootItem.clear(); + if (size.height() > 0) { + m_rootItem.rowCount = size.height(); + m_rootItem.hasChildren = true; + } + + m_rootItem.columnCount = size.width(); + if (m_initialAction == QtRemoteObjects::PrefetchData) { + auto entries = watcher->returnValue().value(); + for (int i = 0; i < entries.data.size(); ++i) + fillCache(entries.data[i], entries.roles); + } + m_pendingRequests.removeAll(watcher); + watcher->deleteLater(); + emit q->layoutChanged(indexes, hint); + }); +} + +static inline QList> listRanges(const QList &list) +{ + QList> result; + if (!list.isEmpty()) { + QPair currentElem = qMakePair(list.first(), list.first()); + const auto end = list.constEnd(); + for (auto it = list.constBegin() + 1; it != end; ++it) { + if (currentElem.first == *it + 1) + currentElem.first = *it; + else if ( currentElem.second == *it -1) + currentElem.second = *it; + else if (currentElem.first <= *it && currentElem.second >= *it) + continue; + else { + result.push_back(currentElem); + currentElem.first = *it; + currentElem.second = *it; + } + } + result.push_back(currentElem); + } + return result; +} + +void QAbstractItemModelReplicaImplementation::requestedHeaderData(QRemoteObjectPendingCallWatcher *qobject) +{ + HeaderWatcher *watcher = static_cast(qobject); + Q_ASSERT(watcher); + + QVariantList data = watcher->returnValue().value(); + Q_ASSERT(watcher->orientations.size() == data.size()); + Q_ASSERT(watcher->sections.size() == data.size()); + Q_ASSERT(watcher->roles.size() == data.size()); + QList horizontalSections; + QList verticalSections; + + for (int i = 0; i < data.size(); ++i) { + if (watcher->orientations[i] == Qt::Horizontal) + horizontalSections.append(watcher->sections[i]); + else + verticalSections.append(watcher->sections[i]); + const int index = watcher->orientations[i] == Qt::Horizontal ? 0 : 1; + const int role = watcher->roles[i]; + QHash &dat = m_headerData[index][watcher->sections[i]].data; + dat[role] = data[i]; + } + QList> horRanges = listRanges(horizontalSections); + QList> verRanges = listRanges(verticalSections); + + for (int i = 0; i < horRanges.size(); ++i) + emit q->headerDataChanged(Qt::Horizontal, horRanges[i].first, horRanges[i].second); + for (int i = 0; i < verRanges.size(); ++i) + emit q->headerDataChanged(Qt::Vertical, verRanges[i].first, verRanges[i].second); + m_pendingRequests.removeAll(watcher); + delete watcher; +} + +/*! + \class QAbstractItemModelReplica + \inmodule QtRemoteObjects + \brief The QAbstractItemModelReplica class serves as a convenience class for + Replicas of Sources based on QAbstractItemModel. + + QAbstractItemModelReplica makes replicating QAbstractItemModels more + efficient by employing caching and pre-fetching. + + \sa QAbstractItemModel +*/ + +/*! + \internal +*/ +QAbstractItemModelReplica::QAbstractItemModelReplica(QAbstractItemModelReplicaImplementation *rep, QtRemoteObjects::InitialAction action, const QList &rolesHint) + : QAbstractItemModel() + , d(rep) +{ + d->m_initialAction = action; + d->m_initialFetchRolesHint = rolesHint; + + rep->setModel(this); + connect(rep, &QAbstractItemModelReplicaImplementation::initialized, d.data(), &QAbstractItemModelReplicaImplementation::init); +} + +/*! + Destroys the instance of QAbstractItemModelReplica. +*/ +QAbstractItemModelReplica::~QAbstractItemModelReplica() +{ +} + +static QVariant findData(const CachedRowEntry &row, const QModelIndex &index, int role, bool *cached = nullptr) +{ + if (index.column() < row.size()) { + const CacheEntry &entry = row[index.column()]; + QHash::ConstIterator it = entry.data.constFind(role); + if (it != entry.data.constEnd()) { + if (cached) + *cached = true; + return it.value(); + } + } + if (cached) + *cached = false; + return QVariant(); +} + +/*! + Returns a pointer to the QItemSelectionModel for the current + QAbstractItemModelReplica. +*/ +QItemSelectionModel* QAbstractItemModelReplica::selectionModel() const +{ + return d->m_selectionModel.data(); +} + +/*! + \reimp +*/ +bool QAbstractItemModelReplica::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (role == Qt::UserRole - 1) { + auto parent = d->cacheData(index); + if (!parent) + return false; + bool ok = true; + auto row = value.toInt(&ok); + if (ok) + parent->ensureChildren(row, row); + return ok; + } + if (!index.isValid()) + return false; + if (index.row() < 0 || index.row() >= rowCount(index.parent())) + return false; + if (index.column() < 0 || index.column() >= columnCount(index.parent())) + return false; + + const QList &availRoles = availableRoles(); + const auto res = std::find(availRoles.begin(), availRoles.end(), role); + if (res == availRoles.end()) { + qCWarning(QT_REMOTEOBJECT_MODELS) << "Tried to setData for index" << index << "on a not supported role" << role; + return false; + } + // sendInvocationRequest to change server side data; + d->replicaSetData(QtPrivate::toModelIndexList(index, this), value, role); + return true; +} + +/*! + Returns the \a role data for the item at \a index if available in cache. + A default-constructed QVariant is returned if the index is invalid, the role + is not one of the available roles, the \l {Replica} is uninitialized or + the data was not available. + If the data was not available in cache it will be requested from the + \l {Source}. + + \sa QAbstractItemModel::data(), hasData(), setData(), isInitialized() +*/ +QVariant QAbstractItemModelReplica::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + QModelRoleData roleData(role); + multiData(index, roleData); + return roleData.data(); +} + +/*! + \reimp +*/ +void QAbstractItemModelReplica::multiData(const QModelIndex &index, + QModelRoleDataSpan roleDataSpan) const +{ + Q_ASSERT(checkIndex(index, CheckIndexOption::IndexIsValid)); + + if (!d->isInitialized()) { + qCDebug(QT_REMOTEOBJECT_MODELS)<<"Data not initialized yet"; + + for (auto &roleData : roleDataSpan) + roleData.clearData(); + return; + } + + QList rolesToFetch; + const auto roles = availableRoles(); + if (auto item = d->cacheData(index); item) { + // If the index is found in cache, try to find the data for each role + for (auto &roleData : roleDataSpan) { + const auto role = roleData.role(); + if (roles.contains(role)) { + bool cached = false; + QVariant result = findData(item->cachedRowEntry, index, role, &cached); + if (cached) { + roleData.setData(std::move(result)); + } else { + roleData.clearData(); + rolesToFetch.push_back(role); + } + } else { + roleData.clearData(); + } + } + } else { + // If the index is not found in cache, schedule all roles for fetching + for (auto &roleData : roleDataSpan) { + const auto role = roleData.role(); + if (roles.contains(role)) + rolesToFetch.push_back(role); + roleData.clearData(); + } + } + + if (rolesToFetch.empty()) + return; + + auto parentItem = d->cacheData(index.parent()); + Q_ASSERT(parentItem); + Q_ASSERT(index.row() < parentItem->rowCount); + const int row = index.row(); + QtPrivate::IndexList parentList = QtPrivate::toModelIndexList(index.parent(), this); + QtPrivate::IndexList start = QtPrivate::IndexList() << parentList << QtPrivate::ModelIndex(row, 0); + QtPrivate::IndexList end = QtPrivate::IndexList() << parentList << QtPrivate::ModelIndex(row, std::max(0, parentItem->columnCount - 1)); + Q_ASSERT(toQModelIndex(start, this).isValid()); + + RequestedData data; + data.start = start; + data.end = end; + data.roles = rolesToFetch; + d->m_requestedData.push_back(data); + qCDebug(QT_REMOTEOBJECT_MODELS) << "FETCH PENDING DATA" << start << end << rolesToFetch; + QMetaObject::invokeMethod(d.data(), "fetchPendingData", Qt::QueuedConnection); +} + +/*! + \reimp +*/ +QModelIndex QAbstractItemModelReplica::parent(const QModelIndex &index) const +{ + if (!index.isValid() || !index.internalPointer()) + return QModelIndex(); + auto parent = static_cast(index.internalPointer()); + Q_ASSERT(parent); + if (parent == &d->m_rootItem) + return QModelIndex(); + if (d->m_activeParents.find(parent) == d->m_activeParents.end() || d->m_activeParents.find(parent->parent) == d->m_activeParents.end()) + return QModelIndex(); + int row = parent->parent->children.find(parent); + Q_ASSERT(row >= 0); + return createIndex(row, 0, parent->parent); +} + +/*! + \reimp +*/ +QModelIndex QAbstractItemModelReplica::index(int row, int column, const QModelIndex &parent) const +{ + auto parentItem = d->cacheData(parent); + if (!parentItem) + return QModelIndex(); + + Q_ASSERT_X((parent.isValid() && parentItem && parentItem != &d->m_rootItem) || (!parent.isValid() && parentItem == &d->m_rootItem), __FUNCTION__, qPrintable(QString(QLatin1String("isValid=%1 equals=%2")).arg(parent.isValid()).arg(parentItem == &d->m_rootItem))); + + // hmpf, following works around a Q_ASSERT-bug in QAbstractItemView::setModel which does just call + // d->model->index(0,0) without checking the range before-hand what triggers our assert in case the + // model is empty when view::setModel is called :-/ So, work around with the following; + if (row < 0 || row >= parentItem->rowCount) + return QModelIndex(); + + if (column < 0 || column >= parentItem->columnCount) + return QModelIndex(); + + if (parentItem != &d->m_rootItem) + parentItem->ensureChildren(row, row); + return createIndex(row, column, reinterpret_cast(parentItem)); +} + +/*! + \reimp +*/ +bool QAbstractItemModelReplica::hasChildren(const QModelIndex &parent) const +{ + auto parentItem = d->cacheData(parent); + if (parent.isValid() && parent.column() != 0) + return false; + else + return parentItem ? parentItem->hasChildren : false; +} + +/*! + \reimp +*/ +int QAbstractItemModelReplica::rowCount(const QModelIndex &parent) const +{ + auto parentItem = d->cacheData(parent); + const bool canHaveChildren = parentItem && parentItem->hasChildren && !parentItem->rowCount && parent.column() == 0; + if (canHaveChildren) { + QtPrivate::IndexList parentList = QtPrivate::toModelIndexList(parent, this); + QRemoteObjectPendingReply reply = d->replicaSizeRequest(parentList); + SizeWatcher *watcher = new SizeWatcher(parentList, reply); + connect(watcher, &SizeWatcher::finished, d.data(), &QAbstractItemModelReplicaImplementation::handleSizeDone); + } else if (parent.column() > 0) { + return 0; + } + + return parentItem ? parentItem->rowCount : 0; +} + +/*! + \reimp +*/ +int QAbstractItemModelReplica::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid() && parent.column() > 0) + return 0; + auto parentItem = d->cacheData(parent); + if (!parentItem) + return 0; + while (parentItem->columnCount < 0 && parentItem->parent) + parentItem = parentItem->parent; + return std::max(0, parentItem->columnCount); +} + +/*! + Returns the data for the given \a role and \a section in the header with the + specified \a orientation. + + If the data is not available it will be requested from the \l {Source}. + + \sa QAbstractItemModel::headerData() +*/ +QVariant QAbstractItemModelReplica::headerData(int section, Qt::Orientation orientation, int role) const +{ + const int index = orientation == Qt::Horizontal ? 0 : 1; + const QList elem = d->m_headerData[index]; + if (section >= elem.size()) + return QVariant(); + + const QHash &dat = elem.at(section).data; + QHash::ConstIterator it = dat.constFind(role); + if (it != dat.constEnd()) + return it.value(); + + RequestedHeaderData data; + data.role = role; + data.section = section; + data.orientation = orientation; + d->m_requestedHeaderData.push_back(data); + QMetaObject::invokeMethod(d.data(), "fetchPendingHeaderData", Qt::QueuedConnection); + return QVariant(); +} + +/*! + \reimp +*/ +Qt::ItemFlags QAbstractItemModelReplica::flags(const QModelIndex &index) const +{ + CacheEntry *entry = d->cacheEntry(index); + return entry ? entry->flags : Qt::NoItemFlags; +} + +/*! + \fn void QAbstractItemModelReplica::initialized() + + The initialized signal is emitted the first time we receive data + from the \l {Source}. + + \sa isInitialized() +*/ + +/*! + Returns \c true if this replica has been initialized with data from the + \l {Source} object. Returns \c false otherwise. + + \sa initialized() +*/ +bool QAbstractItemModelReplica::isInitialized() const +{ + return d->isInitialized(); +} + +/*! + Returns \c true if there exists \a role data for the item at \a index. + Returns \c false in any other case. +*/ +bool QAbstractItemModelReplica::hasData(const QModelIndex &index, int role) const +{ + if (!d->isInitialized() || !index.isValid()) + return false; + auto item = d->cacheData(index); + if (!item) + return false; + bool cached = false; + const CachedRowEntry &entry = item->cachedRowEntry; + QVariant result = findData(entry, index, role, &cached); + Q_UNUSED(result) + return cached; +} + +/*! + Returns the current size of the internal cache. + By default this is set to the value of the \c QTRO_NODES_CACHE_SIZE + environment variable, or a default of \c 1000 if it is invalid or doesn't + exist. + + \sa setRootCacheSize() +*/ +size_t QAbstractItemModelReplica::rootCacheSize() const +{ + return d->m_rootItem.children.cacheSize; +} + +/*! + Sets the size of the internal cache to \a rootCacheSize. + + \sa rootCacheSize() +*/ +void QAbstractItemModelReplica::setRootCacheSize(size_t rootCacheSize) +{ + d->m_rootItem.children.setCacheSize(rootCacheSize); +} + +/*! + Returns a list of available roles. + + \sa QAbstractItemModel +*/ +QList QAbstractItemModelReplica::availableRoles() const +{ + return d->availableRoles(); +} + +/*! + \reimp +*/ +QHash QAbstractItemModelReplica::roleNames() const +{ + return d->roleNames(); +} + +QT_END_NAMESPACE diff --git a/src/remoteobjects/qremoteobjectabstractitemmodelreplica.h b/src/remoteobjects/qremoteobjectabstractitemmodelreplica.h new file mode 100644 index 0000000..80e7a14 --- /dev/null +++ b/src/remoteobjects/qremoteobjectabstractitemmodelreplica.h @@ -0,0 +1,55 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QREMOTEOBJECTS_ABSTRACTITEMMODELREPLICA_H +#define QREMOTEOBJECTS_ABSTRACTITEMMODELREPLICA_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QAbstractItemModelReplicaImplementation; + +class Q_REMOTEOBJECTS_EXPORT QAbstractItemModelReplica : public QAbstractItemModel +{ + Q_OBJECT +public: + ~QAbstractItemModelReplica() override; + + QItemSelectionModel* selectionModel() const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const final; + void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + QModelIndex parent(const QModelIndex & index) const override; + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override; + bool hasChildren(const QModelIndex & parent = QModelIndex()) const override; + int rowCount(const QModelIndex & parent = QModelIndex()) const override; + int columnCount(const QModelIndex & parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QList availableRoles() const; + QHash roleNames() const override; + + bool isInitialized() const; + bool hasData(const QModelIndex &index, int role) const; + + size_t rootCacheSize() const; + void setRootCacheSize(size_t rootCacheSize); + +Q_SIGNALS: + void initialized(); + +private: + explicit QAbstractItemModelReplica(QAbstractItemModelReplicaImplementation *rep, QtRemoteObjects::InitialAction action, const QList &rolesHint); + QScopedPointer d; + friend class QAbstractItemModelReplicaImplementation; + friend class QRemoteObjectNode; +}; + +QT_END_NAMESPACE + +#endif // QREMOTEOBJECTS_ABSTRACTITEMMODELREPLICA_H diff --git a/src/remoteobjects/qremoteobjectabstractitemmodelreplica_p.h b/src/remoteobjects/qremoteobjectabstractitemmodelreplica_p.h new file mode 100644 index 0000000..34c0753 --- /dev/null +++ b/src/remoteobjects/qremoteobjectabstractitemmodelreplica_p.h @@ -0,0 +1,443 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QREMOTEOBJECTS_ABSTRACT_ITEM_REPLICA_P_H +#define QREMOTEOBJECTS_ABSTRACT_ITEM_REPLICA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qremoteobjectabstractitemmodeltypes_p.h" +#include "qremoteobjectabstractitemmodelreplica.h" +#include "qremoteobjectreplica.h" +#include "qremoteobjectpendingcall.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace { + const int DefaultNodesCacheSize = 1000; +} + +struct CacheEntry +{ + QHash data; + Qt::ItemFlags flags; + + explicit CacheEntry() + : flags(Qt::NoItemFlags) + {} +}; + +using CachedRowEntry = QList; + +template +struct LRUCache +{ + typedef std::pair Pair; + std::list cachedItems; + typedef typename std::list::iterator CacheIterator; + std::unordered_map cachedItemsMap; + size_t cacheSize; + + explicit LRUCache() + { + bool ok; + cacheSize = qEnvironmentVariableIntValue("QTRO_NODES_CACHE_SIZE" , &ok); + if (!ok) + cacheSize = DefaultNodesCacheSize; + } + + ~LRUCache() + { + clear(); + } + + inline void cleanCache() + { + Q_ASSERT(cachedItems.size() == cachedItemsMap.size()); + + auto it = cachedItems.rbegin(); + while (cachedItemsMap.size() > cacheSize) { + // Do not trash elements with children + // Workaround QTreeView bugs which caches the children indexes for very long time + while (it->second->hasChildren && it != cachedItems.rend()) + ++it; + + if (it == cachedItems.rend()) + break; + + cachedItemsMap.erase(it->first); + delete it->second; + cachedItems.erase((++it).base()); + } + Q_ASSERT(cachedItems.size() == cachedItemsMap.size()); + } + + void setCacheSize(size_t rootCacheSize) + { + cacheSize = rootCacheSize; + cleanCache(); + cachedItemsMap.reserve(rootCacheSize); + } + + void changeKeys(Key key, Key delta) { + std::vector> changed; + auto it = cachedItemsMap.begin(); + while (it != cachedItemsMap.end()) { + if (it->first >= key) { + changed.emplace_back(it->first + delta, it->second); + it->second->first += delta; + it = cachedItemsMap.erase(it); + } else { + ++it; + } + } + for (auto pair : changed) + cachedItemsMap[pair.first] = pair.second; + } + + void insert(Key key, Value *value) + { + changeKeys(key, 1); + ensure(key, value); + Q_ASSERT(cachedItems.size() == cachedItemsMap.size()); + } + + void ensure(Key key, Value *value) + { + cachedItems.emplace_front(key, value); + cachedItemsMap[key] = cachedItems.begin(); + cleanCache(); + } + + void remove(Key key) + { + auto it = cachedItemsMap.find(key); + if (it != cachedItemsMap.end()) { + delete it->second->second; + cachedItems.erase(it->second); + cachedItemsMap.erase(it); + } + changeKeys(key, -1); + Q_ASSERT(cachedItems.size() == cachedItemsMap.size()); + } + + Value *get(Key key) + { + auto it = cachedItemsMap.find(key); + if (it == cachedItemsMap.end()) + return nullptr; + + // Move the accessed item to front + cachedItems.splice(cachedItems.begin(), cachedItems, it->second); + Q_ASSERT(it->second->first == key); + return it->second->second; + } + + Key find(Value *val) + { + for (auto it = cachedItemsMap.begin(); it != cachedItemsMap.end(); ++it) { + if (it->second->second == val) + return it->first; + } + Q_ASSERT_X(false, __FUNCTION__, "Value not found"); + return Key{}; + } + + bool exists(Value *val) + { + for (const auto &pair : cachedItems) + if (pair.second == val) + return true; + + return false; + } + + bool exists(Key key) + { + return cachedItemsMap.find(key) != cachedItemsMap.end(); + } + + size_t size() + { + return cachedItemsMap.size(); + } + + void clear() + { + for (const auto &pair : cachedItems) + delete pair.second; + cachedItems.clear(); + cachedItemsMap.clear(); + } +}; + +class QAbstractItemModelReplicaImplementation; +struct CacheData +{ + QAbstractItemModelReplicaImplementation *replicaModel; + CacheData *parent; + CachedRowEntry cachedRowEntry; + + bool hasChildren; + LRUCache children; + int columnCount; + int rowCount; + + explicit CacheData(QAbstractItemModelReplicaImplementation *model, CacheData *parentItem = nullptr); + + ~CacheData(); + + void ensureChildren(int start, int end) + { + for (int i = start; i <= end; ++i) + if (!children.exists(i)) + children.ensure(i, new CacheData(replicaModel, this)); + } + + void insertChildren(int start, int end) { + Q_ASSERT_X(start >= 0 && start <= end, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 <= %2")).arg(start).arg(end))); + for (int i = start; i <= end; ++i) { + auto cacheData = new CacheData(replicaModel, this); + cacheData->columnCount = columnCount; + children.insert(i, cacheData); + ++rowCount; + } + if (rowCount) + hasChildren = true; + } + void removeChildren(int start, int end) { + Q_ASSERT_X(start >= 0 && start <= end && end < rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 <= %2 < %3")).arg(start).arg(end).arg(rowCount))); + for (int i = end; i >= start; --i) { + children.remove(i); + --rowCount; + } + hasChildren = rowCount; + } + void clear() { + cachedRowEntry.clear(); + children.clear(); + hasChildren = false; + columnCount = 0; + rowCount = 0; + } +}; + +struct RequestedData +{ + QtPrivate::IndexList start; + QtPrivate::IndexList end; + QList roles; +}; + +struct RequestedHeaderData +{ + int role; + int section; + Qt::Orientation orientation; +}; + +class SizeWatcher : public QRemoteObjectPendingCallWatcher +{ + Q_OBJECT +public: + SizeWatcher(QtPrivate::IndexList _parentList, const QRemoteObjectPendingReply &reply) + : QRemoteObjectPendingCallWatcher(reply), + parentList(_parentList) {} + QtPrivate::IndexList parentList; +}; + +class RowWatcher : public QRemoteObjectPendingCallWatcher +{ + Q_OBJECT +public: + RowWatcher(QtPrivate::IndexList _start, QtPrivate::IndexList _end, QList _roles, const QRemoteObjectPendingReply &reply) + : QRemoteObjectPendingCallWatcher(reply), + start(_start), + end(_end), + roles(_roles) {} + QtPrivate::IndexList start, end; + QList roles; +}; + +class HeaderWatcher : public QRemoteObjectPendingCallWatcher +{ + Q_OBJECT +public: + HeaderWatcher(QList _orientations, QList _sections, QList _roles, const QRemoteObjectPendingReply &reply) + : QRemoteObjectPendingCallWatcher(reply), + orientations(_orientations), + sections(_sections), + roles(_roles) {} + QList orientations; + QList sections, roles; +}; + +class QAbstractItemModelReplicaImplementation : public QRemoteObjectReplica +{ + Q_OBJECT + //TODO Use an input name for the model on the Replica side + Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_TYPE, "ServerModelAdapter") + Q_PROPERTY(QList availableRoles READ availableRoles NOTIFY availableRolesChanged) + Q_PROPERTY(QIntHash roleNames READ roleNames) +public: + QAbstractItemModelReplicaImplementation(); + QAbstractItemModelReplicaImplementation(QRemoteObjectNode *node, const QString &name); + ~QAbstractItemModelReplicaImplementation() override; + void initialize() override; + static void registerMetatypes(); + + inline const QList &availableRoles() const + { + if (m_availableRoles.isEmpty()) + m_availableRoles = propAsVariant(0).value>(); + return m_availableRoles; + } + + QHash roleNames() const + { + QIntHash roles = propAsVariant(1).value(); + return roles; + } + + void setModel(QAbstractItemModelReplica *model); + bool clearCache(const QtPrivate::IndexList &start, const QtPrivate::IndexList &end, const QList &roles); + +Q_SIGNALS: + void availableRolesChanged(); + void dataChanged(QtPrivate::IndexList topLeft, QtPrivate::IndexList bottomRight, QList roles); + void rowsInserted(QtPrivate::IndexList parent, int first, int last); + void rowsRemoved(QtPrivate::IndexList parent, int first, int last); + void rowsMoved(QtPrivate::IndexList parent, int start, int end, QtPrivate::IndexList destination, int row); + void currentChanged(QtPrivate::IndexList current, QtPrivate::IndexList previous); + void modelReset(); + void headerDataChanged(Qt::Orientation,int,int); + void columnsInserted(QtPrivate::IndexList parent, int first, int last); + void layoutChanged(QtPrivate::IndexList parents, QAbstractItemModel::LayoutChangeHint hint); +public Q_SLOTS: + QRemoteObjectPendingReply replicaSizeRequest(QtPrivate::IndexList parentList) + { + static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaSizeRequest(QtPrivate::IndexList)"); + QVariantList __repc_args; + __repc_args << QVariant::fromValue(parentList); + return QRemoteObjectPendingReply(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args)); + } + QRemoteObjectPendingReply replicaRowRequest(QtPrivate::IndexList start, QtPrivate::IndexList end, QList roles) + { + static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaRowRequest(QtPrivate::IndexList,QtPrivate::IndexList,QList)"); + QVariantList __repc_args; + __repc_args << QVariant::fromValue(start) << QVariant::fromValue(end) << QVariant::fromValue(roles); + return QRemoteObjectPendingReply(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args)); + } + QRemoteObjectPendingReply replicaHeaderRequest(QList orientations, QList sections, QList roles) + { + static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaHeaderRequest(QList,QList,QList)"); + QVariantList __repc_args; + __repc_args << QVariant::fromValue(orientations) << QVariant::fromValue(sections) << QVariant::fromValue(roles); + return QRemoteObjectPendingReply(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args)); + } + void replicaSetCurrentIndex(QtPrivate::IndexList index, QItemSelectionModel::SelectionFlags command) + { + static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaSetCurrentIndex(QtPrivate::IndexList,QItemSelectionModel::SelectionFlags)"); + QVariantList __repc_args; + __repc_args << QVariant::fromValue(index) << QVariant::fromValue(command); + send(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args); + } + void replicaSetData(QtPrivate::IndexList index, const QVariant &value, int role) + { + static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaSetData(QtPrivate::IndexList,QVariant,int)"); + QVariantList __repc_args; + __repc_args << QVariant::fromValue(index) << QVariant::fromValue(value) << QVariant::fromValue(role); + send(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args); + } + QRemoteObjectPendingReply replicaCacheRequest(size_t size, QList roles) + { + static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaCacheRequest(size_t,QList)"); + QVariantList __repc_args; + __repc_args << QVariant::fromValue(size) << QVariant::fromValue(roles); + return QRemoteObjectPendingReply(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args)); + } + void onHeaderDataChanged(Qt::Orientation orientation, int first, int last); + void onDataChanged(const QtPrivate::IndexList &start, const QtPrivate::IndexList &end, const QList &roles); + void onRowsInserted(const QtPrivate::IndexList &parent, int start, int end); + void onRowsRemoved(const QtPrivate::IndexList &parent, int start, int end); + void onColumnsInserted(const QtPrivate::IndexList &parent, int start, int end); + void onRowsMoved(QtPrivate::IndexList srcParent, int srcRow, int count, QtPrivate::IndexList destParent, int destRow); + void onCurrentChanged(QtPrivate::IndexList current, QtPrivate::IndexList previous); + void onModelReset(); + void requestedData(QRemoteObjectPendingCallWatcher *); + void requestedHeaderData(QRemoteObjectPendingCallWatcher *); + void init(); + void fetchPendingData(); + void fetchPendingHeaderData(); + void handleInitDone(QRemoteObjectPendingCallWatcher *watcher); + void handleModelResetDone(QRemoteObjectPendingCallWatcher *watcher); + void handleSizeDone(QRemoteObjectPendingCallWatcher *watcher); + void onReplicaCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous); + void fillCache(const QtPrivate::IndexValuePair &pair,const QList &roles); + void onLayoutChanged(const QtPrivate::IndexList &parents, QAbstractItemModel::LayoutChangeHint hint); +public: + QScopedPointer m_selectionModel; + QList m_headerData[2]; + + CacheData m_rootItem; + inline CacheData* cacheData(const QModelIndex &index) const { + if (!index.isValid()) + return const_cast(&m_rootItem); + if (index.internalPointer()) { + auto parent = static_cast(index.internalPointer()); + if (m_activeParents.find(parent) != m_activeParents.end()) + return parent->children.get(index.row()); + } + return nullptr; + } + inline CacheData* cacheData(const QtPrivate::IndexList &index) const { + return cacheData(toQModelIndex(index, q)); + } + inline CacheData* createCacheData(const QtPrivate::IndexList &index) const { + bool ok = false; + auto modelIndex = toQModelIndex(index, q, &ok); + if (!ok) + return nullptr; + cacheData(modelIndex.parent())->ensureChildren(modelIndex.row() , modelIndex.row()); + return cacheData(modelIndex); + } + inline CacheEntry* cacheEntry(const QModelIndex &index) const { + auto data = cacheData(index); + if (!data || index.column() < 0 || index.column() >= data->cachedRowEntry.size()) + return nullptr; + CacheEntry &entry = data->cachedRowEntry[index.column()]; + return &entry; + } + inline CacheEntry* cacheEntry(const QtPrivate::IndexList &index) const { + return cacheEntry(toQModelIndex(index, q)); + } + + QRemoteObjectPendingCallWatcher *doModelReset(); + void initializeModelConnections(); + + bool m_initDone = false; + QList m_requestedData; + QList m_requestedHeaderData; + QList m_pendingRequests; + QAbstractItemModelReplica *q; + mutable QList m_availableRoles; + std::unordered_set m_activeParents; + QtRemoteObjects::InitialAction m_initialAction; + QList m_initialFetchRolesHint; +}; + +QT_END_NAMESPACE + +#endif // QREMOTEOBJECTS_ABSTRACT_ITEM_REPLICA_P_H diff --git a/src/remoteobjects/qremoteobjectabstractitemmodeltypes_p.h b/src/remoteobjects/qremoteobjectabstractitemmodeltypes_p.h new file mode 100644 index 0000000..922ab31 --- /dev/null +++ b/src/remoteobjects/qremoteobjectabstractitemmodeltypes_p.h @@ -0,0 +1,235 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QREMOTEOBJECTS_ABSTRACT_ITEM_MODEL_TYPES_P_H +#define QREMOTEOBJECTS_ABSTRACT_ITEM_MODEL_TYPES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtPrivate { + +struct ModelIndex +{ + ModelIndex() : row(-1), column(-1) {} + ModelIndex(int row_, int column_) + : row(row_) + , column(column_) + {} + + inline bool operator==(const ModelIndex &other) const { return row == other.row && column == other.column; } + inline bool operator!=(const ModelIndex &other) const { return !(*this == other); } + int row; + int column; +}; + +typedef QList IndexList; + +struct IndexValuePair +{ + explicit IndexValuePair(const IndexList index_ = IndexList(), const QVariantList &data_ = QVariantList(), + bool hasChildren_ = false, const Qt::ItemFlags &flags_ = Qt::ItemFlags(), const QSize &size_ = {}) + : index(index_) + , data(data_) + , flags(flags_) + , hasChildren(hasChildren_) + , size(size_) + {} + + inline bool operator==(const IndexValuePair &other) const { return index == other.index && data == other.data && hasChildren == other.hasChildren && flags == other.flags; } + inline bool operator!=(const IndexValuePair &other) const { return !(*this == other); } + + IndexList index; + QVariantList data; + Qt::ItemFlags flags; + bool hasChildren; + QList children; + QSize size; +}; + +struct DataEntries +{ + inline bool operator==(const DataEntries &other) const { return data == other.data; } + inline bool operator!=(const DataEntries &other) const { return !(*this == other); } + + QList data; +}; + +struct MetaAndDataEntries : DataEntries +{ + QList roles; + QSize size; +}; + +inline QDebug operator<<(QDebug stream, const ModelIndex &index) +{ + return stream.nospace() << "ModelIndex[row=" << index.row << ", column=" << index.column << "]"; +} + +inline QDataStream& operator<<(QDataStream &stream, const ModelIndex &index) +{ + return stream << index.row << index.column; +} + +inline QDataStream& operator>>(QDataStream &stream, ModelIndex &index) +{ + return stream >> index.row >> index.column; +} + +inline QDataStream& operator<<(QDataStream &stream, Qt::Orientation orient) +{ + return stream << static_cast(orient); +} + +inline QDataStream& operator>>(QDataStream &stream, Qt::Orientation &orient) +{ + int val; + QDataStream &ret = stream >> val; + orient = static_cast(val); + return ret; +} + +inline QDataStream& operator<<(QDataStream &stream, QItemSelectionModel::SelectionFlags command) +{ + return stream << static_cast(command); +} + +inline QDataStream& operator>>(QDataStream &stream, QItemSelectionModel::SelectionFlags &command) +{ + int val; + QDataStream &ret = stream >> val; + command = static_cast(val); + return ret; +} + +inline QDebug operator<<(QDebug stream, const IndexValuePair &pair) +{ + return stream.nospace() << "IndexValuePair[index=" << pair.index << ", data=" << pair.data << ", hasChildren=" << pair.hasChildren << ", flags=" << pair.flags << "]"; +} + +inline QDataStream& operator<<(QDataStream &stream, const IndexValuePair &pair) +{ + return stream << pair.index << pair.data << pair.hasChildren << static_cast(pair.flags) << pair.children << pair.size; +} + +inline QDataStream& operator>>(QDataStream &stream, IndexValuePair &pair) +{ + int flags; + QDataStream &ret = stream >> pair.index >> pair.data >> pair.hasChildren >> flags >> pair.children >> pair.size; + pair.flags = static_cast(flags); + return ret; +} + +inline QDebug operator<<(QDebug stream, const DataEntries &entries) +{ + return stream.nospace() << "DataEntries[" << entries.data << "]"; +} + +inline QDataStream& operator<<(QDataStream &stream, const DataEntries &entries) +{ + return stream << entries.data; +} + +inline QDataStream& operator>>(QDataStream &stream, DataEntries &entries) +{ + return stream >> entries.data; +} + +inline QDataStream& operator<<(QDataStream &stream, const MetaAndDataEntries &entries) +{ + return stream << entries.data << entries.roles << entries.size; +} + +inline QDataStream& operator>>(QDataStream &stream, MetaAndDataEntries &entries) +{ + return stream >> entries.data >> entries.roles >> entries.size; +} + +inline QString modelIndexToString(const IndexList &list) +{ + QString s; + QDebug(&s) << list; + return s; +} + +inline QString modelIndexToString(const ModelIndex &index) +{ + QString s; + QDebug(&s) << index; + return s; +} + +inline QModelIndex toQModelIndex(const IndexList &list, const QAbstractItemModel *model, bool *ok = nullptr, bool ensureItem = false) +{ + if (ok) + *ok = true; + QModelIndex result; + for (int i = 0; i < list.size(); ++i) { + const ModelIndex &index = list[i]; + if (ensureItem) + const_cast(model)->setData(result, index.row, Qt::UserRole - 1); + + result = model->index(index.row, index.column, result); + if (!result.isValid()) { + if (ok) { + *ok = false; + } else { + qFatal("Internal error: invalid index=%s in indexList=%s", + qPrintable(modelIndexToString(list[i])), qPrintable(modelIndexToString(list))); + } + return QModelIndex(); + } + } + return result; +} + +inline IndexList toModelIndexList(const QModelIndex &index, const QAbstractItemModel *model) +{ + IndexList list; + if (index.isValid()) { + list << ModelIndex(index.row(), index.column()); + for (QModelIndex curIndex = model->parent(index); curIndex.isValid(); curIndex = model->parent(curIndex)) + list.prepend(ModelIndex(curIndex.row(), curIndex.column())); + } + return list; +} + +} // namespace QtPrivate + +QT_END_NAMESPACE + +QT_DECL_METATYPE_EXTERN_TAGGED(QtPrivate::ModelIndex, QtPrivate__ModelIndex, /* not exported */) +QT_DECL_METATYPE_EXTERN_TAGGED(QtPrivate::IndexList, QtPrivate__IndexList, /* not exported */) +QT_DECL_METATYPE_EXTERN_TAGGED(QtPrivate::DataEntries, QtPrivate__DataEntries, /* not exported */) +QT_DECL_METATYPE_EXTERN_TAGGED(QtPrivate::MetaAndDataEntries, QtPrivate__MetaAndDataEntries, + /* not exported */) +QT_DECL_METATYPE_EXTERN_TAGGED(QtPrivate::IndexValuePair, QtPrivate__IndexValuePair, + /* not exported */) +QT_DECL_METATYPE_EXTERN_TAGGED(Qt::Orientation, Qt__Orientation, /* not exported */) +QT_DECL_METATYPE_EXTERN_TAGGED(QItemSelectionModel::SelectionFlags, + QItemSelectionModel__SelectionFlags, + /* not exported */) + +#endif // QREMOTEOBJECTS_ABSTRACT_ITEM_MODEL_TYPES_P_H diff --git a/src/remoteobjects/qremoteobjectcontainers.cpp b/src/remoteobjects/qremoteobjectcontainers.cpp new file mode 100644 index 0000000..9e4cc40 --- /dev/null +++ b/src/remoteobjects/qremoteobjectcontainers.cpp @@ -0,0 +1,143 @@ +// Copyright (C) 2021 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include + +#include "qremoteobjectcontainers_p.h" +#include "qremoteobjectpacket_p.h" + +QT_BEGIN_NAMESPACE + +QDataStream &operator>>(QDataStream &ds, QtROSequentialContainer &p) +{ + QByteArray typeName; + quint32 count; + ds >> typeName; + p.setValueType(typeName); + ds >> count; + p.reserve(count); + QVariant value{p.m_valueType, nullptr}; + for (quint32 i = 0; i < count; i++) { + if (!p.m_valueType.load(ds, value.data())) { + qWarning("QSQ_: unable to load type '%s', returning an empty list.", p.m_valueTypeName.constData()); + p.clear(); + break; + } + p.append(value); + } + return ds; +} + +QDataStream &operator<<(QDataStream &ds, const QtROSequentialContainer &p) +{ + ds << p.m_valueTypeName; + auto pos = ds.device()->pos(); + quint32 count = p.size(); + ds << count; + for (quint32 i = 0; i < count; i++) { + if (!p.m_valueType.save(ds, p.at(i).data())) { + ds.device()->seek(pos); + ds.resetStatus(); + ds << quint32(0); + qWarning("QSQ_: unable to save type '%s'.", p.m_valueTypeName.constData()); + break; + } + } + return ds; +} + +const char *descopedName(QMetaType type) { + auto name = QByteArray::fromRawData(type.name(), qstrlen(type.name())); + int index = name.lastIndexOf(':'); // Returns -1 if not found + return type.name() + index + 1; +} + +QDataStream &operator>>(QDataStream &ds, QtROAssociativeContainer &p) +{ + QByteArray keyTypeName, valueTypeName; + quint32 count; + ds >> keyTypeName; + ds >> valueTypeName; + p.setTypes(keyTypeName, valueTypeName); + ds >> count; + p.m_keys.reserve(count); + auto transferType = p.m_keyType; + if (p.m_keyType.flags().testFlag(QMetaType::IsEnumeration)) + transferType = QRemoteObjectPackets::transferTypeForEnum(p.m_keyType); + QVariant key{transferType, nullptr}; + QVariant value{p.m_valueType, nullptr}; + for (quint32 i = 0; i < count; i++) { + if (!transferType.load(ds, key.data())) { + qWarning("QAS_: unable to load key '%s', returning an empty map.", p.m_keyTypeName.constData()); + p.clear(); + break; + } + if (!p.m_valueType.load(ds, value.data())) { + qWarning("QAS_: unable to load value '%s', returning an empty map.", p.m_valueTypeName.constData()); + p.clear(); + break; + } + if (transferType != p.m_keyType) { + bool isFlag = false; + QVariant enumKey(key); + enumKey.convert(p.m_keyType); + p.m_keys.append(enumKey); + if (auto meta = p.m_keyType.metaObject()) { + int index = meta->indexOfEnumerator(descopedName(p.m_keyType)); + isFlag = meta->enumerator(index).isFlag(); + } + // If multiple flag values are set, toString() returns an empty string + // Thus, for flags, we convert the integer value to a string + if (isFlag) + p.insert(key.toString(), value); + else + p.insert(enumKey.toString(), value); + } else { + p.insert(key.toString(), value); + p.m_keys.append(key); + } + } + return ds; +} + +QDataStream &operator<<(QDataStream &ds, const QtROAssociativeContainer &p) +{ + ds << p.m_keyTypeName; + ds << p.m_valueTypeName; + auto pos = ds.device()->pos(); + quint32 count = p.size(); + ds << count; + QAssociativeIterable map(&p); + QAssociativeIterable::const_iterator iter = map.begin(); + auto transferType = p.m_keyType; + if (p.m_keyType.flags().testFlag(QMetaType::IsEnumeration)) + transferType = QRemoteObjectPackets::transferTypeForEnum(p.m_keyType); + bool keySaved; + for (quint32 i = 0; i < count; i++) { + if (transferType != p.m_keyType) { + QVariant intKey(iter.key()); + intKey.convert(transferType); + keySaved = transferType.save(ds, intKey.data()); + } else { + keySaved = transferType.save(ds, iter.key().data()); + } + if (!keySaved) { + ds.device()->seek(pos); + ds.resetStatus(); + ds << quint32(0); + qWarning("QAS_: unable to save type '%s'.", p.m_valueTypeName.constData()); + break; + } + if (!p.m_valueType.save(ds, iter.value().data())) { + ds.device()->seek(pos); + ds.resetStatus(); + ds << quint32(0); + qWarning("QAS_: unable to save type '%s'.", p.m_valueTypeName.constData()); + break; + } + iter++; + } + return ds; +} + +QT_END_NAMESPACE diff --git a/src/remoteobjects/qremoteobjectcontainers_p.h b/src/remoteobjects/qremoteobjectcontainers_p.h new file mode 100644 index 0000000..c485677 --- /dev/null +++ b/src/remoteobjects/qremoteobjectcontainers_p.h @@ -0,0 +1,85 @@ +// Copyright (C) 2021 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtROSequentialContainer : public QVariantList +{ +public: + QtROSequentialContainer() = default; + QtROSequentialContainer(const QSequentialIterable &lst) + { + m_valueType = lst.metaContainer().valueMetaType(); + reserve(lst.size()); + for (const auto v : lst) + append(v); + } + void setValueType(const QByteArray &valueTypeName) + { + m_valueTypeName = valueTypeName; + m_valueType = QMetaType::fromName(valueTypeName.constData()); + clear(); + } + + QMetaType m_valueType; + QByteArray m_typeName, m_valueTypeName; +}; + +QDataStream &operator>>(QDataStream &ds, QtROSequentialContainer &p); + +QDataStream &operator<<(QDataStream &ds, const QtROSequentialContainer &p); + +class QtROAssociativeContainer : public QVariantMap +{ +public: + QtROAssociativeContainer() = default; + QtROAssociativeContainer(const QAssociativeIterable &map) + { + m_keyType = map.metaContainer().keyMetaType(); + m_valueType = map.metaContainer().mappedMetaType(); + m_keys.reserve(map.size()); + QAssociativeIterable::const_iterator iter = map.begin(); + const QAssociativeIterable::const_iterator end = map.end(); + for ( ; iter != end; ++iter) { + m_keys.append(iter.key()); + insert(iter.key().toString(), iter.value()); + } + } + void setTypes(const QByteArray &keyTypeName, const QByteArray &valueTypeName) + { + m_keyTypeName = keyTypeName; + m_keyType = QMetaType::fromName(keyTypeName.constData()); + m_valueTypeName = valueTypeName; + m_valueType = QMetaType::fromName(valueTypeName.constData()); + clear(); + m_keys.clear(); + } + + QMetaType m_keyType, m_valueType; + QByteArray m_typeName, m_keyTypeName, m_valueTypeName; + QVariantList m_keys; // Map uses QString for key, this doesn't lose information for proxy +}; + +QDataStream &operator>>(QDataStream &ds, QtROAssociativeContainer &p); + +QDataStream &operator<<(QDataStream &ds, const QtROAssociativeContainer &p); + +QT_END_NAMESPACE diff --git a/src/remoteobjects/qremoteobjectdynamicreplica.cpp b/src/remoteobjects/qremoteobjectdynamicreplica.cpp new file mode 100644 index 0000000..7c6bcbc --- /dev/null +++ b/src/remoteobjects/qremoteobjectdynamicreplica.cpp @@ -0,0 +1,180 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qremoteobjectdynamicreplica.h" +#include "qremoteobjectreplica_p.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QRemoteObjectDynamicReplica + \inmodule QtRemoteObjects + \brief A dynamically instantiated \l {Replica}. + + There are generated replicas (replicas having the header files produced by + the \l {repc} {Replica Compiler}), and dynamic replicas, that are generated + on-the-fly. This is the class for the dynamic type of replica. + + When the connection to the \l {Source} object is made, the initialization + step passes the current property values (see \l {Replica Initialization}). + In a DynamicReplica, the property/signal/slot details are also sent, + allowing the replica object to be created on-the-fly. This can be convenient + in QML or scripting, but has two primary disadvantages. First, the object is + in effect "empty" until it is successfully initialized by the \l {Source}. + Second, in C++, calls must be made using QMetaObject::invokeMethod(), as the + moc generated lookup will not be available. + + This class does not have a public constructor. It can only be instantiated + by using the dynamic QRemoteObjectNode::acquire method. +*/ + +QRemoteObjectDynamicReplica::QRemoteObjectDynamicReplica() + : QRemoteObjectReplica() +{ +} + +QRemoteObjectDynamicReplica::QRemoteObjectDynamicReplica(QRemoteObjectNode *node, const QString &name) + : QRemoteObjectReplica(ConstructWithNode) +{ + initializeNode(node, name); +} + +/*! + Destroys the dynamic replica. + + \sa {Replica Ownership} +*/ +QRemoteObjectDynamicReplica::~QRemoteObjectDynamicReplica() +{ +} + +/*! + \internal + Returns a pointer to the dynamically generated meta-object of this object, or + QRemoteObjectDynamicReplica's metaObject if the object is not initialized. This + function overrides the QObject::metaObject() virtual function to provide the same + functionality for dynamic replicas. + + \sa QObject::metaObject(), {Replica Initialization} +*/ +const QMetaObject* QRemoteObjectDynamicReplica::metaObject() const +{ + auto impl = qSharedPointerCast(d_impl); + // Returning nullptr will likely result in a crash if this type is used before the + // definition is received. Note: QRemoteObjectDynamicReplica doesn't include the + // QObject macro, so it's metaobject would resolve to QRemoteObjectReplica::metaObject() + // if we weren't overriding it. + if (!impl->m_metaObject) { + qWarning() << "Dynamic metaobject is not assigned, returning generic Replica metaObject."; + qWarning() << "This may cause issues if used for more than checking the Replica state."; + return QRemoteObjectReplica::metaObject(); + } + + return impl->m_metaObject; +} + +/*! + \internal + This function overrides the QObject::qt_metacast() virtual function to provide the same functionality for dynamic replicas. + + \sa QObject::qt_metacast() +*/ +void *QRemoteObjectDynamicReplica::qt_metacast(const char *name) +{ + if (!name) + return nullptr; + + if (!strcmp(name, "QRemoteObjectDynamicReplica")) + return static_cast(const_cast(this)); + + // not entirely sure that one is needed... TODO: check + auto impl = qSharedPointerCast(d_impl); + if (QString::fromLatin1(name) == impl->m_objectName) + return static_cast(const_cast(this)); + + return QObject::qt_metacast(name); +} + +/*! + \internal + This function overrides the QObject::qt_metacall() virtual function to provide the same functionality for dynamic replicas. + + \sa QObject::qt_metacall() +*/ +int QRemoteObjectDynamicReplica::qt_metacall(QMetaObject::Call call, int id, void **argv) +{ + static const bool debugArgs = qEnvironmentVariableIsSet("QT_REMOTEOBJECT_DEBUG_ARGUMENTS"); + + auto impl = qSharedPointerCast(d_impl); + + int saved_id = id; + id = QRemoteObjectReplica::qt_metacall(call, id, argv); + if (id < 0 || impl->m_metaObject == nullptr) + return id; + + if (call == QMetaObject::ReadProperty || call == QMetaObject::WriteProperty) { + QMetaProperty mp = metaObject()->property(saved_id); + + if (call == QMetaObject::WriteProperty) { + QVariantList args; + if (mp.userType() == QMetaType::QVariant) + args << *reinterpret_cast(argv[0]); + else + args << QVariant(mp.metaType(), argv[0]); + QRemoteObjectReplica::send(QMetaObject::WriteProperty, saved_id, args); + } else { + if (mp.userType() == QMetaType::QVariant) + *reinterpret_cast(argv[0]) = impl->m_propertyStorage[id]; + else { + const QVariant value = propAsVariant(id); + mp.metaType().destruct(argv[0]); + mp.metaType().construct(argv[0], value.data()); + } + } + + id = -1; + } else if (call == QMetaObject::InvokeMetaMethod) { + if (id < impl->m_numSignals) { + qCDebug(QT_REMOTEOBJECT) << "DynamicReplica Activate" << impl->m_metaObject->method(saved_id).methodSignature(); + // signal relay from Source world to Replica + QMetaObject::activate(this, impl->m_metaObject, id, argv); + + } else { + // method relay from Replica to Source + const QMetaMethod mm = impl->m_metaObject->method(saved_id); + const int nParam = mm.parameterCount(); + QVariantList args; + args.reserve(nParam); + for (int i = 0; i < nParam; ++i) { + const auto metaType = mm.parameterMetaType(i); + if (metaType.flags().testFlag(QMetaType::IsEnumeration)) { + auto transferType = QRemoteObjectPackets::transferTypeForEnum(metaType); + args.push_back(QVariant(transferType, argv[i + 1])); + } else + args.push_back(QVariant(metaType, argv[i + 1])); + } + + if (debugArgs) { + qCDebug(QT_REMOTEOBJECT) << "method" << mm.methodSignature() << "invoked - args:" << args; + } else { + qCDebug(QT_REMOTEOBJECT) << "method" << mm.methodSignature() << "invoked"; + } + + if (mm.returnType() == QMetaType::Void) + QRemoteObjectReplica::send(QMetaObject::InvokeMetaMethod, saved_id, args); + else { + QRemoteObjectPendingCall call = QRemoteObjectReplica::sendWithReply(QMetaObject::InvokeMetaMethod, saved_id, args); + if (argv[0]) + *(static_cast(argv[0])) = call; + } + } + + id = -1; + } + + return id; +} + +QT_END_NAMESPACE diff --git a/src/remoteobjects/qremoteobjectdynamicreplica.h b/src/remoteobjects/qremoteobjectdynamicreplica.h new file mode 100644 index 0000000..a159153 --- /dev/null +++ b/src/remoteobjects/qremoteobjectdynamicreplica.h @@ -0,0 +1,29 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QDYNAMICREMOTEOBJECT_H +#define QDYNAMICREMOTEOBJECT_H + +#include + +QT_BEGIN_NAMESPACE + +class Q_REMOTEOBJECTS_EXPORT QRemoteObjectDynamicReplica : public QRemoteObjectReplica +{ +public: + ~QRemoteObjectDynamicReplica() override; + + const QMetaObject *metaObject() const override; + void *qt_metacast(const char *name) override; + int qt_metacall(QMetaObject::Call call, int id, void **argv) override; + +private: + explicit QRemoteObjectDynamicReplica(); + explicit QRemoteObjectDynamicReplica(QRemoteObjectNode *node, const QString &name); + friend class QRemoteObjectNodePrivate; + friend class QRemoteObjectNode; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/remoteobjects/qremoteobjectnode.cpp b/src/remoteobjects/qremoteobjectnode.cpp new file mode 100644 index 0000000..4a2e698 --- /dev/null +++ b/src/remoteobjects/qremoteobjectnode.cpp @@ -0,0 +1,2788 @@ +// Copyright (C) 2017 Ford Motor Company +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "private/qmetaobjectbuilder_p.h" + +#include "qremoteobjectcontainers_p.h" +#include "qremoteobjectnode.h" +#include "qremoteobjectnode_p.h" + +#include "qremoteobjectregistry.h" +#include "qremoteobjectdynamicreplica.h" +#include "qremoteobjectpacket_p.h" +#include "qremoteobjectregistrysource_p.h" +#include "qremoteobjectreplica_p.h" +#include "qremoteobjectsource_p.h" +#include "qremoteobjectabstractitemmodelreplica_p.h" +#include "qremoteobjectabstractitemmodeladapter_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace QtRemoteObjects; +using namespace QRemoteObjectStringLiterals; + +using GadgetType = QVariantList; + +struct ManagedGadgetTypeEntry +{ + GadgetType gadgetType; + QMetaType gadgetMetaType; + QList enumMetaTypes; + std::shared_ptr metaObject; + + void unregisterMetaTypes() + { + QMetaType::unregisterMetaType(gadgetMetaType); + for (auto enumMetaType : enumMetaTypes) + QMetaType::unregisterMetaType(enumMetaType); + } +}; + +static QMutex s_managedTypesMutex; +static QHash s_managedTypes; +static QHash> s_trackedConnections; + +static void GadgetsStaticMetacallFunction(QObject *_o, QMetaObject::Call _c, int _id, void **_a) +{ + if (_c == QMetaObject::ReadProperty) { + GadgetType *_t = reinterpret_cast(_o); + if (_id < _t->size()) { + const auto &prop = _t->at(_id); + prop.metaType().destruct(_a[0]); + prop.metaType().construct(_a[0], prop.constData()); + } + } else if (_c == QMetaObject::WriteProperty) { + GadgetType *_t = reinterpret_cast(_o); + if (_id < _t->size()) { + auto & prop = (*_t)[_id]; + prop = QVariant(prop.metaType(), _a[0]); + } + } +} + +static void GadgetTypedDestructor(const QtPrivate::QMetaTypeInterface *, void *ptr) +{ + reinterpret_cast(ptr)->~GadgetType(); +} + +static void GadgetTypedConstructor(const QtPrivate::QMetaTypeInterface *interface, void *where) +{ + GadgetType *gadget = new(where) GadgetType; + QMutexLocker lock(&s_managedTypesMutex); + auto it = s_managedTypes.find(interface->typeId); + if (it == s_managedTypes.end()) { + delete gadget; + gadget = nullptr; + return; + } + *gadget = it->gadgetType; +} + +static void GadgetTypedCopyConstructor(const QtPrivate::QMetaTypeInterface *, void *where, const void *copy) +{ + new(where) GadgetType(*reinterpret_cast(copy)); +} + +static void GadgetTypedMoveConstructor(const QtPrivate::QMetaTypeInterface *, void *where, void *copy) +{ + new(where) GadgetType(std::move(*reinterpret_cast(copy))); +} + +static bool GadgetEqualsFn(const QtPrivate::QMetaTypeInterface *, const void *a, const void *b) +{ + return *reinterpret_cast(a) == *reinterpret_cast(b); +} + +static void GadgetDebugStreamFn(const QtPrivate::QMetaTypeInterface *, QDebug &dbg, const void *a) +{ + const GadgetType *gadgetProperties = reinterpret_cast(a); + for (const auto &prop : *gadgetProperties) + dbg << prop; +} + +static void GadgetDataStreamOutFn(const QtPrivate::QMetaTypeInterface *, QDataStream &ds, const void *a) +{ + const GadgetType *gadgetProperties = reinterpret_cast(a); + for (const auto &prop : *gadgetProperties) + ds << prop; +} + +static void GadgetDataStreamInFn(const QtPrivate::QMetaTypeInterface *, QDataStream &ds, void *a) +{ + GadgetType *gadgetProperties = reinterpret_cast(a); + for (auto &prop : *gadgetProperties) + ds >> prop; +} + +// Like the Q_GADGET static methods above, we need constructor/destructor methods +// in order to use dynamically defined enums with QVariant or as signal/slot +// parameters (i.e., the queued connection mechanism, which QtRO leverages). +// +// We will need the enum methods to support different sizes when typed scope enum +// support is added, so might as well use that now. +template +static void EnumDestructor(const QtPrivate::QMetaTypeInterface *, void *ptr) +{ + static_cast(ptr)->~T(); +} + +template +static void EnumConstructor(const QtPrivate::QMetaTypeInterface *, void *where) +{ + new(where) T; +} + +template +static void EnumCopyConstructor(const QtPrivate::QMetaTypeInterface *, void *where, const void *copy) +{ + new(where) T(*static_cast(copy)); +} + +template +static void EnumMoveConstructor(const QtPrivate::QMetaTypeInterface *, void *where, void *copy) +{ + new(where) T(std::move(*static_cast(copy))); +} + +// Not used, but keeping these in case we end up with a need for save/load. +template +static void EnumSaveOperator(QDataStream & out, const void *data) +{ + const T value = *static_cast(data); + out << value; +} + +template +static void EnumLoadOperator(QDataStream &in, void *data) +{ + T value = *static_cast(data); + in >> value; +} + +template +static bool EnumEqualsFn(const QtPrivate::QMetaTypeInterface *, const void *a, const void *b) +{ + return *static_cast(a) == *static_cast(b); +} + +template +static bool EnumLessThanFn(const QtPrivate::QMetaTypeInterface *, const void *a, const void *b) +{ + return *static_cast(a) < *static_cast(b); +} + +template +static void EnumDebugStreamFn(const QtPrivate::QMetaTypeInterface *, QDebug &dbg, const void *a) +{ + dbg << *static_cast(a); +} + +template +static void EnumDataStreamOutFn(const QtPrivate::QMetaTypeInterface *, QDataStream &ds, const void *a) +{ + ds << *static_cast(a); +} + +template +static void EnumDataStreamInFn(const QtPrivate::QMetaTypeInterface *, QDataStream &ds, void *a) +{ + ds >> *static_cast(a); +} + +static QString name(const QMetaObject * const mobj) +{ + const int ind = mobj->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE); + return ind >= 0 ? QString::fromLatin1(mobj->classInfo(ind).value()) : QString(); +} + +QString QtRemoteObjects::getTypeNameAndMetaobjectFromClassInfo(const QMetaObject *& meta) { + QString typeName; + const int ind = meta->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE); + if (ind != -1) { //We have an object created from repc or at least with QCLASSINFO defined + typeName = QString::fromLatin1(meta->classInfo(ind).value()); + while (true) { + Q_ASSERT(meta->superClass());//This recurses to QObject, which doesn't have QCLASSINFO_REMOTEOBJECT_TYPE + //At the point superclass doesn't have the same QCLASSINFO_REMOTEOBJECT_TYPE, + //we have the metaobject we should work from + if (ind != meta->superClass()->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE)) + break; + meta = meta->superClass(); + } + } + return typeName; +} + +template +bool map_contains(const QMap &map, const Query &key, typename QMap::const_iterator &result) +{ + const typename QMap::const_iterator it = map.find(key); + if (it == map.end()) + return false; + result = it; + return true; +} + +/*! + \qmltype Node + \instantiates QRemoteObjectNode + \inqmlmodule QtRemoteObjects + \brief A node on a Qt Remote Objects network. + + The Node type provides an entry point to a Qt Remote Objects network. A network + can be as simple as two nodes, or an arbitrarily complex set of processes and devices. + + A Node does not have a url that other nodes can connect to, and thus is able to acquire + replicas only. It is not able to share source objects. + +*/ + +QRemoteObjectNodePrivate::QRemoteObjectNodePrivate() + : QObjectPrivate() + , registry(nullptr) + , retryInterval(250) + , lastError(QRemoteObjectNode::NoError) + , persistedStore(nullptr) +{ } + +QRemoteObjectNodePrivate::~QRemoteObjectNodePrivate() +{ +} + +QRemoteObjectSourceLocations QRemoteObjectNodePrivate::remoteObjectAddresses() const +{ + if (registry) + return registry->sourceLocations(); + return QRemoteObjectSourceLocations(); +} + +QRemoteObjectSourceLocations QRemoteObjectRegistryHostPrivate::remoteObjectAddresses() const +{ + if (registrySource) + return registrySource->sourceLocations(); + return QRemoteObjectSourceLocations(); +} + +/*! + \reimp +*/ +void QRemoteObjectNode::timerEvent(QTimerEvent*) +{ + Q_D(QRemoteObjectNode); + + for (auto it = d->pendingReconnect.begin(), end = d->pendingReconnect.end(); it != end; /*erasing*/) { + const auto &conn = *it; + if (conn->isOpen()) { + it = d->pendingReconnect.erase(it); + } else { + conn->connectToServer(); + ++it; + } + } + + if (d->pendingReconnect.isEmpty()) + d->reconnectTimer.stop(); + + qRODebug(this) << "timerEvent" << d->pendingReconnect.size(); +} + +/*! + \qmlproperty int Node::heartbeatInterval + + Heartbeat interval in ms. + + The heartbeat (only helpful for socket connections) will periodically send a + message to connected nodes to detect whether the connection was disrupted. + Qt Remote Objects will try to reconnect automatically if it detects a dropped + connection. This function can help with that detection since the client will + only detect that the server is unavailable when it tries to send data. + + A value of \c 0 (the default) will disable the heartbeat. +*/ + + +/*! + \property QRemoteObjectNode::heartbeatInterval + \brief Heartbeat interval in ms. + + The heartbeat (only helpful for socket connections) will periodically send a + message to connected nodes to detect whether the connection was disrupted. + Qt Remote Objects will try to reconnect automatically if it detects a dropped + connection. This function can help with that detection since the client will + only detect that the server is unavailable when it tries to send data. + + A value of \c 0 (the default) will disable the heartbeat. +*/ +int QRemoteObjectNode::heartbeatInterval() const +{ + Q_D(const QRemoteObjectNode); + return d->m_heartbeatInterval; +} + +void QRemoteObjectNode::setHeartbeatInterval(int interval) +{ + Q_D(QRemoteObjectNode); + if (d->m_heartbeatInterval == interval) + return; + d->m_heartbeatInterval = interval; + emit heartbeatIntervalChanged(interval); +} + +/*! + \since 5.12 + \typedef QRemoteObjectNode::RemoteObjectSchemaHandler + + Typedef for a std::function method that can take a QUrl input and is + responsible for creating the communications channel between this node and + the node hosting the desired \l Source. As some types of QIODevices (e.g., + QSslSocket) require additional steps before the device is ready for use, + the method is responsible for calling \l addClientSideConnection once the + connection is fully established. +*/ + +/*! + \since 5.12 + \brief Provide a custom method to handle externally provided schemas + + This method is tied to the \l Registry and \l {External Schemas}. By + registering a std::function handler for an external schema, the registered + method will be called when the registry is notified of a \l Source you've + acquired being available. Without this registration, QtRO would only be + able to handle the "built-in" schemas. + + The provided method, \a handler, will be called when the registry sees a \l + Source object on a new (not yet connected) Node with a {QUrl::schema()} of + \a schema. The \a handler, of type \l + QRemoteObjectNode::RemoteObjectSchemaHandler will get the \l QUrl of the + Node providing the \l Source as an input parameter, and is responsible for + establishing the communications channel (a \l QIODevice of some sort) and + calling \l addClientSideConnection with it. + + \sa RemoteObjectSchemaHandler +*/ +void QRemoteObjectNode::registerExternalSchema(const QString &schema, QRemoteObjectNode::RemoteObjectSchemaHandler handler) +{ + Q_D(QRemoteObjectNode); + d->schemaHandlers.insert(schema, handler); +} + +/*! + \since 5.11 + \brief Forward Remote Objects from another network + + The proxy functionality is useful when you want to share \l Source objects + over multiple networks. For instance, if you have an embedded target using + target-only connections (like local) and you want to make some of those + same objects available externally. + + As a concrete example, say you have a set of processes talking to each + other on your target hardware using a registry, with the \l Registry at + "local:registry" and separate processes using a node at "local:MyHost" that + holds \l Source objects. If you wanted to access these objects, but over + tcp, you could create a new proxyNode like so: + +\code + // myInternalHost is a node only visible on the device... + QRemoteObjectHost myInternalHost("local:MyHost"); + myInternalHost.enableRemoting(&someObject); + + // Regular host node, listening on port 12123, so visible to other + // devices + QRemoteObjectHost proxyNode("tcp://localhost:12123"); + + // Enable proxying objects from nodes on the local machine's internal + // QtRO bus + proxyNode.proxy("local:registry"); +\endcode + + And from another device you create another node: + +\code + // NB: localhost resolves to a different ip address than proxyNode + QRemoteObjectHost nodeOnRemoteDevice("tcp://localhost:23234"); + + // Connect to the target's proxyNode directly, or use a tcp registry... + nodeOnRemoteDevice.connectToNode("tcp://:12123"); + + // Because of the proxy, we can get the object over tcp/ip port 12123, + // even though we can't connect directly to "local:MyHost" + SomeObject *so = nodeOnRemoteDevice.acquire(); +\endcode + + This would (internally) create a node in proxyNode, which (again + internally/automatically) connects to the provided registry (given by the + \a registryUrl parameter, "local:registry" in this example). Whenever + local:registry emits the \l remoteObjectAdded signal, the + \c QRemoteObjectSourceLocation is passed to the \a filter given to the proxy + call. If this method returns true (the default filter simply returns true + without any filtering), the object is acquired() from the internal node and + enableRemoting() (once the replica is initialized) is called on proxyNode. + + If a \a hostUrl is provided (which is required to enable reverseProxy, but + not needed otherwise), the internal node will be a \l QRemoteObjectHost node + configured with the provided address. If no \a hostUrl is provided, the + internal node will be a QRemoteObjectNode (not HostNode). + + Returns \c true if the object is acquired from the internal node. + + \sa reverseProxy() +*/ +bool QRemoteObjectHostBase::proxy(const QUrl ®istryUrl, const QUrl &hostUrl, RemoteObjectNameFilter filter) +{ + Q_D(QRemoteObjectHostBase); + if (!registryUrl.isValid() || !QtROClientFactory::instance()->isValid(registryUrl)) { + qROWarning(this) << "Can't proxy to registryUrl (invalid url or schema)" << registryUrl; + return false; + } + + if (!hostUrl.isEmpty() && !QtROClientFactory::instance()->isValid(hostUrl)) { + qROWarning(this) << "Can't proxy using hostUrl (invalid schema)" << hostUrl; + return false; + } + + if (d->proxyInfo) { + qROWarning(this) << "Proxying from multiple objects is currently not supported."; + return false; + } + + QRemoteObjectNode *node; + if (hostUrl.isEmpty()) { + node = new QRemoteObjectNode(registryUrl); + } else { + node = new QRemoteObjectHost(hostUrl, registryUrl); + } + d->proxyInfo = new ProxyInfo(node, this, filter); + return true; +} + +/*! + \since 5.11 + \brief Forwards remote objects to another network. + + The reverseProxy() function allows the \l proxy() functionality to be + extended, in effect mirroring the proxy functionality in the "reverse" + direction. These are distinct, because node communication is not symmetric, + one side calls enableRemoting() with a \l Source object, the other side + calls acquire() to get a \l Replica. Using \l proxy() allows you to + "observe" objects on a target device remotely via acquire, but it does not + allow off-target \l Source objects to be acquired from the device's local:* + network. That is where \l reverseProxy() comes in. If a proxyNode is + created like so: + +\code + // myInternalHost is a node only visible on the device... + QRemoteObjectHost myInternalHost("local:MyHost", "local:registry"); + + // RegistryHost node, listening on port 12123, so visible to other + // devices. The node must be a RegistryHost, so the Sources on + // the "outside" network can be forwarded to the inner network. + QRemoteObjectRegistryHost proxyNode("tcp://0.0.0.0:12123"); + + // Enable proxying objects from nodes on the local machine's internal + // QtRO bus. Note the hostUrl parameter is now needed. + proxyNode.proxy("local:registry", "local:fromProxy"); + proxyNode.reverseProxy(); +\endcode + + And from another device you create another node: + +\code + // Listen on a local port, and connect to "proxyNode" as the registry. + // NB: localhost resolves to a different ip address than proxyNode + QRemoteObjectHost nodeOnRemoteDevice("tcp://localhost:23234", + "tcp://:12123"); + + // Because of the reverseProxy, we can expose objects on this device + // and they will make their way to proxyNode... + nodeOnRemoteDevice.enableRemoting(&otherObject); +\endcode + +\code + // Acquire() can now see the objects on other devices through proxyNode, + // due to the reverseProxy call. + OtherObject *oo = myInternalHost.acquire(); +\endcode + + While the \l proxy() functionality allows \l Source objects on another + network to be acquired(), reverseProxy() allows \l Source objects to be + "pushed" to an otherwise inaccessible network. + + \note proxy() needs to be called before \l reverseProxy(), and a + hostUrl needs to be provided to \l proxy for \l reverseProxy() to work. The + \l reverseProxy() method allows a separate \a filter to be applied. This + reverseProxy specific filter will receive notifications of new \l Source + objects on \c proxyNode and acquire them on the internal node if they pass the + \a filter. + + Returns \c true on success, \c false otherwise. + + \note Currently the reverse proxy functionality is supported only for + QRemoteObjectRegistryHost. Calling this method on a QRemoteObjectHost node + will always return \c false. + + \sa proxy() +*/ +bool QRemoteObjectHostBase::reverseProxy(QRemoteObjectHostBase::RemoteObjectNameFilter filter) +{ + Q_D(QRemoteObjectHostBase); + + if (!d->proxyInfo) { + qROWarning(this) << "proxy() needs to be called before setting up reverse proxy."; + return false; + } + + QRemoteObjectHost *host = qobject_cast(d->proxyInfo->proxyNode); + if (!host) { + qROWarning(this) << "proxy() needs called with host-url to enable reverse proxy."; + return false; + } + + return d->proxyInfo->setReverseProxy(filter); +} + +/*! + \internal The replica needs to have a default constructor to be able + to create a replica from QML. In order for it to be properly + constructed, there needs to be a way to associate the replica with a + node and start the replica initialization. Thus we need a public + method on node to facilitate that. That's initializeReplica. +*/ +void QRemoteObjectNode::initializeReplica(QRemoteObjectReplica *instance, const QString &name) +{ + Q_D(QRemoteObjectNode); + if (instance->inherits("QRemoteObjectDynamicReplica")) { + d->setReplicaImplementation(nullptr, instance, name); + } else { + const QMetaObject *meta = instance->metaObject(); + // This is a templated acquire, so we tell the Source we don't need + // them to send the class definition. Thus we need to store the + // metaObject for this class - if this is a nested class, the QObject + // could be a nullptr or updated from the source, + d->dynamicTypeManager.addFromMetaObject(meta); + d->setReplicaImplementation(meta, instance, name.isEmpty() ? ::name(meta) : name); + } +} + +void QRemoteObjectNodePrivate::setLastError(QRemoteObjectNode::ErrorCode errorCode) +{ + Q_Q(QRemoteObjectNode); + lastError = errorCode; + emit q->error(lastError); +} + +void QRemoteObjectNodePrivate::setReplicaImplementation(const QMetaObject *meta, QRemoteObjectReplica *instance, const QString &name) +{ + qROPrivDebug() << "Starting setReplicaImplementation for" << name; + openConnectionIfNeeded(name); + QMutexLocker locker(&mutex); + if (hasInstance(name)) { + qCDebug(QT_REMOTEOBJECT)<<"setReplicaImplementation - using existing instance"; + QSharedPointer rep = qSharedPointerCast(replicas.value(name).toStrongRef()); + Q_ASSERT(rep); + instance->d_impl = rep; + rep->configurePrivate(instance); + } else { + instance->d_impl.reset(handleNewAcquire(meta, instance, name)); + instance->initialize(); + replicas.insert(name, instance->d_impl.toWeakRef()); + qROPrivDebug() << "setReplicaImplementation - Created new instance" << name<registry; +} + +/*! + \class QRemoteObjectAbstractPersistedStore + \inmodule QtRemoteObjects + \brief A class which provides the methods for setting PROP values of a + replica to value they had the last time the replica was used. + + This can be used to provide a "reasonable" value to be displayed until the + connection to the source is established and current values are available. + + This class must be overridden to provide an implementation for saving (\l + QRemoteObjectAbstractPersistedStore::saveProperties) and restoring (\l + QRemoteObjectAbstractPersistedStore::restoreProperties) PROP values. The derived + type can then be set for a node, and any replica acquired from that node + will then automatically store PERSISTED properties when the replica + destructor is called, and retrieve the values when the replica is + instantiated. +*/ + +/*! + Constructs a QRemoteObjectAbstractPersistedStore with the given \a parent. + The default value of \a parent is \nullptr. +*/ +QRemoteObjectAbstractPersistedStore::QRemoteObjectAbstractPersistedStore(QObject *parent) + : QObject(parent) +{ +} + +QRemoteObjectAbstractPersistedStore::~QRemoteObjectAbstractPersistedStore() +{ +} + +/*! + \fn virtual void QRemoteObjectAbstractPersistedStore::saveProperties(const QString &repName, const QByteArray &repSig, const QVariantList &values) + + This method will be provided the replica class's \a repName, \a repSig and the list of + \a values that PERSISTED properties have when the replica destructor was + called. It is the responsibility of the inheriting class to store the + information in a manner consistent for \l + QRemoteObjectAbstractPersistedStore::restoreProperties to retrieve. + + \sa QRemoteObjectAbstractPersistedStore::restoreProperties +*/ + +/*! + \fn virtual QVariantList QRemoteObjectAbstractPersistedStore::restoreProperties(const QString &repName, const QByteArray &repSig) + + This method will be provided the replica class's \a repName and \a repSig when the + replica is being initialized. It is the responsibility of the inheriting + class to get the last values persisted by \l + QRemoteObjectAbstractPersistedStore::saveProperties and return them. An empty + QVariantList should be returned if no values are available. + + \sa QRemoteObjectAbstractPersistedStore::saveProperties +*/ + + +QRemoteObjectAbstractPersistedStore *QRemoteObjectNode::persistedStore() const +{ + Q_D(const QRemoteObjectNode); + return d->persistedStore; +} + +/*! + \qmlproperty QRemoteObjectAbstractPersistedStore Node::persistedStore + + Allows setting a \l QRemoteObjectAbstractPersistedStore instance for the node. + + Allows replica \l PROP members with the PERSISTED trait to save their current value when the + replica is deleted and restore a stored value the next time the replica is started. + + Requires a \l QRemoteObjectAbstractPersistedStore class implementation to control where and how + persistence is handled. A default QSettings-based implementation is provided by SettingsStore. +*/ + +/*! + \since 5.11 + \property QRemoteObjectNode::persistedStore + \brief Allows setting a \l QRemoteObjectAbstractPersistedStore instance for the node. + + Allows replica \l PROP members with the PERSISTED trait to save their current value when the + replica is deleted and restore a stored value the next time the replica is started. + + Requires a \l QRemoteObjectAbstractPersistedStore class implementation to control where and how + persistence is handled. +*/ +void QRemoteObjectNode::setPersistedStore(QRemoteObjectAbstractPersistedStore *persistedStore) +{ + Q_D(QRemoteObjectNode); + d->persistedStore = persistedStore; +} + +QRemoteObjectAbstractPersistedStore::QRemoteObjectAbstractPersistedStore(QRemoteObjectAbstractPersistedStorePrivate &dptr, QObject *parent) + : QObject(dptr, parent) +{ +} + +QRemoteObjectAbstractPersistedStorePrivate::QRemoteObjectAbstractPersistedStorePrivate() +{ +} + +QRemoteObjectAbstractPersistedStorePrivate::~QRemoteObjectAbstractPersistedStorePrivate() +{ +} + +QRemoteObjectMetaObjectManager::~QRemoteObjectMetaObjectManager() +{ + for (QMetaObject *mo : dynamicTypes) { + for (auto metaType : enumTypes[mo]) + QMetaType::unregisterMetaType(metaType); + enumTypes.remove(mo); + free(mo); //QMetaObjectBuilder uses malloc, not new + } +} + +const QMetaObject *QRemoteObjectMetaObjectManager::metaObjectForType(const QString &type) +{ + qCDebug(QT_REMOTEOBJECT) << "metaObjectForType: looking for" << type << "static keys:" << staticTypes.keys() << "dynamic keys:" << dynamicTypes.keys(); + Q_ASSERT(staticTypes.contains(type) || dynamicTypes.contains(type)); + auto it = staticTypes.constFind(type); + if (it != staticTypes.constEnd()) + return it.value(); + return dynamicTypes.value(type); +} + +static void trackConnection(int typeId, QtROIoDeviceBase *connection) +{ + QMutexLocker lock(&s_managedTypesMutex); + if (s_trackedConnections[typeId].contains(connection)) + return; + s_trackedConnections[typeId].insert(connection); + auto unregisterIfNotUsed = [typeId, connection]{ + QMutexLocker lock(&s_managedTypesMutex); + Q_ASSERT(s_trackedConnections.contains(typeId)); + Q_ASSERT(s_trackedConnections[typeId].contains(connection)); + s_trackedConnections[typeId].remove(connection); + if (s_trackedConnections[typeId].isEmpty()) { + s_trackedConnections.remove(typeId); + s_managedTypes[typeId].unregisterMetaTypes(); + s_managedTypes.remove(typeId); // Destroys the meta types + } + }; + + // Unregister the type only when the connection is destroyed + // Do not unregister types when the connections is discconected, because + // if it gets reconnected it will not register the types again + QObject::connect(connection, &QtROIoDeviceBase::destroyed, unregisterIfNotUsed); +} + +struct EnumPair { + QByteArray name; + int value; +}; + +struct EnumData { + QByteArray name; + bool isFlag, isScoped; + quint32 keyCount, size; + QList values; +}; + +struct GadgetProperty { + QByteArray name; + QByteArray type; +}; + +struct GadgetData { + QList properties; + QList enums; +}; + +static const char *strDup(const QByteArray &s) +{ + auto result = new char[uint(s.size()) + 1]; + auto end = std::copy(s.cbegin(), s.cend(), result); + *end = 0; + return result; +} + +using Gadgets = QHash; +struct TypeInfo : public QtPrivate::QMetaTypeInterface +{ + const QMetaObject *metaObject; +}; +static const QMetaObject *metaObjectFn(const QtPrivate::QMetaTypeInterface *self) +{ + return static_cast(self)->metaObject; +} + +template +static TypeInfo *enumMetaType(const QByteArray &name, uint size, const QMetaObject *meta=nullptr) +{ + static const auto flags = QMetaType::IsEnumeration | QMetaType::NeedsConstruction + | QMetaType::NeedsDestruction; + + auto typeInfo = new TypeInfo { + { + 0, alignof(Int), size, uint(flags), 0, metaObjectFn, strDup(name), + EnumConstructor, + EnumCopyConstructor, + EnumMoveConstructor, + EnumDestructor, + EnumEqualsFn, + EnumLessThanFn, + EnumDebugStreamFn, + EnumDataStreamOutFn, + EnumDataStreamInFn, + nullptr + }, meta + }; + return typeInfo; +} + +static TypeInfo *registerEnum(const QByteArray &name, uint size=4u) +{ + TypeInfo *result = nullptr; + if (QMetaType::fromName(name).isValid()) + return result; + switch (size) { + case 1: + result = enumMetaType(name, size); + break; + case 2: + result = enumMetaType(name, size); + break; + case 4: + result = enumMetaType(name, size); + break; + // Qt currently only supports enum values of 4 or less bytes (QMetaEnum value(index) returns int) +// case 8: id = QMetaType::registerType(name.constData(), nullptr, nullptr, &EnumDestructor, +// &EnumConstructor, size, flags, meta); +// break; + default: + qWarning() << "Invalid enum detected" << name << "with size" << size << ". Defaulting to register as int."; + size = 4; + result = enumMetaType(name, size); + break; + } +#ifdef QTRO_VERBOSE_PROTOCOL + qDebug() << "Registering new enum" << name << "size:" << size; +#endif + return result; +} + +static int registerGadgets(QtROIoDeviceBase *connection, Gadgets &gadgets, QByteArray typeName) +{ + const auto &gadget = gadgets.take(typeName); + // TODO Look at having registerGadgets return QMetaType index of the id of the type + int typeId = QMetaType::fromName(typeName).id(); + if (typeId != QMetaType::UnknownType) { + trackConnection(typeId, connection); + return typeId; + } + + ManagedGadgetTypeEntry entry; + + QMetaObjectBuilder gadgetBuilder; + gadgetBuilder.setClassName(typeName); + gadgetBuilder.setFlags(DynamicMetaObject | PropertyAccessInStaticMetaCall); + for (const auto &prop : gadget.properties) { + int propertyType = QMetaType::fromName(prop.type).id(); + if (!propertyType && gadgets.contains(prop.type)) + propertyType = registerGadgets(connection, gadgets, prop.type); + entry.gadgetType.push_back(QVariant(QMetaType(propertyType))); + auto dynamicProperty = gadgetBuilder.addProperty(prop.name, prop.type); + dynamicProperty.setWritable(true); + dynamicProperty.setReadable(true); + } + QList enumsToBeAssignedMetaObject; + enumsToBeAssignedMetaObject.reserve(gadget.enums.size()); + for (const auto &enumData: gadget.enums) { + auto enumBuilder = gadgetBuilder.addEnumerator(enumData.name); + enumBuilder.setIsFlag(enumData.isFlag); + enumBuilder.setIsScoped(enumData.isScoped); + + for (quint32 k = 0; k < enumData.keyCount; ++k) { + const auto pair = enumData.values.at(k); + enumBuilder.addKey(pair.name, pair.value); + } + const QByteArray registeredName = QByteArray(typeName).append("::").append(enumData.name); + auto typeInfo = registerEnum(registeredName, enumData.size); + if (typeInfo) + enumsToBeAssignedMetaObject.append(typeInfo); + } + auto meta = gadgetBuilder.toMetaObject(); + entry.metaObject = std::shared_ptr{meta, [](QMetaObject *ptr){ ::free(ptr); }}; + for (auto typeInfo : enumsToBeAssignedMetaObject) { + typeInfo->metaObject = meta; + auto metaType = QMetaType(typeInfo); + entry.enumMetaTypes.append(metaType); + auto id = metaType.id(); + qCDebug(QT_REMOTEOBJECT) << "Registering new gadget enum with id" << id << typeInfo->name << "size:" << typeInfo->size; + } + + QMetaType::TypeFlags flags = QMetaType::IsGadget; + if (meta->propertyCount()) { + meta->d.static_metacall = &GadgetsStaticMetacallFunction; + meta->d.superdata = nullptr; + flags |= QMetaType::NeedsConstruction | QMetaType::NeedsDestruction; + auto typeInfo = new TypeInfo { + { + 0, alignof(GadgetType), sizeof(GadgetType), uint(flags), 0, metaObjectFn, + strDup(typeName), + GadgetTypedConstructor, + GadgetTypedCopyConstructor, + GadgetTypedMoveConstructor, + GadgetTypedDestructor, + GadgetEqualsFn, + nullptr, /* LessThanFn */ + GadgetDebugStreamFn, + GadgetDataStreamOutFn, + GadgetDataStreamInFn, + nullptr /* LegacyRegisterOp */ + }, + meta + }; + entry.gadgetMetaType = QMetaType(typeInfo); + } else { + auto typeInfo = new TypeInfo { + { + 0, alignof(GadgetType), sizeof(GadgetType), uint(flags), 0, metaObjectFn, + strDup(typeName), + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + }, + meta + }; + entry.gadgetMetaType = QMetaType(typeInfo); + } + const int gadgetTypeId = entry.gadgetMetaType.id(); + trackConnection(gadgetTypeId, connection); + QMutexLocker lock(&s_managedTypesMutex); + s_managedTypes.insert(gadgetTypeId, entry); + return gadgetTypeId; +} + +static void registerAllGadgets(QtROIoDeviceBase *connection, Gadgets &gadgets) +{ + while (!gadgets.isEmpty()) + registerGadgets(connection, gadgets, gadgets.constBegin().key()); +} + +static void deserializeEnum(QDataStream &ds, EnumData &enumData) +{ + ds >> enumData.name; + ds >> enumData.isFlag; + ds >> enumData.isScoped; + ds >> enumData.size; + ds >> enumData.keyCount; + for (quint32 i = 0; i < enumData.keyCount; i++) { + EnumPair pair; + ds >> pair.name; + ds >> pair.value; + enumData.values.push_back(pair); + } +} + +static void parseGadgets(QtROIoDeviceBase *connection, QDataStream &in) +{ + quint32 qtEnums, numGadgets; + in >> qtEnums; // Qt enums - just need registration + for (quint32 i = 0; i < qtEnums; ++i) { + QByteArray enumName; + in >> enumName; + QMetaType type(enumMetaType(enumName, 4, &Qt::staticMetaObject)); + type.id(); + } + in >> numGadgets; + if (numGadgets == 0) + return; + Gadgets gadgets; + for (quint32 i = 0; i < numGadgets; ++i) { + QByteArray type; + in >> type; + quint32 numProperties, numEnums; + in >> numProperties; + auto &properties = gadgets[type].properties; + for (quint32 p = 0; p < numProperties; ++p) { + GadgetProperty prop; + in >> prop.name; + in >> prop.type; + properties.push_back(prop); + } + in >> numEnums; + auto &enums = gadgets[type].enums; + for (quint32 e = 0; e < numEnums; ++e) { + EnumData enumData; + deserializeEnum(in, enumData); + enums.push_back(enumData); + } + } + registerAllGadgets(connection, gadgets); +} + +QMetaObject *QRemoteObjectMetaObjectManager::addDynamicType(QtROIoDeviceBase *connection, QDataStream &in) +{ + QMetaObjectBuilder builder; + builder.setSuperClass(&QRemoteObjectReplica::staticMetaObject); + builder.setFlags(DynamicMetaObject); + + QString typeString; + QByteArray type; + quint32 numEnums = 0; + quint32 numSignals = 0; + quint32 numMethods = 0; + quint32 numProperties = 0; + QHash classEnums; + + in >> typeString; + type = typeString.toLatin1(); + builder.addClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE, type); + builder.setClassName(type); + + in >> numEnums; + QList enumSizes(numEnums); + enumsToBeAssignedMetaObject.reserve(numEnums); + for (quint32 i = 0; i < numEnums; ++i) { + EnumData enumData; + deserializeEnum(in, enumData); + auto enumBuilder = builder.addEnumerator(enumData.name); + enumBuilder.setIsFlag(enumData.isFlag); + enumBuilder.setIsScoped(enumData.isScoped); + enumSizes[i] = enumData.size; + + for (quint32 k = 0; k < enumData.keyCount; ++k) { + const auto pair = enumData.values.at(k); + enumBuilder.addKey(pair.name, pair.value); + } + const QByteArray registeredName = QByteArray(type).append("::").append(enumData.name); + classEnums[enumData.name] = registeredName; + auto typeInfo = registerEnum(registeredName, enumData.size); + if (typeInfo) { + enumsToBeAssignedMetaObject[typeInfo] = QMetaType(typeInfo); + int id = enumsToBeAssignedMetaObject[typeInfo].id(); + qCDebug(QT_REMOTEOBJECT) << "Registering new class enum with id" << id << typeInfo->name << "size:" << typeInfo->size; + } + } + parseGadgets(connection, in); + + int curIndex = 0; + + in >> numSignals; + for (quint32 i = 0; i < numSignals; ++i) { + QByteArray signature; + QByteArrayList paramNames; + in >> signature; + in >> paramNames; + ++curIndex; + auto mmb = builder.addSignal(signature); + mmb.setParameterNames(paramNames); + } + + in >> numMethods; + for (quint32 i = 0; i < numMethods; ++i) { + QByteArray signature, returnType; + QByteArrayList paramNames; + in >> signature; + in >> returnType; + in >> paramNames; + ++curIndex; + const bool isVoid = returnType.isEmpty() || returnType == QByteArrayLiteral("void"); + QMetaMethodBuilder mmb; + if (isVoid) + mmb = builder.addMethod(signature); + else + mmb = builder.addMethod(signature, QByteArrayLiteral("QRemoteObjectPendingCall")); + mmb.setParameterNames(paramNames); + } + + in >> numProperties; + + for (quint32 i = 0; i < numProperties; ++i) { + QByteArray name; + QByteArray typeName; + QByteArray signalName; + in >> name; + in >> typeName; + in >> signalName; + + auto choppedName = QByteArray::fromRawData(typeName.constData(), + typeName.size() - 1); // Remove trailing null + // The typeName for class enums is qualified with the class name. + // Need to remove the class name before checking if it's a class enum. + if (auto idx = choppedName.indexOf("::"); idx >= 0) { + choppedName = choppedName.sliced(idx + 2); + if (classEnums.contains(choppedName)) + typeName = classEnums[choppedName] + '\0'; // Update to the enum's registered name + } + + if (signalName.isEmpty()) + builder.addProperty(name, typeName); + else + builder.addProperty(name, typeName, builder.indexOfSignal(signalName)); + } + + auto meta = builder.toMetaObject(); + for (auto typeInfo : enumsToBeAssignedMetaObject.keys()) { + auto typeInfoWithMetaObject = static_cast(typeInfo); + typeInfoWithMetaObject->metaObject = meta; + enumTypes[meta].append(enumsToBeAssignedMetaObject.take(typeInfo)); + } + dynamicTypes.insert(typeString, meta); + return meta; +} + +void QRemoteObjectMetaObjectManager::addFromMetaObject(const QMetaObject *metaObject) +{ + QString className = QLatin1String(metaObject->className()); + if (!className.endsWith(QLatin1String("Replica"))) + return; + if (className == QLatin1String("QRemoteObjectDynamicReplica") || staticTypes.contains(className)) + return; + className.chop(7); //Remove 'Replica' from name + staticTypes.insert(className, metaObject); +} + +void QRemoteObjectNodePrivate::connectReplica(QObject *object, QRemoteObjectReplica *instance) +{ + int nConnections = 0; + const QMetaObject *us = instance->metaObject(); + const QMetaObject *them = object->metaObject(); + + static const int memberOffset = QRemoteObjectReplica::staticMetaObject.methodCount(); + for (int idx = memberOffset; idx < us->methodCount(); ++idx) { + const QMetaMethod mm = us->method(idx); + + qROPrivDebug() << idx << mm.name(); + if (mm.methodType() != QMetaMethod::Signal) + continue; + + // try to connect to a signal on the parent that has the same method signature + QByteArray sig = QMetaObject::normalizedSignature(mm.methodSignature().constData()); + qROPrivDebug() << sig; + if (them->indexOfSignal(sig.constData()) == -1) + continue; + + sig.prepend(QSIGNAL_CODE + '0'); + const char * const csig = sig.constData(); + const bool res = QObject::connect(object, csig, instance, csig); + Q_UNUSED(res) + ++nConnections; + + qROPrivDebug() << sig << res; + } + + qROPrivDebug() << "# connections =" << nConnections; +} + +void QRemoteObjectNodePrivate::openConnectionIfNeeded(const QString &name) +{ + qROPrivDebug() << Q_FUNC_INFO << name << this; + if (!remoteObjectAddresses().contains(name)) { + qROPrivDebug() << name << "not available - available addresses:" << remoteObjectAddresses(); + return; + } + + if (!initConnection(remoteObjectAddresses().value(name).hostUrl)) + qROPrivWarning() << "failed to open connection to" << name; +} + +bool QRemoteObjectNodePrivate::initConnection(const QUrl &address) +{ + Q_Q(QRemoteObjectNode); + if (requestedUrls.contains(address)) { + qROPrivDebug() << "Connection already requested for " << address.toString(); + return true; + } + + requestedUrls.insert(address); + + if (schemaHandlers.contains(address.scheme())) { + schemaHandlers[address.scheme()](address); + return true; + } + + QtROClientIoDevice *connection = QtROClientFactory::instance()->create(address, q); + if (!connection) { + qROPrivWarning() << "Could not create QtROClientIoDevice for client. Invalid url/scheme provided?" << address; + return false; + } + qROPrivDebug() << "Opening connection to" << address.toString(); + qROPrivDebug() << "Replica Connection isValid" << connection->isOpen(); + QObject::connect(connection, &QtROClientIoDevice::shouldReconnect, q, [this, connection]() { + onShouldReconnect(connection); + }); + QObject::connect(connection, &QtROIoDeviceBase::readyRead, q, [this, connection]() { + onClientRead(connection); + }); + connection->connectToServer(); + + return true; +} + +bool QRemoteObjectNodePrivate::hasInstance(const QString &name) +{ + if (!replicas.contains(name)) + return false; + + QSharedPointer rep = replicas.value(name).toStrongRef(); + if (!rep) { //already deleted + replicas.remove(name); + return false; + } + + return true; +} + +static QDebug operator<<(QDebug debug, + const QHash> &hash) +{ + const QDebugStateSaver saver(debug); + debug.nospace() << "QHash("; + for (auto it = hash.cbegin(); it != hash.cend(); ++it) + debug << '(' << it.key() << ", " << it.value().isNull() << ')'; + debug << ')'; + return debug; +} + +void QRemoteObjectNodePrivate::onRemoteObjectSourceAdded(const QRemoteObjectSourceLocation &entry) +{ + qROPrivDebug() << "onRemoteObjectSourceAdded" << entry << replicas << replicas.contains(entry.first); + if (!entry.first.isEmpty()) { + QRemoteObjectSourceLocations locs = registry->sourceLocations(); + locs[entry.first] = entry.second; + //TODO Is there a way to extend QRemoteObjectSourceLocations in place? + registry->d_impl->setProperty(0, QVariant::fromValue(locs)); + registry->notifySourceLocationsChanged(); + qROPrivDebug() << "onRemoteObjectSourceAdded, now locations =" << locs; + } + if (replicas.contains(entry.first)) //We have a replica waiting on this remoteObject + { + QSharedPointer rep = replicas.value(entry.first).toStrongRef(); + if (!rep) { //replica has been deleted, remove from list + replicas.remove(entry.first); + return; + } + + initConnection(entry.second.hostUrl); + + qROPrivDebug() << "Called initConnection due to new RemoteObjectSource added via registry" << entry.first; + } +} + +void QRemoteObjectNodePrivate::onRemoteObjectSourceRemoved(const QRemoteObjectSourceLocation &entry) +{ + if (!entry.first.isEmpty()) { + QRemoteObjectSourceLocations locs = registry->sourceLocations(); + locs.remove(entry.first); + registry->d_impl->setProperty(0, QVariant::fromValue(locs)); + registry->notifySourceLocationsChanged(); + } +} + +void QRemoteObjectNodePrivate::onRegistryInitialized() +{ + qROPrivDebug() << "Registry Initialized" << remoteObjectAddresses(); + + const auto remotes = remoteObjectAddresses(); + for (auto i = remotes.cbegin(), end = remotes.cend(); i != end; ++i) { + if (replicas.contains(i.key())) //We have a replica waiting on this remoteObject + { + QSharedPointer rep = replicas.value(i.key()).toStrongRef(); + if (rep && !requestedUrls.contains(i.value().hostUrl)) + initConnection(i.value().hostUrl); + else if (!rep) //replica has been deleted, remove from list + replicas.remove(i.key()); + + continue; + } + } +} + +void QRemoteObjectNodePrivate::onShouldReconnect(QtROClientIoDevice *ioDevice) +{ + Q_Q(QRemoteObjectNode); + + const auto remoteObjects = ioDevice->remoteObjects(); + for (const QString &remoteObject : remoteObjects) { + connectedSources.remove(remoteObject); + ioDevice->removeSource(remoteObject); + if (replicas.contains(remoteObject)) { //We have a replica waiting on this remoteObject + QSharedPointer rep = qSharedPointerCast(replicas.value(remoteObject).toStrongRef()); + if (rep && !rep->connectionToSource.isNull()) { + rep->setDisconnected(); + } else if (!rep) { + replicas.remove(remoteObject); + } + } + } + if (requestedUrls.contains(ioDevice->url())) { + // Only try to reconnect to URLs requested via connectToNode + // If we connected via registry, wait for the registry to see the node/source again + pendingReconnect.insert(ioDevice); + if (!reconnectTimer.isActive()) { + reconnectTimer.start(retryInterval, q); + qROPrivDebug() << "Starting reconnect timer"; + } + } else { + qROPrivDebug() << "Url" << ioDevice->url().toDisplayString().toLatin1() + << "lost. We will reconnect Replicas if they reappear on the Registry."; + } +} + +//This version of handleNewAcquire creates a QConnectedReplica. If this is a +//host node, the QRemoteObjectHostBasePrivate overload is called instead. +QReplicaImplementationInterface *QRemoteObjectNodePrivate::handleNewAcquire(const QMetaObject *meta, QRemoteObjectReplica *instance, const QString &name) +{ + Q_Q(QRemoteObjectNode); + QConnectedReplicaImplementation *rp = new QConnectedReplicaImplementation(name, meta, q); + rp->configurePrivate(instance); + if (connectedSources.contains(name)) { //Either we have a peer connections, or existing connection via registry + handleReplicaConnection(connectedSources[name].objectSignature, rp, connectedSources[name].device); + } else { + //No existing connection, but we know we can connect via registry + const auto &sourceLocations = remoteObjectAddresses(); + const auto it = sourceLocations.constFind(name); + // This will try the connection, and if successful, the remoteObjects will be sent + // The link to the replica will be handled then + if (it != sourceLocations.constEnd()) + initConnection(it.value().hostUrl); + } + return rp; +} + +void QRemoteObjectNodePrivate::handleReplicaConnection(const QString &name) +{ + QSharedPointer rep = qSharedPointerCast(replicas.value(name).toStrongRef()); + if (!rep) { //replica has been deleted, remove from list + replicas.remove(name); + return; + } + + if (rep->isShortCircuit()) + return; + + QConnectedReplicaImplementation *connectedRep = static_cast(rep.data()); + if (connectedRep->connectionToSource.isNull()) { + const auto sourceInfo = connectedSources.value(name); + handleReplicaConnection(sourceInfo.objectSignature, connectedRep, sourceInfo.device); + } +} + +void QRemoteObjectNodePrivate::handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, QtROIoDeviceBase *connection) +{ + if (!checkSignatures(rep->m_objectSignature, sourceSignature)) { + qROPrivWarning() << "Signature mismatch for" << rep->m_metaObject->className() << (rep->m_objectName.isEmpty() ? QLatin1String("(unnamed)") : rep->m_objectName); + rep->setState(QRemoteObjectReplica::SignatureMismatch); + return; + } + rep->setConnection(connection); +} + +//Host Nodes can use the more efficient InProcess Replica if we (this Node) hold the Source for the +//requested Replica. If not, fall back to the Connected Replica case. +QReplicaImplementationInterface *QRemoteObjectHostBasePrivate::handleNewAcquire(const QMetaObject *meta, QRemoteObjectReplica *instance, const QString &name) +{ + QMap::const_iterator mapIt; + if (remoteObjectIo && map_contains(remoteObjectIo->m_sourceObjects, name, mapIt)) { + Q_Q(QRemoteObjectHostBase); + QInProcessReplicaImplementation *rp = new QInProcessReplicaImplementation(name, meta, q); + rp->configurePrivate(instance); + connectReplica(mapIt.value()->m_object, instance); + rp->connectionToSource = mapIt.value(); + return rp; + } + return QRemoteObjectNodePrivate::handleNewAcquire(meta, instance, name); +} + +void QRemoteObjectNodePrivate::onClientRead(QObject *obj) +{ + using namespace QRemoteObjectPackets; + QtROIoDeviceBase *connection = qobject_cast(obj); + QRemoteObjectPacketTypeEnum packetType; + Q_ASSERT(connection); + auto &codec = connection->d_func()->m_codec; + + do { + if (!connection->read(packetType, rxName)) + return; + + if (packetType != Handshake && codec == nullptr) { + qROPrivWarning() << "Expected Handshake, got " << packetType; + setLastError(QRemoteObjectNode::ProtocolMismatch); + connection->close(); + break; + } + + switch (packetType) { + case QRemoteObjectPacketTypeEnum::Pong: + { + QSharedPointer rep = qSharedPointerCast(replicas.value(rxName).toStrongRef()); + if (rep) + rep->notifyAboutReply(0, {}); + else //replica has been deleted, remove from list + replicas.remove(rxName); + break; + } + case QRemoteObjectPacketTypeEnum::Handshake: + if (rxName != QtRemoteObjects::protocolVersion) { + qWarning() << "*** Protocol Mismatch, closing connection ***. Got" << rxName << "expected" << QtRemoteObjects::protocolVersion; + setLastError(QRemoteObjectNode::ProtocolMismatch); + connection->close(); + } else { + // TODO should have some sort of manager for the codec + codec = new QRemoteObjectPackets::QDataStreamCodec; + } + break; + case QRemoteObjectPacketTypeEnum::ObjectList: + { + codec->deserializeObjectListPacket(connection->d_func()->stream(), rxObjects); + qROPrivDebug() << "newObjects:" << rxObjects; + // We need to make sure all of the source objects are in connectedSources before we add connections, + // otherwise nested QObjects could fail (we want to acquire children before parents, and the object + // list is unordered) + for (const auto &remoteObject : std::as_const(rxObjects)) { + qROPrivDebug() << " connectedSources.contains(" << remoteObject << ")" << connectedSources.contains(remoteObject.name) << replicas.contains(remoteObject.name); + if (!connectedSources.contains(remoteObject.name)) { + connectedSources[remoteObject.name] = SourceInfo{connection, remoteObject.typeName, remoteObject.signature}; + connection->addSource(remoteObject.name); + // Make sure we handle Registry first if it is available + if (remoteObject.name == QLatin1String("Registry") && replicas.contains(remoteObject.name)) + handleReplicaConnection(remoteObject.name); + } + } + for (const auto &remoteObject : std::as_const(rxObjects)) { + if (replicas.contains(remoteObject.name)) //We have a replica waiting on this remoteObject + handleReplicaConnection(remoteObject.name); + } + break; + } + case QRemoteObjectPacketTypeEnum::InitPacket: + { + qROPrivDebug() << "InitPacket-->" << rxName << this; + QSharedPointer rep = qSharedPointerCast(replicas.value(rxName).toStrongRef()); + //Use m_rxArgs (a QVariantList to hold the properties QVariantList) + codec->deserializeInitPacket(connection->d_func()->stream(), rxArgs); + if (rep) + { + handlePointerToQObjectProperties(rep.data(), rxArgs); + rep->initialize(std::move(rxArgs)); + } else { //replica has been deleted, remove from list + replicas.remove(rxName); + } + break; + } + case QRemoteObjectPacketTypeEnum::InitDynamicPacket: + { + qROPrivDebug() << "InitDynamicPacket-->" << rxName << this; + const QMetaObject *meta = dynamicTypeManager.addDynamicType(connection, connection->d_func()->stream()); + codec->deserializeInitPacket(connection->d_func()->stream(), rxArgs); + QSharedPointer rep = qSharedPointerCast(replicas.value(rxName).toStrongRef()); + if (rep) + { + rep->setDynamicMetaObject(meta); + handlePointerToQObjectProperties(rep.data(), rxArgs); + rep->setDynamicProperties(std::move(rxArgs)); + } else { //replica has been deleted, remove from list + replicas.remove(rxName); + } + break; + } + case QRemoteObjectPacketTypeEnum::RemoveObject: + { + qROPrivDebug() << "RemoveObject-->" << rxName << this; + connectedSources.remove(rxName); + connection->removeSource(rxName); + if (replicas.contains(rxName)) { //We have a replica using the removed source + QSharedPointer rep = qSharedPointerCast(replicas.value(rxName).toStrongRef()); + if (rep && !rep->connectionToSource.isNull()) { + rep->connectionToSource.clear(); + rep->setState(QRemoteObjectReplica::Suspect); + } else if (!rep) { + replicas.remove(rxName); + } + } + break; + } + case QRemoteObjectPacketTypeEnum::PropertyChangePacket: + { + int propertyIndex; + codec->deserializePropertyChangePacket(connection->d_func()->stream(), propertyIndex, rxValue); + QSharedPointer rep = qSharedPointerCast(replicas.value(rxName).toStrongRef()); + if (rep) { + QConnectedReplicaImplementation *connectedRep = nullptr; + if (!rep->isShortCircuit()) { + connectedRep = static_cast(rep.data()); + if (!connectedRep->childIndices().contains(propertyIndex)) + connectedRep = nullptr; //connectedRep will be a valid pointer only if propertyIndex is a child index + } + if (connectedRep) + rep->setProperty(propertyIndex, handlePointerToQObjectProperty(connectedRep, propertyIndex, rxValue)); + else { + const QMetaProperty property = rep->m_metaObject->property(propertyIndex + rep->m_metaObject->propertyOffset()); + if (property.userType() == QMetaType::QVariant && rxValue.canConvert()) { + // This is a type that requires registration + QRO_ typeInfo = rxValue.value(); + QDataStream in(typeInfo.classDefinition); + parseGadgets(connection, in); + QDataStream ds(typeInfo.parameters); + ds >> rxValue; + } + rep->setProperty(propertyIndex, decodeVariant(std::move(rxValue), property.metaType())); + } + } else { //replica has been deleted, remove from list + replicas.remove(rxName); + } + break; + } + case QRemoteObjectPacketTypeEnum::InvokePacket: + { + int call, index, serialId, propertyIndex; + codec->deserializeInvokePacket(connection->d_func()->stream(), call, index, rxArgs, serialId, propertyIndex); + QSharedPointer rep = qSharedPointerCast(replicas.value(rxName).toStrongRef()); + if (rep) { + static QVariant null(QMetaType::fromType(), nullptr); + QVariant paramValue; + // Qt usually supports 9 arguments, so ten should be usually safe + QVarLengthArray param(rxArgs.size() + 1); + param[0] = null.data(); //Never a return value + if (rxArgs.size()) { + auto signal = rep->m_metaObject->method(index+rep->m_signalOffset); + for (int i = 0; i < rxArgs.size(); i++) { + if (signal.parameterType(i) == QMetaType::QVariant) + param[i + 1] = const_cast(reinterpret_cast(&rxArgs.at(i))); + else { + rxArgs[i] = decodeVariant(std::move(rxArgs[i]), signal.parameterMetaType(i)); + param[i + 1] = const_cast(rxArgs.at(i).data()); + } + } + } else if (propertyIndex != -1) { + param.resize(2); + paramValue = rep->getProperty(propertyIndex); + param[1] = paramValue.data(); + } + qROPrivDebug() << "Replica Invoke-->" << rxName << rep->m_metaObject->method(index+rep->m_signalOffset).name() << index << rep->m_signalOffset; + // We activate on rep->metaobject() so the private metacall is used, not m_metaobject (which + // is the class thie replica looks like) + QMetaObject::activate(rep.data(), rep->metaObject(), index+rep->m_signalOffset, param.data()); + } else { //replica has been deleted, remove from list + replicas.remove(rxName); + } + break; + } + case QRemoteObjectPacketTypeEnum::InvokeReplyPacket: + { + int ackedSerialId; + codec->deserializeInvokeReplyPacket(connection->d_func()->stream(), ackedSerialId, rxValue); + QSharedPointer rep = qSharedPointerCast(replicas.value(rxName).toStrongRef()); + if (rep) { + qROPrivDebug() << "Received InvokeReplyPacket ack'ing serial id:" << ackedSerialId; + rep->notifyAboutReply(ackedSerialId, rxValue); + } else { //replica has been deleted, remove from list + replicas.remove(rxName); + } + break; + } + case QRemoteObjectPacketTypeEnum::AddObject: + case QRemoteObjectPacketTypeEnum::Invalid: + case QRemoteObjectPacketTypeEnum::Ping: + qROPrivWarning() << "Unexpected packet received"; + } + } while (connection->bytesAvailable()); // have bytes left over, so do another iteration +} + +/*! + \class QRemoteObjectNode + \inmodule QtRemoteObjects + \brief A node on a Qt Remote Objects network. + + The QRemoteObjectNode class provides an entry point to a QtRemoteObjects + network. A network can be as simple as two nodes, or an arbitrarily complex + set of processes and devices. + + A QRemoteObjectNode does not have a url that other nodes can connect to, + and thus is able to acquire replicas only. It is not able to share source + objects (only QRemoteObjectHost and QRemoteObjectRegistryHost Nodes can + share). + + Nodes may connect to each other directly using \l connectToNode, or + they can use the QRemoteObjectRegistry to simplify connections. + + The QRemoteObjectRegistry is a special replica available to every node that + connects to the Registry Url. It knows how to connect to every + QRemoteObjectSource object on the network. + + \sa QRemoteObjectHost, QRemoteObjectRegistryHost +*/ + +/*! + \class QRemoteObjectHostBase + \inmodule QtRemoteObjects + \brief The QRemoteObjectHostBase class provides base functionality common to \l + {QRemoteObjectHost} {Host} and \l {QRemoteObjectRegistryHost} {RegistryHost} classes. + + QRemoteObjectHostBase is a base class that cannot be instantiated directly. + It provides the enableRemoting and disableRemoting functionality shared by + all host nodes (\l {QRemoteObjectHost} {Host} and \l + {QRemoteObjectRegistryHost} {RegistryHost}) as well as the logic required + to expose \l {Source} objects on the Remote Objects network. +*/ + +/*! + \class QRemoteObjectHost + \inmodule QtRemoteObjects + \brief A (Host) Node on a Qt Remote Objects network. + + The QRemoteObjectHost class provides an entry point to a QtRemoteObjects + network. A network can be as simple as two nodes, or an arbitrarily complex + set of processes and devices. + + QRemoteObjectHosts have the same capabilities as QRemoteObjectNodes, but + they can also be connected to and can share source objects on the network. + + Nodes may connect to each other directly using \l connectToNode, or they + can use the QRemoteObjectRegistry to simplify connections. + + The QRemoteObjectRegistry is a special replica available to every node that + connects to the registry Url. It knows how to connect to every + QRemoteObjectSource object on the network. + + \sa QRemoteObjectNode, QRemoteObjectRegistryHost +*/ + +/*! + \class QRemoteObjectRegistryHost + \inmodule QtRemoteObjects + \brief A (Host/Registry) node on a Qt Remote Objects network. + + The QRemoteObjectRegistryHost class provides an entry point to a QtRemoteObjects + network. A network can be as simple as two Nodes, or an arbitrarily complex + set of processes and devices. + + A QRemoteObjectRegistryHost has the same capability that a + QRemoteObjectHost has (which includes everything a QRemoteObjectNode + supports), and in addition is the owner of the Registry. Any + QRemoteObjectHost node that connects to this Node will have all of their + Source objects made available by the Registry. + + Nodes only support connection to one \l registry, calling \l + QRemoteObjectNode::setRegistryUrl when a Registry is already set is + considered an error. For something like a secure and insecure network + (where different Registries would be applicable), the recommendation is to + create separate Nodes to connect to each, in effect creating two + independent Qt Remote Objects networks. + + Nodes may connect to each other directly using \l connectToNode, or they + can use the QRemoteObjectRegistry to simplify connections. + + The QRemoteObjectRegistry is a special Replica available to every Node that + connects to the Registry Url. It knows how to connect to every + QRemoteObjectSource object on the network. + + \sa QRemoteObjectNode, QRemoteObjectHost +*/ + +/*! + \enum QRemoteObjectNode::ErrorCode + + This enum type specifies the various error codes associated with QRemoteObjectNode errors: + + \value NoError No error. + \value RegistryNotAcquired The registry could not be acquired. + \value RegistryAlreadyHosted The registry is already defined and hosting Sources. + \value NodeIsNoServer The given QRemoteObjectNode is not a host node. + \value ServerAlreadyCreated The host node has already been initialized. + \value UnintendedRegistryHosting An attempt was made to create a host QRemoteObjectNode and connect to itself as the registry. + \value OperationNotValidOnClientNode The attempted operation is not valid on a client QRemoteObjectNode. + \value SourceNotRegistered The given QRemoteObjectSource is not registered on this node. + \value MissingObjectName The given QObject does not have objectName() set. + \value HostUrlInvalid The given url has an invalid or unrecognized scheme. + \value ProtocolMismatch The client and the server have different protocol versions. + \value ListenFailed Can't listen on the specified host port. +*/ + +/*! + \enum QRemoteObjectHostBase::AllowedSchemas + + This enum is used to specify whether a Node will accept a url with an + unrecognized schema for the hostUrl. By default only urls with known + schemas are accepted, but using \c AllowExternalRegistration will enable + the \l Registry to pass your external (to QtRO) url to client Nodes. + + \value BuiltInSchemasOnly Only allow the hostUrl to be set to a QtRO + supported schema. This is the default value, and causes a Node error to be + set if an unrecognized schema is provided. + \value AllowExternalRegistration The provided schema is registered as an + \l {External Schemas}{External Schema} + + \sa QRemoteObjectHost +*/ + +/*! + \fn ObjectType *QRemoteObjectNode::acquire(const QString &name) + + Returns a pointer to a Replica of type ObjectType (which is a template + parameter and must inherit from \l QRemoteObjectReplica). That is, the + template parameter must be a \l {repc} generated type. The \a name + parameter can be used to specify the \a name given to the object + during the QRemoteObjectHost::enableRemoting() call. +*/ + +void QRemoteObjectNodePrivate::initialize() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); //For queued qnx error() + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + // To support dynamic MODELs, we need to make sure the types are registered + QAbstractItemModelSourceAdapter::registerTypes(); +} + +bool QRemoteObjectNodePrivate::checkSignatures(const QByteArray &a, const QByteArray &b) +{ + // if any of a or b is empty it means it's a dynamic ojects or an item model + if (a.isEmpty() || b.isEmpty()) + return true; + return a == b; +} + + +void QRemoteObjectNode::persistProperties(const QString &repName, const QByteArray &repSig, const QVariantList &props) +{ + Q_D(QRemoteObjectNode); + if (d->persistedStore) { + d->persistedStore->saveProperties(repName, repSig, props); + } else { + qCWarning(QT_REMOTEOBJECT) << qPrintable(objectName()) << "Unable to store persisted properties for" << repName; + qCWarning(QT_REMOTEOBJECT) << " No persisted store set."; + } +} + +QVariantList QRemoteObjectNode::retrieveProperties(const QString &repName, const QByteArray &repSig) +{ + Q_D(QRemoteObjectNode); + if (d->persistedStore) { + return d->persistedStore->restoreProperties(repName, repSig); + } + qCWarning(QT_REMOTEOBJECT) << qPrintable(objectName()) << "Unable to retrieve persisted properties for" << repName; + qCWarning(QT_REMOTEOBJECT) << " No persisted store set."; + return QVariantList(); +} + +/*! + Default constructor for QRemoteObjectNode with the given \a parent. A Node + constructed in this manner can not be connected to, and thus can not expose + Source objects on the network. It also will not include a \l + QRemoteObjectRegistry, unless set manually using setRegistryUrl. + + \sa connectToNode, setRegistryUrl +*/ +QRemoteObjectNode::QRemoteObjectNode(QObject *parent) + : QObject(*new QRemoteObjectNodePrivate, parent) +{ + Q_D(QRemoteObjectNode); + d->initialize(); +} + +/*! + QRemoteObjectNode connected to a {QRemoteObjectRegistry} {Registry}. A Node + constructed in this manner can not be connected to, and thus can not expose + Source objects on the network. Finding and connecting to other (Host) Nodes + is handled by the QRemoteObjectRegistry specified by \a registryAddress. + + \sa connectToNode, setRegistryUrl, QRemoteObjectHost, QRemoteObjectRegistryHost +*/ +QRemoteObjectNode::QRemoteObjectNode(const QUrl ®istryAddress, QObject *parent) + : QObject(*new QRemoteObjectNodePrivate, parent) +{ + Q_D(QRemoteObjectNode); + d->initialize(); + d->setRegistryUrlNodeImpl(registryAddress); +} + +QRemoteObjectNode::QRemoteObjectNode(QRemoteObjectNodePrivate &dptr, QObject *parent) + : QObject(dptr, parent) +{ + Q_D(QRemoteObjectNode); + d->initialize(); +} + +/*! + \qmltype Host + \instantiates QRemoteObjectHost + \inqmlmodule QtRemoteObjects + \brief A host node on a Qt Remote Objects network. + + The Host type provides an entry point to a Qt Remote Objects network. A network + can be as simple as two nodes, or an arbitrarily complex set of processes and devices. + + Hosts have the same capabilities as Nodes, but they can also be connected to and can + share source objects on the network. +*/ + +/*! + \internal This is a base class for both QRemoteObjectHost and + QRemoteObjectRegistryHost to provide the shared features/functions for + sharing \l Source objects. +*/ +QRemoteObjectHostBase::QRemoteObjectHostBase(QRemoteObjectHostBasePrivate &d, QObject *parent) + : QRemoteObjectNode(d, parent) +{ } + +/*! + Constructs a new QRemoteObjectHost Node (i.e., a Node that supports + exposing \l Source objects on the QtRO network) with the given \a parent. + This constructor is meant specific to support QML in the future as it will + not be available to connect to until \l {QRemoteObjectHost::}{setHostUrl} is called. + + \sa setHostUrl(), setRegistryUrl() +*/ +QRemoteObjectHost::QRemoteObjectHost(QObject *parent) + : QRemoteObjectHostBase(*new QRemoteObjectHostPrivate, parent) +{ } + +/*! + Constructs a new QRemoteObjectHost Node (i.e., a Node that supports + exposing \l Source objects on the QtRO network) with address \a address. If + set, \a registryAddress will be used to connect to the \l + QRemoteObjectRegistry at the provided address. The \a allowedSchemas + parameter is only needed (and should be set to \l + {QRemoteObjectHostBase::AllowExternalRegistration} + {AllowExternalRegistration}) if the schema of the url should be used as an + \l {External Schemas} {External Schema} by the registry. + + \sa setHostUrl(), setRegistryUrl() +*/ +QRemoteObjectHost::QRemoteObjectHost(const QUrl &address, const QUrl ®istryAddress, + AllowedSchemas allowedSchemas, QObject *parent) + : QRemoteObjectHostBase(*new QRemoteObjectHostPrivate, parent) +{ + if (!address.isEmpty()) { + if (!d_func()->setHostUrlBaseImpl(address, allowedSchemas)) + return; + } + + if (!registryAddress.isEmpty()) + d_func()->setRegistryUrlNodeImpl(registryAddress); +} + +/*! + Constructs a new QRemoteObjectHost Node (i.e., a Node that supports + exposing \l Source objects on the QtRO network) with a url of \a + address and the given \a parent. This overload is provided as a convenience for specifying a + QObject parent without providing a registry address. + + \sa setHostUrl(), setRegistryUrl() +*/ +QRemoteObjectHost::QRemoteObjectHost(const QUrl &address, QObject *parent) + : QRemoteObjectHostBase(*new QRemoteObjectHostPrivate, parent) +{ + if (!address.isEmpty()) + d_func()->setHostUrlBaseImpl(address); +} + +/*! + \internal QRemoteObjectHost::QRemoteObjectHost +*/ +QRemoteObjectHost::QRemoteObjectHost(QRemoteObjectHostPrivate &d, QObject *parent) + : QRemoteObjectHostBase(d, parent) +{ } + +QRemoteObjectHost::~QRemoteObjectHost() {} + +/*! + Constructs a new QRemoteObjectRegistryHost Node with the given \a parent. RegistryHost Nodes have + the same functionality as \l QRemoteObjectHost Nodes, except rather than + being able to connect to a \l QRemoteObjectRegistry, the provided Host QUrl + (\a registryAddress) becomes the address of the registry for other Nodes to + connect to. +*/ +QRemoteObjectRegistryHost::QRemoteObjectRegistryHost(const QUrl ®istryAddress, QObject *parent) + : QRemoteObjectHostBase(*new QRemoteObjectRegistryHostPrivate, parent) +{ + if (registryAddress.isEmpty()) + return; + + d_func()->setRegistryUrlRegistryHostImpl(registryAddress); +} + +/*! + \internal +*/ +QRemoteObjectRegistryHost::QRemoteObjectRegistryHost(QRemoteObjectRegistryHostPrivate &d, QObject *parent) + : QRemoteObjectHostBase(d, parent) +{ } + +QRemoteObjectRegistryHost::~QRemoteObjectRegistryHost() {} + +QRemoteObjectNode::~QRemoteObjectNode() +{ } + +QRemoteObjectHostBase::~QRemoteObjectHostBase() +{ } + +/*! + Sets \a name as the internal name for this Node. This + is then output as part of the logging (if enabled). + This is primarily useful if you merge log data from multiple nodes. +*/ +void QRemoteObjectNode::setName(const QString &name) +{ + setObjectName(name); +} + +/*! + Similar to QObject::setObjectName() (which this method calls), but this + version also applies the \a name to internal classes as well, which are + used in some of the debugging output. +*/ +void QRemoteObjectHostBase::setName(const QString &name) +{ + Q_D(QRemoteObjectHostBase); + setObjectName(name); + if (d->remoteObjectIo) + d->remoteObjectIo->setObjectName(name); +} + +/*! + \internal The HostBase version of this method is protected so the method + isn't exposed on RegistryHost nodes. +*/ +QUrl QRemoteObjectHostBase::hostUrl() const +{ + Q_D(const QRemoteObjectHostBase); + if (d->remoteObjectIo) + return d->remoteObjectIo->serverAddress(); + return QUrl(); +} + +/*! + \internal The HostBase version of this method is protected so the method + isn't exposed on RegistryHost nodes. +*/ +bool QRemoteObjectHostBase::setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas) +{ + return d_func()->setHostUrlBaseImpl(hostAddress, allowedSchemas); +} + +bool QRemoteObjectHostBasePrivate::setHostUrlBaseImpl( + const QUrl &hostAddress, QRemoteObjectHostBase::AllowedSchemas allowedSchemas) +{ + Q_Q(QRemoteObjectHostBase); + if (remoteObjectIo) { + setLastError(QRemoteObjectHostBase::ServerAlreadyCreated); + return false; + } + + if (allowedSchemas == QRemoteObjectHostBase::AllowedSchemas::BuiltInSchemasOnly && !QtROServerFactory::instance()->isValid(hostAddress)) { + setLastError(QRemoteObjectHostBase::HostUrlInvalid); + return false; + } + + if (allowedSchemas == QRemoteObjectHostBase::AllowedSchemas::AllowExternalRegistration && QtROServerFactory::instance()->isValid(hostAddress)) { + qWarning() << qPrintable(q->objectName()) << "Overriding a valid QtRO url (" << hostAddress << ") with AllowExternalRegistration is not allowed."; + setLastError(QRemoteObjectHostBase::HostUrlInvalid); + return false; + } + remoteObjectIo = new QRemoteObjectSourceIo(hostAddress, q); + + if (allowedSchemas == QRemoteObjectHostBase::AllowedSchemas::BuiltInSchemasOnly && !remoteObjectIo->startListening()) { + setLastError(QRemoteObjectHostBase::ListenFailed); + delete remoteObjectIo; + remoteObjectIo = nullptr; + return false; + } + + //If we've given a name to the node, set it on the sourceIo as well + if (!q->objectName().isEmpty()) + remoteObjectIo->setObjectName(q->objectName()); + //Since we don't know whether setHostUrl or setRegistryUrl/setRegistryHost will be called first, + //break it into two pieces. setHostUrl connects the RemoteObjectSourceIo->[add/remove]RemoteObjectSource to QRemoteObjectReplicaNode->[add/remove]RemoteObjectSource + //setRegistry* calls appropriately connect RemoteObjecSourcetIo->[add/remove]RemoteObjectSource to the registry when it is created + QObject::connect(remoteObjectIo, &QRemoteObjectSourceIo::remoteObjectAdded, q, &QRemoteObjectHostBase::remoteObjectAdded); + QObject::connect(remoteObjectIo, &QRemoteObjectSourceIo::remoteObjectRemoved, q, &QRemoteObjectHostBase::remoteObjectRemoved); + + return true; +} + +/*! + \qmlproperty url Host::hostUrl + + The host address for the node. + + This is the address where source objects remoted by this node will reside. +*/ + +/*! + \property QRemoteObjectHost::hostUrl + \brief The host address for the node. + + This is the address where source objects remoted by this node will reside. +*/ + +/*! + Returns the host address for the QRemoteObjectNode as a QUrl. If the Node + is not a Host node, returns an empty QUrl. + + \sa setHostUrl() +*/ +QUrl QRemoteObjectHost::hostUrl() const +{ + return QRemoteObjectHostBase::hostUrl(); +} + +/*! + Sets the \a hostAddress for a host QRemoteObjectNode. + + Returns \c true if the Host address is set, otherwise \c false. + + The \a allowedSchemas parameter is only needed (and should be set to \l + {QRemoteObjectHostBase::AllowExternalRegistration} + {AllowExternalRegistration}) if the schema of the url should be used as an + \l {External Schemas} {External Schema} by the registry. +*/ +bool QRemoteObjectHost::setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas) +{ + return d_func()->setHostUrlHostImpl(hostAddress, allowedSchemas); +} + +bool QRemoteObjectHostPrivate::setHostUrlHostImpl( + const QUrl &hostAddress, QRemoteObjectHostBase::AllowedSchemas allowedSchemas) +{ + bool success = setHostUrlBaseImpl(hostAddress, allowedSchemas); + if (success) + q_func()->hostUrlChanged(); + return success; +} + +/*! + This method can be used to set the address of this Node to \a registryUrl + (used for other Nodes to connect to this one), if the QUrl isn't set in the + constructor. Since this Node becomes the Registry, calling this setter + method causes this Node to use the url as the host address. All other + Node's use the \l {QRemoteObjectNode::setRegistryUrl} method initiate a + connection to the Registry. + + Returns \c true if the registry address is set, otherwise \c false. + + \sa QRemoteObjectRegistryHost(), QRemoteObjectNode::setRegistryUrl +*/ +bool QRemoteObjectRegistryHost::setRegistryUrl(const QUrl ®istryUrl) +{ + return d_func()->setRegistryUrlRegistryHostImpl(registryUrl); +} + +bool QRemoteObjectRegistryHostPrivate::setRegistryUrlRegistryHostImpl(const QUrl ®istryUrl) +{ + Q_Q(QRemoteObjectRegistryHost); + if (setHostUrlBaseImpl(registryUrl)) { + if (!remoteObjectIo) { + setLastError(QRemoteObjectNode::ServerAlreadyCreated); + return false; + } else if (registry) { + setLastError(QRemoteObjectNode::RegistryAlreadyHosted); + return false; + } + + QRegistrySource *remoteObject = new QRegistrySource(q); + q->enableRemoting(remoteObject); + registryAddress = remoteObjectIo->serverAddress(); + registrySource = remoteObject; + //Connect RemoteObjectSourceIo->remoteObject[Added/Removde] to the registry Slot + QObject::connect(q, &QRemoteObjectRegistryHost::remoteObjectAdded, registrySource, &QRegistrySource::addSource); + QObject::connect(q, &QRemoteObjectRegistryHost::remoteObjectRemoved, registrySource, &QRegistrySource::removeSource); + QObject::connect(remoteObjectIo, &QRemoteObjectSourceIo::serverRemoved, registrySource, &QRegistrySource::removeServer); + //onAdd/Remove update the known remoteObjects list in the RegistrySource, so no need to connect to the RegistrySource remoteObjectAdded/Removed signals + setRegistry(q->acquire()); + return true; + } + return false; +} + +/*! + Returns the last error set. +*/ +QRemoteObjectNode::ErrorCode QRemoteObjectNode::lastError() const +{ + Q_D(const QRemoteObjectNode); + return d->lastError; +} + +/*! + \qmlproperty url Node::registryUrl + + The address of the \l {QRemoteObjectRegistry} {Registry} used by this node. + + This is an empty QUrl if there is no registry in use. +*/ + +/*! + \property QRemoteObjectNode::registryUrl + \brief The address of the \l {QRemoteObjectRegistry} {Registry} used by this node. + + This is an empty QUrl if there is no registry in use. +*/ +QUrl QRemoteObjectNode::registryUrl() const +{ + Q_D(const QRemoteObjectNode); + return d->registryAddress; +} + +bool QRemoteObjectNode::setRegistryUrl(const QUrl ®istryAddress) +{ + Q_D(QRemoteObjectNode); + return d->setRegistryUrlNodeImpl(registryAddress); +} + +bool QRemoteObjectNodePrivate::setRegistryUrlNodeImpl(const QUrl ®istryAddr) +{ + Q_Q(QRemoteObjectNode); + if (registry) { + setLastError(QRemoteObjectNode::RegistryAlreadyHosted); + return false; + } + + registryAddress = registryAddr; + setRegistry(q->acquire()); + //Connect remoteObject[Added/Removed] to the registry Slot + QObject::connect(q, &QRemoteObjectNode::remoteObjectAdded, registry, &QRemoteObjectRegistry::addSource); + QObject::connect(q, &QRemoteObjectNode::remoteObjectRemoved, registry, &QRemoteObjectRegistry::removeSource); + q->connectToNode(registryAddress); + return true; +} + +void QRemoteObjectNodePrivate::setRegistry(QRemoteObjectRegistry *reg) +{ + Q_Q(QRemoteObjectNode); + registry = reg; + reg->setParent(q); + //Make sure when we get the registry initialized, we update our replicas + QObject::connect(reg, &QRemoteObjectRegistry::initialized, q, [this]() { + onRegistryInitialized(); + }); + //Make sure we handle new RemoteObjectSources on Registry... + QObject::connect(reg, &QRemoteObjectRegistry::remoteObjectAdded, + q, [this](const QRemoteObjectSourceLocation &location) { + onRemoteObjectSourceAdded(location); + }); + QObject::connect(reg, &QRemoteObjectRegistry::remoteObjectRemoved, + q, [this](const QRemoteObjectSourceLocation &location) { + onRemoteObjectSourceRemoved(location); + }); +} + +QVariant QRemoteObjectNodePrivate::handlePointerToQObjectProperty(QConnectedReplicaImplementation *rep, int index, const QVariant &property) +{ + Q_Q(QRemoteObjectNode); + using namespace QRemoteObjectPackets; + + QVariant retval; + + Q_ASSERT(property.canConvert()); + QRO_ childInfo = property.value(); + qROPrivDebug() << "QRO_:" << childInfo.name << replicas.contains(childInfo.name) << replicas.keys(); + if (childInfo.isNull) { + // Either the source has changed the pointer and we need to update it, or the source pointer is a nullptr + if (replicas.contains(childInfo.name)) + replicas.remove(childInfo.name); + if (childInfo.type == ObjectType::CLASS) + retval = QVariant::fromValue(nullptr); + else + retval = QVariant::fromValue(nullptr); + return retval; + } + + const bool newReplica = !replicas.contains(childInfo.name) || rep->isInitialized(); + if (newReplica) { + if (rep->isInitialized()) { + auto childRep = qSharedPointerCast(replicas.take(childInfo.name)); + if (childRep && !childRep->isShortCircuit()) { + qCDebug(QT_REMOTEOBJECT) << "Checking if dynamic type should be added to dynamicTypeManager (type =" << childRep->m_metaObject->className() << ")"; + dynamicTypeManager.addFromMetaObject(childRep->m_metaObject); + } + } + if (childInfo.type == ObjectType::CLASS) + retval = QVariant::fromValue(q->acquireDynamic(childInfo.name)); + else + retval = QVariant::fromValue(q->acquireModel(childInfo.name)); + } else //We are receiving the initial data for the QObject + retval = rep->getProperty(index); //Use existing value so changed signal isn't emitted + + QSharedPointer childRep = qSharedPointerCast(replicas.value(childInfo.name).toStrongRef()); + if (childRep->connectionToSource.isNull()) + childRep->connectionToSource = rep->connectionToSource; + QVariantList parameters; + QDataStream ds(childInfo.parameters); + if (childRep->needsDynamicInitialization()) { + if (childInfo.classDefinition.isEmpty()) { + auto typeName = childInfo.typeName; + if (typeName == QLatin1String("QObject")) { + // The sender would have included the class name if needed + // So the acquire must have been templated, and we have the typeName + typeName = QString::fromLatin1(rep->getProperty(index).typeName()); + if (typeName.endsWith(QLatin1String("Replica*"))) + typeName.chop(8); + } + childRep->setDynamicMetaObject(dynamicTypeManager.metaObjectForType(typeName)); + } else { + QDataStream in(childInfo.classDefinition); + childRep->setDynamicMetaObject(dynamicTypeManager.addDynamicType(rep->connectionToSource, in)); + } + if (!childInfo.parameters.isEmpty()) + ds >> parameters; + handlePointerToQObjectProperties(childRep.data(), parameters); + childRep->setDynamicProperties(std::move(parameters)); + } else { + if (!childInfo.parameters.isEmpty()) + ds >> parameters; + handlePointerToQObjectProperties(childRep.data(), parameters); + childRep->initialize(std::move(parameters)); + } + + return retval; +} + +void QRemoteObjectNodePrivate::handlePointerToQObjectProperties(QConnectedReplicaImplementation *rep, QVariantList &properties) +{ + for (const int index : rep->childIndices()) + properties[index] = handlePointerToQObjectProperty(rep, index, properties.at(index)); +} + +/*! + Blocks until this Node's \l Registry is initialized or \a timeout (in + milliseconds) expires. Returns \c true if the \l Registry is successfully + initialized upon return, or \c false otherwise. +*/ +bool QRemoteObjectNode::waitForRegistry(int timeout) +{ + Q_D(QRemoteObjectNode); + if (!d->registry) { + qCWarning(QT_REMOTEOBJECT) << qPrintable(objectName()) << "waitForRegistry() error: No valid registry url set"; + return false; + } + + return d->registry->waitForSource(timeout); +} + +/*! + Connects a client node to the host node at \a address. + + Connections will remain valid until the host node is deleted or no longer + accessible over a network. + + Once a client is connected to a host, valid Replicas can then be acquired + if the corresponding Source is being remoted. + + Return \c true on success, \c false otherwise (usually an unrecognized url, + or connecting to already connected address). +*/ +bool QRemoteObjectNode::connectToNode(const QUrl &address) +{ + Q_D(QRemoteObjectNode); + if (!d->initConnection(address)) { + d->setLastError(RegistryNotAcquired); + return false; + } + return true; +} + +/*! + \since 5.12 + + In order to \l QRemoteObjectNode::acquire() \l Replica objects over \l + {External QIODevices}, Qt Remote Objects needs access to the communications + channel (a \l QIODevice) between the respective nodes. It is the + addClientSideConnection() call that enables this, taking the \a ioDevice as + input. Any acquire() call made without calling addClientSideConnection will + still work, but the Node will not be able to initialize the \l Replica + without being provided the connection to the Host node. + + \sa {QRemoteObjectHostBase::addHostSideConnection} +*/ +void QRemoteObjectNode::addClientSideConnection(QIODevice *ioDevice) +{ + Q_D(QRemoteObjectNode); + if (!ioDevice || !ioDevice->isOpen()) { + qWarning() << "A null or closed QIODevice was passed to addClientSideConnection(). Ignoring."; + return; + } + QtROExternalIoDevice *device = new QtROExternalIoDevice(ioDevice, this); + connect(device, &QtROIoDeviceBase::readyRead, this, [d, device]() { + d->onClientRead(device); + }); + if (device->bytesAvailable()) + d->onClientRead(device); +} + +/*! + \fn void QRemoteObjectNode::remoteObjectAdded(const QRemoteObjectSourceLocation &loc) + + This signal is emitted whenever a new \l {Source} object is added to + the Registry. The signal will not be emitted if there is no Registry set + (i.e., Sources over connections made via connectToNode directly). The \a + loc parameter contains the information about the added Source, including + name, type and the QUrl of the hosting Node. + + \sa remoteObjectRemoved(), instances() +*/ + +/*! + \fn void QRemoteObjectNode::remoteObjectRemoved(const QRemoteObjectSourceLocation &loc) + + This signal is emitted whenever a known \l {Source} object is + removed from the Registry. The signal will not be emitted if there is no + Registry set (i.e., Sources over connections made via connectToNode + directly). The \a loc parameter contains the information about the removed + Source, including name, type and the QUrl of the hosting Node. + + \sa remoteObjectAdded, instances +*/ + +/*! + \fn QStringList QRemoteObjectNode::instances() const + + This templated function (taking a \l repc generated type as the template parameter) will + return the list of names of every instance of that type on the Remote + Objects network. For example, if you have a Shape class defined in a .rep file, + and Circle and Square classes inherit from the Source definition, they can + be shared on the Remote Objects network using \l {QRemoteObjectHostBase::enableRemoting} {enableRemoting}. + \code + Square square; + Circle circle; + myHost.enableRemoting(&square, "Square"); + myHost.enableRemoting(&circle, "Circle"); + \endcode + Then instance can be used to find the available instances of Shape. + \code + QStringList instances = clientNode.instances(); + // will return a QStringList containing "Circle" and "Square" + auto instance1 = clientNode.acquire("Circle"); + auto instance2 = clientNode.acquire("Square"); + ... + \endcode +*/ + +/*! + \overload instances() + + This convenience function provides the same result as the templated + version, but takes the name of the \l {Source} class as a parameter (\a + typeName) rather than deriving it from the class type. +*/ +QStringList QRemoteObjectNode::instances(QStringView typeName) const +{ + Q_D(const QRemoteObjectNode); + QStringList names; + for (auto it = d->connectedSources.cbegin(), end = d->connectedSources.cend(); it != end; ++it) { + if (it.value().typeName == typeName) { + names << it.key(); + } + } + return names; +} + +/*! + \keyword dynamic acquire + Returns a QRemoteObjectDynamicReplica of the Source \a name. +*/ +QRemoteObjectDynamicReplica *QRemoteObjectNode::acquireDynamic(const QString &name) +{ + return new QRemoteObjectDynamicReplica(this, name); +} + +/*! + \qmlmethod bool Host::enableRemoting(object object, string name) + Enables a host node to dynamically provide remote access to the QObject \a + object. Client nodes connected to the node hosting this object may obtain + Replicas of this Source. + + The optional \a name defines the lookup-name under which the QObject can be acquired + using \l QRemoteObjectNode::acquire() . If not explicitly set then the name + given in the QCLASSINFO_REMOTEOBJECT_TYPE will be used. If no such macro + was defined for the QObject then the \l QObject::objectName() is used. + + Returns \c false if the current node is a client node, or if the QObject is already + registered to be remoted, and \c true if remoting is successfully enabled + for the dynamic QObject. + + \sa disableRemoting() +*/ + +/*! + Enables a host node to dynamically provide remote access to the QObject \a + object. Client nodes connected to the node + hosting this object may obtain Replicas of this Source. + + The optional \a name defines the lookup-name under which the QObject can be acquired + using \l QRemoteObjectNode::acquire() . If not explicitly set then the name + given in the QCLASSINFO_REMOTEOBJECT_TYPE will be used. If no such macro + was defined for the QObject then the \l QObject::objectName() is used. + + Returns \c false if the current node is a client node, or if the QObject is already + registered to be remoted, and \c true if remoting is successfully enabled + for the dynamic QObject. + + \sa disableRemoting() +*/ +bool QRemoteObjectHostBase::enableRemoting(QObject *object, const QString &name) +{ + Q_D(QRemoteObjectHostBase); + if (!d->remoteObjectIo) { + d->setLastError(OperationNotValidOnClientNode); + return false; + } + + const QMetaObject *meta = object->metaObject(); + QString _name = name; + QString typeName = getTypeNameAndMetaobjectFromClassInfo(meta); + if (typeName.isEmpty()) { //This is a passed in QObject, use its API + if (_name.isEmpty()) { + _name = object->objectName(); + if (_name.isEmpty()) { + d->setLastError(MissingObjectName); + qCWarning(QT_REMOTEOBJECT) << qPrintable(objectName()) << "enableRemoting() Error: Unable to Replicate an object that does not have objectName() set."; + return false; + } + } + } else if (_name.isEmpty()) + _name = typeName; + return d->remoteObjectIo->enableRemoting(object, meta, _name, typeName); +} + +/*! + This overload of enableRemoting() is specific to \l QAbstractItemModel types + (or any type derived from \l QAbstractItemModel). This is useful if you want + to have a model and the HMI for the model in different processes. + + The three required parameters are the \a model itself, the \a name by which + to lookup the model, and the \a roles that should be exposed on the Replica + side. If you want to synchronize selection between \l Source and \l + Replica, the optional \a selectionModel parameter can be used. This is only + recommended when using a single Replica. + + Behind the scenes, Qt Remote Objects batches data() lookups and prefetches + data when possible to make the model interaction as responsive as possible. + + Returns \c false if the current node is a client node, or if the QObject is already + registered to be remoted, and \c true if remoting is successfully enabled + for the QAbstractItemModel. + + \sa disableRemoting() + */ +bool QRemoteObjectHostBase::enableRemoting(QAbstractItemModel *model, const QString &name, const QList roles, QItemSelectionModel *selectionModel) +{ + //This looks complicated, but hopefully there is a way to have an adapter be a template + //parameter and this makes sure that is supported. + QObject *adapter = QAbstractItemModelSourceAdapter::staticMetaObject.newInstance(Q_ARG(QAbstractItemModel*, model), + Q_ARG(QItemSelectionModel*, selectionModel), + Q_ARG(QList, roles)); + QAbstractItemAdapterSourceAPI *api = + new QAbstractItemAdapterSourceAPI(name); + if (!this->objectName().isEmpty()) + adapter->setObjectName(this->objectName().append(QLatin1String("Adapter"))); + return enableRemoting(model, api, adapter); +} + +/*! + \fn template