diff --git a/LICENSES/LGPL-2.1-or-later.txt b/LICENSES/LGPL-2.1-or-later.txt new file mode 100644 index 00000000000..04bb156e77d --- /dev/null +++ b/LICENSES/LGPL-2.1-or-later.txt @@ -0,0 +1,468 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 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. + +[This is the first released version of the Lesser GPL. It also counts as the +successor of the GNU Library Public License, version 2, hence the version +number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Lesser General Public License, applies to some specially +designated software packages--typically libraries--of the Free Software Foundation +and other authors who decide to use it. You can use it too, but we suggest +you first think carefully about whether this license or the ordinary General +Public License is the better strategy to use in any particular case, based +on the explanations below. + +When we speak of free software, we are referring to freedom of use, 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 and use pieces of it in new free programs; and that +you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors +to deny you these rights or to ask you to surrender these rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +other code with the library, you must provide complete object files to the +recipients, so that they can relink them with the library after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, +and (2) we offer you this license, which gives you legal permission to copy, +distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no +warranty for the free library. Also, if the library is modified by someone +else and passed on, the recipients should know that what they have is not +the original version, so that the original author's reputation will not be +affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free +program. We wish to make sure that a company cannot effectively restrict the +users of a free program by obtaining a restrictive license from a patent holder. +Therefore, we insist that any patent license obtained for a version of the +library must be consistent with the full freedom of use specified in this +license. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License. This license, the GNU Lesser General Public License, +applies to certain designated libraries, and is quite different from the ordinary +General Public License. We use this license for certain libraries in order +to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared +library, the combination of the two is legally speaking a combined work, a +derivative of the original library. The ordinary General Public License therefore +permits such linking only if the entire combination fits its criteria of freedom. +The Lesser General Public License permits more lax criteria for linking other +code with the library. + +We call this license the "Lesser" General Public License because it does Less +to protect the user's freedom than the ordinary General Public License. It +also provides other free software developers Less of an advantage over competing +non-free programs. These disadvantages are the reason we use the ordinary +General Public License for many libraries. However, the Lesser license provides +advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the +widest possible use of a certain library, so that it becomes a de-facto standard. +To achieve this, non-free programs must be allowed to use the library. A more +frequent case is that a free library does the same job as widely used non-free +libraries. In this case, there is little to gain by limiting the free library +to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs +enables a greater number of people to use a large body of free software. For +example, permission to use the GNU C Library in non-free programs enables +many more people to use the whole GNU operating system, as well as its variant, +the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' +freedom, it does ensure that the user of a program that is linked with the +Library has the freedom and the wherewithal to run that program using a modified +version of the Library. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, whereas the latter must be combined with the library in +order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program +which contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Lesser General +Public License (also called "this License"). Each licensee is addressed as +"you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, 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 library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with +the Library. + +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 Library or any portion of it, +thus forming a work based on the Library, 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) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, 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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you 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. + +If distribution of 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 satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (1) uses at run time a copy of the library +already present on the user's computer system, rather than copying library +functions into the executable, and (2) will operate properly with a modified +version of the library, if the user installs one, as long as the modified +version is interface-compatible with the version that the work was made with. + +c) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +d) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +e) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the materials to be 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. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. 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 not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library 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. + +9. 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 +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library 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 with this License. + +11. 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 Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +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 Library. + +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. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library 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. + +13. The Free Software Foundation may publish revised and/or new versions of +the 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 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 +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, 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 + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. 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 library is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 2.1 of the License, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this library; if not, 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. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +< signature of Ty Coon > , 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/cmake/QtBaseHelpers.cmake b/cmake/QtBaseHelpers.cmake index 4b48f8f1766..bd59bff8026 100644 --- a/cmake/QtBaseHelpers.cmake +++ b/cmake/QtBaseHelpers.cmake @@ -146,6 +146,62 @@ macro(qt_internal_qtbase_install_mkspecs) endforeach() endmacro() +function(qt_internal_qtbase_install_wayland_files) + qt_path_join(wlprotocols_build_dir + ${QT_BUILD_DIR} + ${INSTALL_SHAREDIR}/qt6/wayland/protocols) + qt_path_join(wlprotocols_install_dir + ${QT_INSTALL_DIR} + ${INSTALL_SHAREDIR}/qt6/wayland/protocols) + + file(GLOB wlprotocols_subdirs + LIST_DIRECTORIES TRUE + "${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/*") + foreach(entry IN LISTS wlprotocols_subdirs) + if (IS_DIRECTORY "${entry}") + qt_copy_or_install(DIRECTORY "${entry}" + DESTINATION "${wlprotocols_install_dir}" + USE_SOURCE_PERMISSIONS) + if(QT_WILL_INSTALL) + file(COPY "${entry}" DESTINATION "${wlprotocols_build_dir}") + endif() + else() + qt_copy_or_install(FILES "${entry}" + DESTINATION "${wlprotocols_install_dir}") + if(QT_WILL_INSTALL) + file(COPY "${entry}" DESTINATION "${wlprotocols_build_dir}") + endif() + endif() + endforeach() + + qt_path_join(wlextensions_build_dir + ${QT_BUILD_DIR} + ${INSTALL_SHAREDIR}/qt6/wayland/extensions) + qt_path_join(wlextensions_install_dir + ${QT_INSTALL_DIR} + ${INSTALL_SHAREDIR}/qt6/wayland/extensions) + + file(GLOB wlextensions_subdirs + LIST_DIRECTORIES TRUE + "${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/extensions/*") + foreach(entry IN LISTS wlextensions_subdirs) + if (IS_DIRECTORY "${entry}") + qt_copy_or_install(DIRECTORY "${entry}" + DESTINATION "${wlextensions_install_dir}" + USE_SOURCE_PERMISSIONS) + if(QT_WILL_INSTALL) + file(COPY "${entry}" DESTINATION "${wlextensions_build_dir}") + endif() + else() + qt_copy_or_install(FILES "${entry}" + DESTINATION "${wlextensions_install_dir}") + if(QT_WILL_INSTALL) + file(COPY "${entry}" DESTINATION "${wlextensions_build_dir}") + endif() + endif() + endforeach() +endfunction() + macro(qt_internal_qtbase_build_repo) qt_internal_qtbase_pre_project_setup() @@ -219,6 +275,8 @@ macro(qt_internal_qtbase_build_repo) qt_internal_qtbase_install_mkspecs() endif() + qt_internal_qtbase_install_wayland_files() + qt_build_repo_post_process() qt_build_repo_impl_tests() diff --git a/src/3rdparty/wayland/extensions/README.md b/src/3rdparty/wayland/extensions/README.md new file mode 100644 index 00000000000..8024ffa80e7 --- /dev/null +++ b/src/3rdparty/wayland/extensions/README.md @@ -0,0 +1,24 @@ +# Internal Qt protocol extensions + +The protocol extensions in this folder are considered implementation details of +Qt. I.e. they may removed, renamed or changed without warning. + +However, starting with Qt 5.4, we promise not to break backwards compatibility +without renaming (or removing) the protocol. I.e., if your client sees a global +from one of these extensions, it can safely bind to it: the existing events +and requests will always take the same number of arguments, regardless of +compositor version. + +This is important also within a Qt-only scope if there are multiple versions of +Qt on the system. Consider for instance an application statically linked to Qt +(such as Qt Creator) running against a Qt compositor installed by the distro). +In such cases we don't want the compositor and client to disagree on the +protocol definition. + +## Protocol versioning. + +Protocol extensions in this folder should be versioned (e.g. `zqt_key_v1`). +If it is necessary to break protocol compatibility, they will be renamed by +incrementing the version number. For legacy reasons, there are also unversioned +protocols in this folder. Those protocols should be renamed to be versioned +if compatibility is broken. diff --git a/src/3rdparty/wayland/extensions/REUSE.toml b/src/3rdparty/wayland/extensions/REUSE.toml new file mode 100644 index 00000000000..7debc4f4e09 --- /dev/null +++ b/src/3rdparty/wayland/extensions/REUSE.toml @@ -0,0 +1,8 @@ +version = 1 + +[[annotations]] +path = "qt-text-input-method-unstable-v1.xml" +precedence = "closest" +comment = "License is in full text in file. Should it be changed?" +SPDX-FileCopyrightText = "Copyright © 2020 The Qt Company Ltd." +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/extensions/brcm.xml b/src/3rdparty/wayland/extensions/brcm.xml new file mode 100644 index 00000000000..39e059604c1 --- /dev/null +++ b/src/3rdparty/wayland/extensions/brcm.xml @@ -0,0 +1,21 @@ + + + + + Copyright (C) 2015 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + + + + + + + + + + + + + diff --git a/src/3rdparty/wayland/extensions/drm-egl-server-buffer.xml b/src/3rdparty/wayland/extensions/drm-egl-server-buffer.xml new file mode 100644 index 00000000000..9fc9dd908f7 --- /dev/null +++ b/src/3rdparty/wayland/extensions/drm-egl-server-buffer.xml @@ -0,0 +1,23 @@ + + + + Copyright (C) 2015 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + + + + + + + + + + + + + + + + + diff --git a/src/3rdparty/wayland/extensions/hardware-integration.xml b/src/3rdparty/wayland/extensions/hardware-integration.xml new file mode 100644 index 00000000000..19e6f0c5192 --- /dev/null +++ b/src/3rdparty/wayland/extensions/hardware-integration.xml @@ -0,0 +1,21 @@ + + + + Copyright (C) 2015 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + + + + Using this protocol the compositor can signal to clients which buffer sharing extensions + the client should use + + + + + + + + + + diff --git a/src/3rdparty/wayland/extensions/libhybris-egl-server-buffer.xml b/src/3rdparty/wayland/extensions/libhybris-egl-server-buffer.xml new file mode 100644 index 00000000000..605ec65d447 --- /dev/null +++ b/src/3rdparty/wayland/extensions/libhybris-egl-server-buffer.xml @@ -0,0 +1,32 @@ + + + + Copyright (C) 2014 Jolla Ltd + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/3rdparty/wayland/extensions/qt-dmabuf-server-buffer.xml b/src/3rdparty/wayland/extensions/qt-dmabuf-server-buffer.xml new file mode 100644 index 00000000000..c8c142f51a0 --- /dev/null +++ b/src/3rdparty/wayland/extensions/qt-dmabuf-server-buffer.xml @@ -0,0 +1,27 @@ + + + + Copyright (C) 2018 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + + + This protocol is used internally by Qt for implementing the + qt_server_buffer extension on hardware that supports Linux dma-buf. + + This protocol is not part of the Qt API. It exists purely as an + implementation detail and may change from version to + version without notice, or even be removed. + + + + + + + + + + + + + diff --git a/src/3rdparty/wayland/extensions/qt-text-input-method-unstable-v1.xml b/src/3rdparty/wayland/extensions/qt-text-input-method-unstable-v1.xml new file mode 100644 index 00000000000..2e8cd4ec3a6 --- /dev/null +++ b/src/3rdparty/wayland/extensions/qt-text-input-method-unstable-v1.xml @@ -0,0 +1,301 @@ + + + + + Copyright © 2020 The Qt Company Ltd. + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The qt_text_input_method interface represents input method events + associated with a seat, and is intended to exactly match the + internal events of the Qt framework. + + + + + Destroy the qt_text_input_method object. + + + + + + Enable text input in a surface (usually when a text entry inside of it + has focus). + + This can be called before or after a surface gets text (or keyboard) + focus via the enter event. Text input to a surface is only active + when it has the current text (or keyboard) focus and is enabled. + + + + + + + Disable text input in a surface (typically when there is no focus on any + text entry inside the surface). + + + + + + + Notification that this seat's text-input focus is on a certain surface. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. + + + + + + + Notification that this seat's text-input focus is no longer on + a certain surface. + + The leave notification is sent before the enter notification + for the new focus. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. + + + + + + + A QKeyEvent has been sent from the input method. + + + + + + + + + + + + + + + Starts an input method event. This can be followed by + any number of input_method_event_attribute events + and will always be finished by an end_input_method_event. + + + + + + + + Appends an attribute to the input method event with + the given serial. Must be preceded by a start_input_method_event + and concluded by a an end_input_method_event. See documentation + of QInputMethodEvent for details on the attributes. + + + + + + + + + + + Concludes a previously started input method event. Together with + the preceding input_method_event_attribute events with the same + serial, this should be converted into a QInputMethodEvent. + + + + + + + + + + + Event to notify client that the visibility of the input method has + been changed. + + + + + + + Event to notify client that the keyboard rectangle of the input method + has been changed. + + + + + + + + + + Event to notify client that the locale of the input method + has been changed. + + + + + + + Event to notify client that the input direction of the input method + has been changed. + + + + + + + Request for the input method to reset. Corresponds to QInputMethod::reset(). + + + + + + Request for the input method to commit its current content. Corresponds to QInputMethod::commit(). + + + + + + Passes a mouse click or context menu request from the client to the server. Corresponds to QInputMethod::invokeAction(). + + + + + + + + Notifies the server of the client's current input method hints. + + + + + + + Notifies the server of the client's current cursor rectangle. + + + + + + + + + + Notifies the server of the client's current cursor position. + + + + + + + Notifies the server of the client's current surrounding text and its offset in the + complete text. + + + + + + + + Notifies the server of the client's current anchor position. + + + + + + + Notifies the server of the client's current absolute cursor position. + + + + + + + Notifies the server of the client's current preferred language. + + + + + + + Starts an update sequence to notify the server that the client's state has + changed. This is followed by any number of update requests for specific + parts of the state and concluded by an end_update request. + + + + + + + Concludes the previously started update request. + + + + + + Requests that the input panel of the input method is visible. + + + + + + Requests that the input panel of the input method is not visible. + + + + + + Sent on receipt of an end_input_method_event to acknowledge that + the client has received and handled the event. + + + + + + + Manages qt_text_input_method objects. + + + + + Destroy the qt_text_input_method_manager object. + + + + + + Creates a new text-input-method object for a given seat. + + + + + + diff --git a/src/3rdparty/wayland/extensions/qt-texture-sharing-unstable-v1.xml b/src/3rdparty/wayland/extensions/qt-texture-sharing-unstable-v1.xml new file mode 100644 index 00000000000..ae3ab9ea970 --- /dev/null +++ b/src/3rdparty/wayland/extensions/qt-texture-sharing-unstable-v1.xml @@ -0,0 +1,24 @@ + + + + Copyright (C) 2019 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + + + + + + + + + + + + + + + + + + diff --git a/src/3rdparty/wayland/extensions/qt-vulkan-server-buffer-unstable-v1.xml b/src/3rdparty/wayland/extensions/qt-vulkan-server-buffer-unstable-v1.xml new file mode 100644 index 00000000000..08a27e5d303 --- /dev/null +++ b/src/3rdparty/wayland/extensions/qt-vulkan-server-buffer-unstable-v1.xml @@ -0,0 +1,31 @@ + + + + Copyright (C) 2019 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + + + This protocol is used internally by Qt for implementing the + qt_server_buffer extension on hardware that supports Vulkan external memory . + + This protocol is not part of the Qt API. It exists purely as an + implementation detail and may change from version to + version without notice, or even be removed. + + + + Informs the client about a newly created server buffer. + The "fd" argument is a POSIX file descriptor representing the + underlying resources of a Vulkan device memory object as defined + in the GL_EXT_memory_object_fd extension. + + + + + + + + + + diff --git a/src/3rdparty/wayland/extensions/qt-windowmanager.xml b/src/3rdparty/wayland/extensions/qt-windowmanager.xml new file mode 100644 index 00000000000..bd6e88f74db --- /dev/null +++ b/src/3rdparty/wayland/extensions/qt-windowmanager.xml @@ -0,0 +1,25 @@ + + + + Copyright (C) 2015 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + + + + + If remaining is not 0 this is a multipart request, and + the server must concatenate subsequent requst urls to and + including a request where remaining is set to 0, before + it can handle the url. + + + + + + + + + + + diff --git a/src/3rdparty/wayland/extensions/server-buffer-extension.xml b/src/3rdparty/wayland/extensions/server-buffer-extension.xml new file mode 100644 index 00000000000..75c7f624d50 --- /dev/null +++ b/src/3rdparty/wayland/extensions/server-buffer-extension.xml @@ -0,0 +1,20 @@ + + + + Copyright (C) 2015 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + + + + Server buffer is an extension which makes it possible to share a + buffer created by the compositor to share it with arbitrary + clients. These types of buffers are intended to be read only + buffers for clients, but this is an implementation detail. + + Server buffers main purpose is to help implement caches + + + + + diff --git a/src/3rdparty/wayland/extensions/shm-emulation-server-buffer.xml b/src/3rdparty/wayland/extensions/shm-emulation-server-buffer.xml new file mode 100644 index 00000000000..9850f915035 --- /dev/null +++ b/src/3rdparty/wayland/extensions/shm-emulation-server-buffer.xml @@ -0,0 +1,30 @@ + + + + Copyright (C) 2017 The Qt Company Ltd. + SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + + + This is software-based implementation of the qt_server_buffer extension. + It is intended for testing and debugging purposes only. + + + + + + + + Informs the client about a newly created server buffer. + The "key" argument contains a QSharedMemory key. + + + + + + + + + + + diff --git a/src/3rdparty/wayland/protocols/MIT_LICENSE.txt b/src/3rdparty/wayland/protocols/MIT_LICENSE.txt new file mode 100644 index 00000000000..edc2e5fb5e0 --- /dev/null +++ b/src/3rdparty/wayland/protocols/MIT_LICENSE.txt @@ -0,0 +1,18 @@ +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/3rdparty/wayland/protocols/appmenu/LGPL-2.1-or-later.txt b/src/3rdparty/wayland/protocols/appmenu/LGPL-2.1-or-later.txt new file mode 100644 index 00000000000..04bb156e77d --- /dev/null +++ b/src/3rdparty/wayland/protocols/appmenu/LGPL-2.1-or-later.txt @@ -0,0 +1,468 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 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. + +[This is the first released version of the Lesser GPL. It also counts as the +successor of the GNU Library Public License, version 2, hence the version +number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Lesser General Public License, applies to some specially +designated software packages--typically libraries--of the Free Software Foundation +and other authors who decide to use it. You can use it too, but we suggest +you first think carefully about whether this license or the ordinary General +Public License is the better strategy to use in any particular case, based +on the explanations below. + +When we speak of free software, we are referring to freedom of use, 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 and use pieces of it in new free programs; and that +you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors +to deny you these rights or to ask you to surrender these rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +other code with the library, you must provide complete object files to the +recipients, so that they can relink them with the library after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, +and (2) we offer you this license, which gives you legal permission to copy, +distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no +warranty for the free library. Also, if the library is modified by someone +else and passed on, the recipients should know that what they have is not +the original version, so that the original author's reputation will not be +affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free +program. We wish to make sure that a company cannot effectively restrict the +users of a free program by obtaining a restrictive license from a patent holder. +Therefore, we insist that any patent license obtained for a version of the +library must be consistent with the full freedom of use specified in this +license. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License. This license, the GNU Lesser General Public License, +applies to certain designated libraries, and is quite different from the ordinary +General Public License. We use this license for certain libraries in order +to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared +library, the combination of the two is legally speaking a combined work, a +derivative of the original library. The ordinary General Public License therefore +permits such linking only if the entire combination fits its criteria of freedom. +The Lesser General Public License permits more lax criteria for linking other +code with the library. + +We call this license the "Lesser" General Public License because it does Less +to protect the user's freedom than the ordinary General Public License. It +also provides other free software developers Less of an advantage over competing +non-free programs. These disadvantages are the reason we use the ordinary +General Public License for many libraries. However, the Lesser license provides +advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the +widest possible use of a certain library, so that it becomes a de-facto standard. +To achieve this, non-free programs must be allowed to use the library. A more +frequent case is that a free library does the same job as widely used non-free +libraries. In this case, there is little to gain by limiting the free library +to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs +enables a greater number of people to use a large body of free software. For +example, permission to use the GNU C Library in non-free programs enables +many more people to use the whole GNU operating system, as well as its variant, +the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' +freedom, it does ensure that the user of a program that is linked with the +Library has the freedom and the wherewithal to run that program using a modified +version of the Library. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, whereas the latter must be combined with the library in +order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program +which contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Lesser General +Public License (also called "this License"). Each licensee is addressed as +"you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, 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 library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with +the Library. + +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 Library or any portion of it, +thus forming a work based on the Library, 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) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, 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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you 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. + +If distribution of 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 satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (1) uses at run time a copy of the library +already present on the user's computer system, rather than copying library +functions into the executable, and (2) will operate properly with a modified +version of the library, if the user installs one, as long as the modified +version is interface-compatible with the version that the work was made with. + +c) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +d) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +e) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the materials to be 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. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. 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 not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library 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. + +9. 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 +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library 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 with this License. + +11. 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 Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +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 Library. + +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. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library 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. + +13. The Free Software Foundation may publish revised and/or new versions of +the 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 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 +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, 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 + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. 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 library is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 2.1 of the License, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this library; if not, 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. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +< signature of Ty Coon > , 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/src/3rdparty/wayland/protocols/appmenu/REUSE.toml b/src/3rdparty/wayland/protocols/appmenu/REUSE.toml new file mode 100644 index 00000000000..d9c4f74ae53 --- /dev/null +++ b/src/3rdparty/wayland/protocols/appmenu/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = ["LGPL-2.1-or-later.txt", "appmenu.xml"] +precedence = "closest" +SPDX-FileCopyrightText = ["Copyright 2017 David Edmundson"] +SPDX-License-Identifier = "LGPL-2.1-or-later" diff --git a/src/3rdparty/wayland/protocols/appmenu/appmenu.xml b/src/3rdparty/wayland/protocols/appmenu/appmenu.xml new file mode 100644 index 00000000000..6e8d33b18dd --- /dev/null +++ b/src/3rdparty/wayland/protocols/appmenu/appmenu.xml @@ -0,0 +1,36 @@ + + + + + + This interface allows a client to link a window (or wl_surface) to an com.canonical.dbusmenu + interface registered on DBus. + + + + + + + + + The DBus service name and object path where the appmenu interface is present + The object should be registered on the session bus before sending this request. + If not applicable, clients should remove this object. + + + + Set or update the service name and object path. + Strings should be formatted in Latin-1 matching the relevant DBus specifications. + + + + + + + + + diff --git a/src/3rdparty/wayland/protocols/appmenu/qt_attribution.json b/src/3rdparty/wayland/protocols/appmenu/qt_attribution.json new file mode 100644 index 00000000000..53f10b090d1 --- /dev/null +++ b/src/3rdparty/wayland/protocols/appmenu/qt_attribution.json @@ -0,0 +1,16 @@ +{ + "Id": "appmenu", + "Name": "Wayland KDE DBus Menu Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "appmenu.xml", + + "Description": "Attach a dbus menu to a window", + "Homepage": "https://kde.org", + "Version": "1", + "DownloadLocation": "https://invent.kde.org/libraries/plasma-wayland-protocols/-/blob/v1.14.0/src/protocols/appmenu.xml", + "LicenseId": "LGPL-2.1-or-later", + "License": "GNU Lesser General Public License 2.1 or later", + "LicenseFile": "LGPL-2.1-or-later.txt", + "Copyright": "Copyright 2017 David Edmundson" +} diff --git a/src/3rdparty/wayland/protocols/color-management/REUSE.toml b/src/3rdparty/wayland/protocols/color-management/REUSE.toml new file mode 100644 index 00000000000..c7b978663b1 --- /dev/null +++ b/src/3rdparty/wayland/protocols/color-management/REUSE.toml @@ -0,0 +1,11 @@ +version = 1 + +[[annotations]] +path = "xx-color-management-v4.xml" +precedence = "closest" +SPDX-FileCopyrightText = ["Copyright 2019 Sebastian Wick", + "Copyright 2019 Erwin Burema", + "Copyright 2020 AMD", + "Copyright 2020-2024 Collabora, Ltd.", + "Copyright 2024 Xaver Hugl"] +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/color-management/qt_attribution.json b/src/3rdparty/wayland/protocols/color-management/qt_attribution.json new file mode 100644 index 00000000000..246e9df70fa --- /dev/null +++ b/src/3rdparty/wayland/protocols/color-management/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-color-management-protocol", + "Name": "Wayland Color Management Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin.", + "Files": "xx-color-management-v4.xml", + + "Description": "An extension to use different colorspaces from sRGB", + "Homepage": "https://wayland.freedesktop.org", + "Version": "experimental v4", + "DownloadLocation": "https://gitlab.freedesktop.org/swick/wayland-protocols/-/blob/708a8b4119d4072820158a115166598733d378f4/staging/color-management/xx-color-management-v4.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright 2019 Sebastian Wick\nCopyright 2019 Erwin Burema\nCopyright 2020 AMD\nCopyright 2020-2024 Collabora, Ltd.\nCopyright 2024 Xaver Hugl" + } +] diff --git a/src/3rdparty/wayland/protocols/color-management/xx-color-management-v4.xml b/src/3rdparty/wayland/protocols/color-management/xx-color-management-v4.xml new file mode 100644 index 00000000000..eab84dfd992 --- /dev/null +++ b/src/3rdparty/wayland/protocols/color-management/xx-color-management-v4.xml @@ -0,0 +1,1453 @@ + + + + Copyright 2019 Sebastian Wick + Copyright 2019 Erwin Burema + Copyright 2020 AMD + Copyright 2020-2024 Collabora, Ltd. + Copyright 2024 Xaver Hugl + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + The aim of the color management extension is to allow clients to know + the color properties of outputs, and to tell the compositor about the color + properties of their content on surfaces. Doing this enables a compositor + to perform automatic color management of content for different outputs + according to how content is intended to look like. + + The color properties are represented as an image description object which + is immutable after it has been created. A wl_output always has an + associated image description that clients can observe. A wl_surface + always has an associated preferred image description as a hint chosen by + the compositor that clients can also observe. Clients can set an image + description on a wl_surface to denote the color characteristics of the + surface contents. + + An image description includes SDR and HDR colorimetry and encoding, HDR + metadata, and viewing environment parameters. An image description does + not include the properties set through color-representation extension. + It is expected that the color-representation extension is used in + conjunction with the color management extension when necessary, + particularly with the YUV family of pixel formats. + + Recommendation ITU-T H.273 + "Coding-independent code points for video signal type identification" + shall be referred to as simply H.273 here. + + The color-and-hdr repository + (https://gitlab.freedesktop.org/pq/color-and-hdr) contains + background information on the protocol design and legacy color management. + It also contains a glossary, learning resources for digital color, tools, + samples and more. + + The terminology used in this protocol is based on common color science and + color encoding terminology where possible. The glossary in the color-and-hdr + repository shall be the authority on the definition of terms in this + protocol. + + + + + A global interface used for getting color management extensions for + wl_surface and wl_output objects, and for creating client defined image + description objects. The extension interfaces allow + getting the image description of outputs and setting the image + description of surfaces. + + + + + Destroy the xx_color_manager_v4 object. This does not affect any other + objects in any way. + + + + + + + + + + + See the ICC.1:2022 specification from the International Color Consortium + for more details about rendering intents. + + The principles of ICC defined rendering intents apply with all types of + image descriptions, not only those with ICC file profiles. + + Compositors must support the perceptual rendering intent. Other + rendering intents are optional. + + + + + + + + + + + + + + + + + + + + The compositor supports set_mastering_display_primaries request with a + target color volume fully contained inside the primary color volume. + + + + + The compositor additionally supports target color volumes that + extend outside of the primary color volume. + + This can only be advertised if feature set_mastering_display_primaries + is supported as well. + + + + + + + Named color primaries used to encode well-known sets of primaries. H.273 + is the authority, when it comes to the exact values of primaries and + authoritative specifications, where an equivalent code point exists. + + Descriptions do list the specifications for convenience. + + + + + Color primaries as defined by + - Rec. ITU-R BT.709-6 + - Rec. ITU-R BT.1361-0 conventional colour gamut system and extended + colour gamut system (historical) + - IEC 61966-2-1 sRGB or sYCC + - IEC 61966-2-4 + - Society of Motion Picture and Television Engineers (SMPTE) RP 177 + (1993) Annex B + Equivalent to H.273 ColourPrimaries code point 1. + + + + + Color primaries as defined by + - Rec. ITU-R BT.470-6 System M (historical) + - United States National Television System Committee 1953 + Recommendation for transmission standards for color television + - United States Federal Communications Commission (2003) Title 47 Code + of Federal Regulations 73.682 (a)(20) + Equivalent to H.273 ColourPrimaries code point 4. + + + + + Color primaries as defined by + - Rec. ITU-R BT.470-6 System B, G (historical) + - Rec. ITU-R BT.601-7 625 + - Rec. ITU-R BT.1358-0 625 (historical) + - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + Equivalent to H.273 ColourPrimaries code point 5. + + + + + Color primaries as defined by + - Rec. ITU-R BT.601-7 525 + - Rec. ITU-R BT.1358-1 525 or 625 (historical) + - Rec. ITU-R BT.1700-0 NTSC + - SMPTE 170M (2004) + - SMPTE 240M (1999) (historical) + Equivalent to H.273 ColourPrimaries code point 6 and 7. + + + + + Color primaries as defined by H.273 for generic film. + Equivalent to H.273 ColourPrimaries code point 8. + + + + + Color primaries as defined by + - Rec. ITU-R BT.2020-2 + - Rec. ITU-R BT.2100-0 + Equivalent to H.273 ColourPrimaries code point 9. + + + + + Color primaries as defined as the maximum of the CIE 1931 XYZ color + space by + - SMPTE ST 428-1 + - (CIE 1931 XYZ as in ISO 11664-1) + Equivalent to H.273 ColourPrimaries code point 10. + + + + + Color primaries as defined by Digital Cinema System and published in + SMPTE RP 431-2 (2011). Equivalent to H.273 ColourPrimaries code point + 11. + + + + + Color primaries as defined by Digital Cinema System and published in + SMPTE EG 432-1 (2010). + Equivalent to H.273 ColourPrimaries code point 12. + + + + + Color primaries as defined by Adobe as "Adobe RGB" and later published + by ISO 12640-4 (2011). + + + + + + + Named transfer functions used to encode well-known transfer + characteristics. H.273 is the authority, when it comes to the exact + formulas and authoritative specifications, where an equivalent code + point exists. + + Descriptions do list the specifications for convenience. + + + + + Transfer characteristics as defined by + - Rec. ITU-R BT.709-6 + - Rec. ITU-R BT.1361-0 conventional colour gamut system (historical) + Equivalent to H.273 TransferCharacteristics code point 1, 6, 14, 15. + + + + + Transfer characteristics as defined by + - Rec. ITU-R BT.470-6 System M (historical) + - United States National Television System Committee 1953 + Recommendation for transmission standards for color television + - United States Federal Communications Commission (2003) Title 47 Code + of Federal Regulations 73.682 (a) (20) + - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + Equivalent to H.273 TransferCharacteristics code point 4. + + + + + Transfer characteristics as defined by + - Rec. ITU-R BT.470-6 System B, G (historical) + Equivalent to H.273 TransferCharacteristics code point 5. + + + + + Transfer characteristics as defined by + - SMPTE ST 240 (1999) + Equivalent to H.273 TransferCharacteristics code point 7. + + + + + Linear transfer characteristics. + Equivalent to H.273 TransferCharacteristics code point 8. + + + + + Logarithmic transfer characteristic (100:1 range). + Equivalent to H.273 TransferCharacteristics code point 9. + + + + + Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range). + Equivalent to H.273 TransferCharacteristics code point 10. + + + + + Transfer characteristics as defined by + - IEC 61966-2-4 + Equivalent to H.273 TransferCharacteristics code point 11. + + + + + Transfer characteristics as defined by + - Rec. ITU-R BT.1361-0 extended colour gamut system (historical) + Equivalent to H.273 TransferCharacteristics code point 12. + + + + + Transfer characteristics as defined by + - IEC 61966-2-1 sRGB + Equivalent to H.273 TransferCharacteristics code point 13 with + MatrixCoefficients set to 0. + + + + + Transfer characteristics as defined by + - IEC 61966-2-1 sYCC + Equivalent to H.273 TransferCharacteristics code point 13 with + MatrixCoefficients set to anything but 0. + + + + + Transfer characteristics as defined by + - SMPTE ST 2084 (2014) for 10-, 12-, 14- and 16-bit systems + - Rec. ITU-R BT.2100-2 perceptual quantization (PQ) system + Equivalent to H.273 TransferCharacteristics code point 16. + + This TF implies these default luminances + - primary color volume minimum: 0.005 cd/m² + - primary color volume maximum: 10000 cd/m² + - reference white: 203 cd/m² + + + + + Transfer characteristics as defined by + - SMPTE ST 428-1 (2019) + Equivalent to H.273 TransferCharacteristics code point 17. + + + + + Transfer characteristics as defined by + - ARIB STD-B67 (2015) + - Rec. ITU-R BT.2100-2 hybrid log-gamma (HLG) system + Equivalent to H.273 TransferCharacteristics code point 18. + + This TF implies these default luminances + - primary color volume minimum: 0.005 cd/m² + - primary color volume maximum: 1000 cd/m² + - reference white: 203 cd/m² + Note: HLG is a scene referred signal. All absolute luminance values + used here for HLG assume a 1000 cd/m² display. + + + + + + + This creates a new xx_color_management_output_v4 object for the + given wl_output. + + See the xx_color_management_output_v4 interface for more details. + + + + + + + + + If a xx_color_management_surface_v4 object already exists for the given + wl_surface, the protocol error surface_exists is raised. + + This creates a new color xx_color_management_surface_v4 object for the + given wl_surface. + + See the xx_color_management_surface_v4 interface for more details. + + + + + + + + + This creates a new color xx_color_management_feedback_surface_v4 object + for the given wl_surface. + + See the xx_color_management_feedback_surface_v4 interface for more + details. + + + + + + + + + Makes a new ICC-based image description creator object with all + properties initially unset. The client can then use the object's + interface to define all the required properties for an image description + and finally create a xx_image_description_v4 object. + + This request can be used when the compositor advertises + xx_color_manager_v4.feature.icc_v2_v4. + Otherwise this request raises the protocol error unsupported_feature. + + + + + + + + Makes a new parametric image description creator object with all + properties initially unset. The client can then use the object's + interface to define all the required properties for an image description + and finally create a xx_image_description_v4 object. + + This request can be used when the compositor advertises + xx_color_manager_v4.feature.parametric. + Otherwise this request raises the protocol error unsupported_feature. + + + + + + + + When this object is created, it shall immediately send this event once + for each rendering intent the compositor supports. + + + + + + + + When this object is created, it shall immediately send this event once + for each compositor supported feature listed in the enumeration. + + + + + + + + When this object is created, it shall immediately send this event once + for each named transfer function the compositor supports with the + parametric image description creator. + + + + + + + + When this object is created, it shall immediately send this event once + for each named set of primaries the compositor supports with the + parametric image description creator. + + + + + + + + + A xx_color_management_output_v4 describes the color properties of an + output. + + The xx_color_management_output_v4 is associated with the wl_output global + underlying the wl_output object. Therefore the client destroying the + wl_output object has no impact, but the compositor removing the output + global makes the xx_color_management_output_v4 object inert. + + + + + Destroy the color xx_color_management_output_v4 object. This does not + affect any remaining protocol objects. + + + + + + This event is sent whenever the image description of the output changed, + followed by one wl_output.done event common to output events across all + extensions. + + If the client wants to use the updated image description, it needs to do + get_image_description again, because image description objects are + immutable. + + + + + + This creates a new xx_image_description_v4 object for the current image + description of the output. There always is exactly one image description + active for an output so the client should destroy the image description + created by earlier invocations of this request. This request is usually + sent as a reaction to the image_description_changed event or when + creating a xx_color_management_output_v4 object. + + The image description of an output represents the color encoding the + output expects. There might be performance and power advantages, as well + as improved color reproduction, if a content update matches the image + description of the output it is being shown on. If a content update is + shown on any other output than the one it matches the image description + of, then the color reproduction on those outputs might be considerably + worse. + + The created xx_image_description_v4 object preserves the image + description of the output from the time the object was created. + + The resulting image description object allows get_information request. + + If this protocol object is inert, the resulting image description object + shall immediately deliver the xx_image_description_v4.failed event with + the no_output cause. + + If the interface version is inadequate for the output's image + description, meaning that the client does not support all the events + needed to deliver the crucial information, the resulting image + description object shall immediately deliver the + xx_image_description_v4.failed event with the low_version cause. + + Otherwise the object shall immediately deliver the ready event. + + + + + + + + + A xx_color_management_surface_v4 allows the client to set the color + space and HDR properties of a surface. + + If the wl_surface associated with the xx_color_management_surface_v4 is + destroyed, the xx_color_management_surface_v4 object becomes inert. + + + + + Destroy the xx_color_management_surface_v4 object and do the same as + unset_image_description. + + + + + + + + + + + + Set the image description of the underlying surface. The image + description and rendering intent are double-buffered state, see + wl_surface.commit. + + It is the client's responsibility to understand the image description + it sets on a surface, and to provide content that matches that image + description. Compositors might convert images to match their own or any + other image descriptions. + + Image description whose creation gracefully failed (received + xx_image_description_v4.failed) are forbidden in this request, and in + such case the protocol error image_description is raised. + + All image descriptions whose creation succeeded (received + xx_image_description_v4.ready) are allowed and must always be accepted + by the compositor. + + A rendering intent provides the client's preference on how content + colors should be mapped to each output. The render_intent value must + be one advertised by the compositor with + xx_color_manager_v4.render_intent event, otherwise the protocol error + render_intent is raised. + + By default, a surface does not have an associated image description + nor a rendering intent. The handling of color on such surfaces is + compositor implementation defined. Compositors should handle such + surfaces as sRGB but may handle them differently if they have specific + requirements. + + + + + + + + + This request removes any image description from the surface. See + set_image_description for how a compositor handles a surface without + an image description. This is double-buffered state, see + wl_surface.commit. + + + + + + + A xx_color_management_feedback_surface_v4 allows the client to get the + preferred color description of a surface. + + If the wl_surface associated with this object is destroyed, the + xx_color_management_feedback_surface_v4 object becomes inert. + + + + + Destroy the xx_color_management_feedback_surface_v4 object. + + + + + + + + + + + The preferred image description is the one which likely has the most + performance and/or quality benefits for the compositor if used by the + client for its wl_surface contents. This event is sent whenever the + compositor changes the wl_surface's preferred image description. + + This event is merely a notification. When the client wants to know + what the preferred image description is, it shall use the get_preferred + request. + + The preferred image description is not automatically used for anything. + It is only a hint, and clients may set any valid image description with + set_image_description but there might be performance and color accuracy + improvements by providing the wl_surface contents in the preferred + image description. Therefore clients that can, should render according + to the preferred image description + + + + + + If this protocol object is inert, the protocol error inert is raised. + + The preferred image description represents the compositor's preferred + color encoding for this wl_surface at the current time. There might be + performance and power advantages, as well as improved color + reproduction, if the image description of a content update matches the + preferred image description. + + This creates a new xx_image_description_v4 object for the currently + preferred image description for the wl_surface. The client should + stop using and destroy the image descriptions created by earlier + invocations of this request for the associated wl_surface. + This request is usually sent as a reaction to the preferred_changed + event or when creating a xx_color_management_feedback_surface_v4 object + if the client is capable of adapting to image descriptions. + + The created xx_image_description_v4 object preserves the preferred image + description of the wl_surface from the time the object was created. + + The resulting image description object allows get_information request. + + If the interface version is inadequate for the preferred image + description, meaning that the client does not support all the + events needed to deliver the crucial information, the resulting image + description object shall immediately deliver the + xx_image_description_v4.failed event with the low_version cause, + otherwise the object shall immediately deliver the ready event. + + + + + + + + + This type of object is used for collecting all the information required + to create a xx_image_description_v4 object from an ICC file. A complete + set of required parameters consists of these properties: + - ICC file + + Each required property must be set exactly once if the client is to create + an image description. The set requests verify that a property was not + already set. The create request verifies that all required properties are + set. There may be several alternative requests for setting each property, + and in that case the client must choose one of them. + + Once all properties have been set, the create request must be used to + create the image description object, destroying the creator in the + process. + + + + + + + + + + + + + + + Create an image description object based on the ICC information + previously set on this object. A compositor must parse the ICC data in + some undefined but finite amount of time. + + The completeness of the parameter set is verified. If the set is not + complete, the protocol error incomplete_set is raised. For the + definition of a complete set, see the description of this interface. + + If the particular combination of the information is not supported + by the compositor, the resulting image description object shall + immediately deliver the xx_image_description_v4.failed event with the + 'unsupported' cause. If a valid image description was created from the + information, the xx_image_description_v4.ready event will eventually + be sent instead. + + This request destroys the xx_image_description_creator_icc_v4 object. + + The resulting image description object does not allow get_information + request. + + + + + + + + Sets the ICC profile file to be used as the basis of the image + description. + + The data shall be found through the given fd at the given offset, having + the given length. The fd must seekable and readable. Violating these + requirements raises the bad_fd protocol error. + + If reading the data fails due to an error independent of the client, the + compositor shall send the xx_image_description_v4.failed event on the + created xx_image_description_v4 with the 'operating_system' cause. + + The maximum size of the ICC profile is 4 MB. If length is greater than + that or zero, the protocol error bad_size is raised. If offset + length + exceeds the file size, the protocol error out_of_file is raised. + + A compositor may read the file at any time starting from this request + and only until whichever happens first: + - If create request was issued, the xx_image_description_v4 object + delivers either failed or ready event; or + - if create request was not issued, this + xx_image_description_creator_icc_v4 object is destroyed. + + A compositor shall not modify the contents of the file, and the fd may + be sealed for writes and size changes. The client must ensure to its + best ability that the data does not change while the compositor is + reading it. + + The data must represent a valid ICC profile. The ICC profile version + must be 2 or 4, it must be a 3 channel profile and the class must be + Display or ColorSpace. Violating these requirements will not result in a + protocol error but will eventually send the + xx_image_description_v4.failed event on the created + xx_image_description_v4 with the 'unsupported' cause. + + See the International Color Consortium specification ICC.1:2022 for more + details about ICC profiles. + + If ICC file has already been set on this object, the protocol error + already_set is raised. + + + + + + + + + + + This type of object is used for collecting all the parameters required + to create a xx_image_description_v4 object. A complete set of required + parameters consists of these properties: + - transfer characteristic function (tf) + - chromaticities of primaries and white point (primary color volume) + + The following properties are optional and have a well-defined default + if not explicitly set: + - primary color volume luminance range + - reference white luminance level + - mastering display primaries and white point (target color volume) + - mastering luminance range + - maximum content light level + - maximum frame-average light level + + Each required property must be set exactly once if the client is to create + an image description. The set requests verify that a property was not + already set. The create request verifies that all required properties are + set. There may be several alternative requests for setting each property, + and in that case the client must choose one of them. + + Once all properties have been set, the create request must be used to + create the image description object, destroying the creator in the + process. + + + + + + + + + + + + + + + + + + Create an image description object based on the parameters previously + set on this object. + + The completeness of the parameter set is verified. If the set is not + complete, the protocol error incomplete_set is raised. For the + definition of a complete set, see the description of this interface. + + Also, the combination of the parameter set is verified. If the set is + not consistent, the protocol error inconsistent_set is raised. + + If the particular combination of the parameter set is not supported + by the compositor, the resulting image description object shall + immediately deliver the xx_image_description_v4.failed event with the + 'unsupported' cause. If a valid image description was created from the + parameter set, the xx_image_description_v4.ready event will eventually + be sent instead. + + This request destroys the xx_image_description_creator_params_v4 + object. + + The resulting image description object does not allow get_information + request. + + + + + + + + Sets the transfer characteristic using explicitly enumerated named + functions. + + When the resulting image description is attached to an image, the + content should be encoded and decoded according to the industry standard + practices for the transfer characteristic. + + Only names advertised with xx_color_manager_v4 event supported_tf_named + are allowed. Other values shall raise the protocol error invalid_tf. + + If transfer characteristic has already been set on this object, the + protocol error already_set is raised. + + + + + + + + Sets the color component transfer characteristic to a power curve with + the given exponent. This curve represents the conversion from electrical + to optical pixel or color values. + + When the resulting image description is attached to an image, the + content should be encoded with the inverse of the power curve. + + The curve exponent shall be multiplied by 10000 to get the argument eexp + value to carry the precision of 4 decimals. + + The curve exponent must be at least 1.0 and at most 10.0. Otherwise the + protocol error invalid_tf is raised. + + If transfer characteristic has already been set on this object, the + protocol error already_set is raised. + + This request can be used when the compositor advertises + xx_color_manager_v4.feature.set_tf_power. Otherwise this request raises + the protocol error unsupported_feature. + + + + + + + + Sets the color primaries and white point using explicitly named sets. + This describes the primary color volume which is the basis for color + value encoding. + + Only names advertised with xx_color_manager_v4 event + supported_primaries_named are allowed. Other values shall raise the + protocol error invalid_primaries. + + If primaries have already been set on this object, the protocol error + already_set is raised. + + + + + + + + Sets the color primaries and white point using CIE 1931 xy chromaticity + coordinates. This describes the primary color volume which is the basis + for color value encoding. + + Each coordinate value is multiplied by 10000 to get the argument value + to carry precision of 4 decimals. + + If primaries have already been set on this object, the protocol error + already_set is raised. + + This request can be used if the compositor advertises + xx_color_manager_v4.feature.set_primaries. Otherwise this request raises + the protocol error unsupported_feature. + + + + + + + + + + + + + + + Sets the primary color volume luminance range and the reference white + luminance level. + + The default luminances are + - primary color volume minimum: 0.2 cd/m² + - primary color volume maximum: 80 cd/m² + - reference white: 80 cd/m² + + Setting a named transfer characteristic can imply other default + luminances. + + The default luminances get overwritten when this request is used. + + 'min_lum' and 'max_lum' specify the minimum and maximum luminances of + the primary color volume as reproduced by the targeted display. + + 'reference_lum' specifies the luminance of the reference white as + reproduced by the targeted display, and reflects the targeted viewing + environment. + + Compositors should make sure that all content is anchored, meaning that + an input signal level of 'reference_lum' on one image description and + another input signal level of 'reference_lum' on another image + description should produce the same output level, even though the + 'reference_lum' on both image representations can be different. + + If 'max_lum' is less than the 'reference_lum', or 'reference_lum' is + less than or equal to 'min_lum', the protocol error invalid_luminance is + raised. + + The minimum luminance is multiplied by 10000 to get the argument + 'min_lum' value and carries precision of 4 decimals. The maximum + luminance and reference white luminance values are unscaled. + + If the primary color volume luminance range and the reference white + luminance level have already been set on this object, the protocol error + already_set is raised. + + This request can be used if the compositor advertises + xx_color_manager_v4.feature.set_luminances. Otherwise this request + raises the protocol error unsupported_feature. + + + + + + + + + + Provides the color primaries and white point of the mastering display + using CIE 1931 xy chromaticity coordinates. This is compatible with the + SMPTE ST 2086 definition of HDR static metadata. + + The mastering display primaries define the target color volume. + + If mastering display primaries are not explicitly set, the target color + volume is assumed to be equal to the primary color volume. + + The target color volume is defined by all tristimulus values between 0.0 + and 1.0 (inclusive) of the color space defined by the given mastering + display primaries and white point. The colorimetry is identical between + the container color space and the mastering display color space, + including that no chromatic adaptation is applied even if the white + points differ. + + The target color volume can exceed the primary color volume to allow for + a greater color volume with an existing color space definition (for + example scRGB). It can be smaller than the primary color volume to + minimize gamut and tone mapping distances for big color spaces (HDR + metadata). + + To make use of the entire target color volume a suitable pixel format + has to be chosen (e.g. floating point to exceed the primary color + volume, or abusing limited quantization range as with xvYCC). + + Each coordinate value is multiplied by 10000 to get the argument value + to carry precision of 4 decimals. + + If mastering display primaries have already been set on this object, the + protocol error already_set is raised. + + This request can be used if the compositor advertises + xx_color_manager_v4.feature.set_mastering_display_primaries. Otherwise + this request raises the protocol error unsupported_feature. The + advertisement implies support only for target color volumes fully + contained within the primary color volume. + + If a compositor additionally supports target color volume exceeding the + primary color volume, it must advertise + xx_color_manager_v4.feature.extended_target_volume. If a client uses + target color volume exceeding the primary color volume and the + compositor does not support it, the result is implementation defined. + Compositors are recommended to detect this case and fail the image + description gracefully, but it may as well result in color artifacts. + + + + + + + + + + + + + + + Sets the luminance range that was used during the content mastering + process as the minimum and maximum absolute luminance L. This is + compatible with the SMPTE ST 2086 definition of HDR static metadata. + + The mastering luminance range is undefined by default. + + If max L is less than or equal to min L, the protocol error + invalid_luminance is raised. + + Min L value is multiplied by 10000 to get the argument min_lum value + and carry precision of 4 decimals. Max L value is unscaled for max_lum. + + + + + + + + + Sets the maximum content light level (max_cll) as defined by CTA-861-H. + + This can only be set when set_tf_cicp is used to set the transfer + characteristic to Rec. ITU-R BT.2100-2 perceptual quantization system. + Otherwise, 'create' request shall raise inconsistent_set protocol + error. + + max_cll is undefined by default. + + + + + + + + Sets the maximum frame-average light level (max_fall) as defined by + CTA-861-H. + + This can only be set when set_tf_cicp is used to set the transfer + characteristic to Rec. ITU-R BT.2100-2 perceptual quantization system. + Otherwise, 'create' request shall raise inconsistent_set protocol error. + + max_fall is undefined by default. + + + + + + + + + An image description carries information about the color encoding used on + a surface when attached to a wl_surface via + xx_color_management_surface_v4.set_image_description. A compositor can use + this information to decode pixel values into colorimetrically meaningful + quantities. + + Note, that the xx_image_description_v4 object is not ready to be used + immediately after creation. The object eventually delivers either the + 'ready' or the 'failed' event, specified in all requests creating it. The + object is deemed "ready" after receiving the 'ready' event. + + An object which is not ready is illegal to use, it can only be destroyed. + Any other request in this interface shall result in the 'not_ready' + protocol error. Attempts to use an object which is not ready through other + interfaces shall raise protocol errors defined there. + + Once created and regardless of how it was created, a + xx_image_description_v4 object always refers to one fixed image + description. It cannot change after creation. + + + + + Destroy this object. It is safe to destroy an object which is not ready. + + Destroying a xx_image_description_v4 object has no side-effects, not + even if a xx_color_management_surface_v4.set_image_description has not + yet been followed by a wl_surface.commit. + + + + + + + + + + + + + + + + + + + + + + If creating a xx_image_description_v4 object fails for a reason that is + not defined as a protocol error, this event is sent. + + The requests that create image description objects define whether and + when this can occur. Only such creation requests can trigger this event. + This event cannot be triggered after the image description was + successfully formed. + + Once this event has been sent, the xx_image_description_v4 object will + never become ready and it can only be destroyed. + + + + + + + + + Once this event has been sent, the xx_image_description_v4 object is + deemed "ready". Ready objects can be used to send requests and can be + used through other interfaces. + + Every ready xx_image_description_v4 protocol object refers to an + underlying image description record in the compositor. Multiple protocol + objects may end up referring to the same record. Clients may identify + these "copies" by comparing their id numbers: if the numbers from two + protocol objects are identical, the protocol objects refer to the same + image description record. Two different image description records + cannot have the same id number simultaneously. The id number does not + change during the lifetime of the image description record. + + The id number is valid only as long as the protocol object is alive. If + all protocol objects referring to the same image description record are + destroyed, the id number may be recycled for a different image + description record. + + Image description id number is not a protocol object id. Zero is + reserved as an invalid id number. It shall not be possible for a client + to refer to an image description by its id number in protocol. The id + numbers might not be portable between Wayland connections. + + This identity allows clients to de-duplicate image description records + and avoid get_information request if they already have the image + description information. + + + + + + + + Creates a xx_image_description_info_v4 object which delivers the + information that makes up the image description. + + Not all image description protocol objects allow get_information + request. Whether it is allowed or not is defined by the request that + created the object. If get_information is not allowed, the protocol + error no_information is raised. + + + + + + + + + Sends all matching events describing an image description object exactly + once and finally sends the 'done' event. + + Once a xx_image_description_info_v4 object has delivered a 'done' event it + is automatically destroyed. + + Every xx_image_description_info_v4 created from the same + xx_image_description_v4 shall always return the exact same data. + + + + + Signals the end of information events and destroys the object. + + + + + + The icc argument provides a file descriptor to the client which may be + memory-mapped to provide the ICC profile matching the image description. + The fd is read-only, and if mapped then it must be mapped with + MAP_PRIVATE by the client. + + The ICC profile version and other details are determined by the + compositor. There is no provision for a client to ask for a specific + kind of a profile. + + + + + + + + + + Delivers the primary color volume primaries and white point using CIE + 1931 xy chromaticity coordinates. + + Each coordinate value is multiplied by 10000 to get the argument value + to carry precision of 4 decimals. + + + + + + + + + + + + + + + Delivers the primary color volume primaries and white point using an + explicitly enumerated named set. + + + + + + + + The color component transfer characteristic of this image description is + a pure power curve. This event provides the exponent of the power + function. This curve represents the conversion from electrical to + optical pixel or color values. + + The curve exponent has been multiplied by 10000 to get the argument eexp + value to carry the precision of 4 decimals. + + + + + + + + Delivers the transfer characteristic using an explicitly enumerated + named function. + + + + + + + + Delivers the primary color volume luminance range and the reference + white luminance level. + + The minimum luminance is multiplied by 10000 to get the argument + 'min_lum' value and carries precision of 4 decimals. The maximum + luminance and reference white luminance values are unscaled. + + + + + + + + + + Provides the color primaries and white point of the target color volume + using CIE 1931 xy chromaticity coordinates. This is compatible with the + SMPTE ST 2086 definition of HDR static metadata for mastering displays. + + While primary color volume is about how color is encoded, the target + color volume is the actually displayable color volume. If target color + volume is equal to the primary color volume, then this event is not + sent. + + Each coordinate value is multiplied by 10000 to get the argument value + to carry precision of 4 decimals. + + + + + + + + + + + + + + + Provides the luminance range that the image description is targeting as + the minimum and maximum absolute luminance L. This is compatible with + the SMPTE ST 2086 definition of HDR static metadata. + + This luminance range is only theoretical and may not correspond to the + luminance of light emitted on an actual display. + + Min L value is multiplied by 10000 to get the argument min_lum value and + carry precision of 4 decimals. Max L value is unscaled for max_lum. + + + + + + + + + Provides the targeted max_cll of the image description. max_cll is + defined by CTA-861-H. + + This luminance is only theoretical and may not correspond to the + luminance of light emitted on an actual display. + + + + + + + + Provides the targeted max_fall of the image description. max_fall is + defined by CTA-861-H. + + This luminance is only theoretical and may not correspond to the + luminance of light emitted on an actual display. + + + + + + diff --git a/src/3rdparty/wayland/protocols/cursor-shape/REUSE.toml b/src/3rdparty/wayland/protocols/cursor-shape/REUSE.toml new file mode 100644 index 00000000000..2db8971fb00 --- /dev/null +++ b/src/3rdparty/wayland/protocols/cursor-shape/REUSE.toml @@ -0,0 +1,8 @@ +version = 1 + +[[annotations]] +path = "cursor-shape-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = ["Copyright 2018 The Chromium Authors", + "Copyright 2023 Simon Ser"] +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/cursor-shape/cursor-shape-v1.xml b/src/3rdparty/wayland/protocols/cursor-shape/cursor-shape-v1.xml new file mode 100644 index 00000000000..b6fbe08b78f --- /dev/null +++ b/src/3rdparty/wayland/protocols/cursor-shape/cursor-shape-v1.xml @@ -0,0 +1,146 @@ + + + + Copyright 2018 The Chromium Authors + Copyright 2023 Simon Ser + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This global allows clients to set cursor images by name instead of + creating and attaching buffers. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + Destroy the cursor shape manager. + + + + + + Obtain a wp_cursor_shape_device_v1 for a wl_pointer object. + + + + + + + + Obtain a wp_cursor_shape_device_v1 for a zwp_tablet_tool_v2 object. + + + + + + + + + This interface advertises the list of supported cursor shapes for a + device, and allows clients to set the cursor shape. + + + + + This enum describes cursor shapes. + + The names are taken from the CSS W3C specification: + https://w3c.github.io/csswg-drafts/css-ui/#cursor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Destroy the cursor shape device. + + The device cursor shape remains unchanged. + + + + + + Sets the device cursor to the specified shape. The compositor will + change the cursor image based on the specified shape. + + The cursor actually changes only if the input device focus is one of + the requesting client's surfaces. If any, the previous cursor image + (surface or shape) is replaced. + + The "shape" argument must be a valid enum entry, otherwise the + invalid_shape protocol error is raised. + + This is similar to the wl_pointer.set_cursor and + zwp_tablet_tool_v2.set_cursor requests, but this request accepts a + shape instead of contents in the form of a surface. Clients can mix + set_cursor and set_shape requests. + + The serial parameter must match the latest wl_pointer.enter or + zwp_tablet_tool_v2.proximity_in serial number sent to the client. + Otherwise the request will be ignored. + + + + + + diff --git a/src/3rdparty/wayland/protocols/fractional-scale/REUSE.toml b/src/3rdparty/wayland/protocols/fractional-scale/REUSE.toml new file mode 100644 index 00000000000..6f03d8f64cd --- /dev/null +++ b/src/3rdparty/wayland/protocols/fractional-scale/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "fractional-scale-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2022 Kenny Levinsen" +SPDX-License-Identifier = "MIT" \ No newline at end of file diff --git a/src/3rdparty/wayland/protocols/fractional-scale/fractional-scale-v1.xml b/src/3rdparty/wayland/protocols/fractional-scale/fractional-scale-v1.xml new file mode 100644 index 00000000000..350bfc01eaf --- /dev/null +++ b/src/3rdparty/wayland/protocols/fractional-scale/fractional-scale-v1.xml @@ -0,0 +1,102 @@ + + + + Copyright © 2022 Kenny Levinsen + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows a compositor to suggest for surfaces to render at + fractional scales. + + A client can submit scaled content by utilizing wp_viewport. This is done by + creating a wp_viewport object for the surface and setting the destination + rectangle to the surface size before the scale factor is applied. + + The buffer size is calculated by multiplying the surface size by the + intended scale. + + The wl_surface buffer scale should remain set to 1. + + If a surface has a surface-local size of 100 px by 50 px and wishes to + submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should + be used and the wp_viewport destination rectangle should be 100 px by 50 px. + + For toplevel surfaces, the size is rounded halfway away from zero. The + rounding algorithm for subsurface position and size is not defined. + + + + + A global interface for requesting surfaces to use fractional scales. + + + + + Informs the server that the client will not be using this protocol + object anymore. This does not affect any other objects, + wp_fractional_scale_v1 objects included. + + + + + + + + + + Create an add-on object for the the wl_surface to let the compositor + request fractional scales. If the given wl_surface already has a + wp_fractional_scale_v1 object associated, the fractional_scale_exists + protocol error is raised. + + + + + + + + + An additional interface to a wl_surface object which allows the compositor + to inform the client of the preferred scale. + + + + + Destroy the fractional scale object. When this object is destroyed, + preferred_scale events will no longer be sent. + + + + + + Notification of a new preferred scale for this surface that the + compositor suggests that the client should use. + + The sent scale is the numerator of a fraction with a denominator of 120. + + + + + diff --git a/src/3rdparty/wayland/protocols/fractional-scale/qt_attribution.json b/src/3rdparty/wayland/protocols/fractional-scale/qt_attribution.json new file mode 100644 index 00000000000..653a8eb0c15 --- /dev/null +++ b/src/3rdparty/wayland/protocols/fractional-scale/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "fractional-scale-v1", + "Name": "Wayland Fractional Scale Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "fractional-scale-v1.xml", + + "Description": "Send a preferred scale to different clients", + "Homepage": "https://wayland.freedesktop.org", + "Version": "1", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/raw/1.31/unstable/fractional-scale/fractional-scale-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2022 Kenny Levinsen" + } +] diff --git a/src/3rdparty/wayland/protocols/fullscreen-shell/REUSE.toml b/src/3rdparty/wayland/protocols/fullscreen-shell/REUSE.toml new file mode 100644 index 00000000000..7f939e45153 --- /dev/null +++ b/src/3rdparty/wayland/protocols/fullscreen-shell/REUSE.toml @@ -0,0 +1,9 @@ +version = 1 + +[[annotations]] +path = "fullscreen-shell-unstable-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = ["Copyright 2016 Yong Bakos", + "Copyright 2015 Jason Ekstrand", + "Copyright 2015 Jonas Ådahl"] +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/fullscreen-shell/fullscreen-shell-unstable-v1.xml b/src/3rdparty/wayland/protocols/fullscreen-shell/fullscreen-shell-unstable-v1.xml new file mode 100644 index 00000000000..1bca7b7c7d2 --- /dev/null +++ b/src/3rdparty/wayland/protocols/fullscreen-shell/fullscreen-shell-unstable-v1.xml @@ -0,0 +1,245 @@ + + + + + Copyright © 2016 Yong Bakos + Copyright © 2015 Jason Ekstrand + Copyright © 2015 Jonas Ådahl + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + Displays a single surface per output. + + This interface provides a mechanism for a single client to display + simple full-screen surfaces. While there technically may be multiple + clients bound to this interface, only one of those clients should be + shown at a time. + + To present a surface, the client uses either the present_surface or + present_surface_for_mode requests. Presenting a surface takes effect + on the next wl_surface.commit. See the individual requests for + details about scaling and mode switches. + + The client can have at most one surface per output at any time. + Requesting a surface to be presented on an output that already has a + surface replaces the previously presented surface. Presenting a null + surface removes its content and effectively disables the output. + Exactly what happens when an output is "disabled" is + compositor-specific. The same surface may be presented on multiple + outputs simultaneously. + + Once a surface is presented on an output, it stays on that output + until either the client removes it or the compositor destroys the + output. This way, the client can update the output's contents by + simply attaching a new buffer. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Release the binding from the wl_fullscreen_shell interface. + + This destroys the server-side object and frees this binding. If + the client binds to wl_fullscreen_shell multiple times, it may wish + to free some of those bindings. + + + + + + Various capabilities that can be advertised by the compositor. They + are advertised one-at-a-time when the wl_fullscreen_shell interface is + bound. See the wl_fullscreen_shell.capability event for more details. + + ARBITRARY_MODES: + This is a hint to the client that indicates that the compositor is + capable of setting practically any mode on its outputs. If this + capability is provided, wl_fullscreen_shell.present_surface_for_mode + will almost never fail and clients should feel free to set whatever + mode they like. If the compositor does not advertise this, it may + still support some modes that are not advertised through wl_global.mode + but it is less likely. + + CURSOR_PLANE: + This is a hint to the client that indicates that the compositor can + handle a cursor surface from the client without actually compositing. + This may be because of a hardware cursor plane or some other mechanism. + If the compositor does not advertise this capability then setting + wl_pointer.cursor may degrade performance or be ignored entirely. If + CURSOR_PLANE is not advertised, it is recommended that the client draw + its own cursor and set wl_pointer.cursor(NULL). + + + + + + + + Advertises a single capability of the compositor. + + When the wl_fullscreen_shell interface is bound, this event is emitted + once for each capability advertised. Valid capabilities are given by + the wl_fullscreen_shell.capability enum. If clients want to take + advantage of any of these capabilities, they should use a + wl_display.sync request immediately after binding to ensure that they + receive all the capability events. + + + + + + + Hints to indicate to the compositor how to deal with a conflict + between the dimensions of the surface and the dimensions of the + output. The compositor is free to ignore this parameter. + + + + + + + + + + + Present a surface on the given output. + + If the output is null, the compositor will present the surface on + whatever display (or displays) it thinks best. In particular, this + may replace any or all surfaces currently presented so it should + not be used in combination with placing surfaces on specific + outputs. + + The method parameter is a hint to the compositor for how the surface + is to be presented. In particular, it tells the compositor how to + handle a size mismatch between the presented surface and the + output. The compositor is free to ignore this parameter. + + The "zoom", "zoom_crop", and "stretch" methods imply a scaling + operation on the surface. This will override any kind of output + scaling, so the buffer_scale property of the surface is effectively + ignored. + + + + + + + + + Presents a surface on the given output for a particular mode. + + If the current size of the output differs from that of the surface, + the compositor will attempt to change the size of the output to + match the surface. The result of the mode-switch operation will be + returned via the provided wl_fullscreen_shell_mode_feedback object. + + If the current output mode matches the one requested or if the + compositor successfully switches the mode to match the surface, + then the mode_successful event will be sent and the output will + contain the contents of the given surface. If the compositor + cannot match the output size to the surface size, the mode_failed + will be sent and the output will contain the contents of the + previously presented surface (if any). If another surface is + presented on the given output before either of these has a chance + to happen, the present_cancelled event will be sent. + + Due to race conditions and other issues unknown to the client, no + mode-switch operation is guaranteed to succeed. However, if the + mode is one advertised by wl_output.mode or if the compositor + advertises the ARBITRARY_MODES capability, then the client should + expect that the mode-switch operation will usually succeed. + + If the size of the presented surface changes, the resulting output + is undefined. The compositor may attempt to change the output mode + to compensate. However, there is no guarantee that a suitable mode + will be found and the client has no way to be notified of success + or failure. + + The framerate parameter specifies the desired framerate for the + output in mHz. The compositor is free to ignore this parameter. A + value of 0 indicates that the client has no preference. + + If the value of wl_output.scale differs from wl_surface.buffer_scale, + then the compositor may choose a mode that matches either the buffer + size or the surface size. In either case, the surface will fill the + output. + + + + + + + + + + These errors can be emitted in response to wl_fullscreen_shell requests. + + + + + + + + + This event indicates that the attempted mode switch operation was + successful. A surface of the size requested in the mode switch + will fill the output without scaling. + + Upon receiving this event, the client should destroy the + wl_fullscreen_shell_mode_feedback object. + + + + + + This event indicates that the attempted mode switch operation + failed. This may be because the requested output mode is not + possible or it may mean that the compositor does not want to allow it. + + Upon receiving this event, the client should destroy the + wl_fullscreen_shell_mode_feedback object. + + + + + + This event indicates that the attempted mode switch operation was + cancelled. Most likely this is because the client requested a + second mode switch before the first one completed. + + Upon receiving this event, the client should destroy the + wl_fullscreen_shell_mode_feedback object. + + + + + diff --git a/src/3rdparty/wayland/protocols/fullscreen-shell/qt_attribution.json b/src/3rdparty/wayland/protocols/fullscreen-shell/qt_attribution.json new file mode 100644 index 00000000000..07926f16429 --- /dev/null +++ b/src/3rdparty/wayland/protocols/fullscreen-shell/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-fullscreen-protocol", + "Name": "Wayland Fullscreen Shell Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin.", + "Files": "fullscreen-shell-unstable-v1.xml", + + "Description": "A Wayland shell for displaying a single surface per output", + "Homepage": "https://wayland.freedesktop.org", + "Version": "unstable v1", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/raw/1.18/unstable/fullscreen-shell/fullscreen-shell-unstable-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2016 Yong Bakos\nCopyright © 2015 Jason Ekstrand\nCopyright © 2015 Jonas Ådahl" + } +] diff --git a/src/3rdparty/wayland/protocols/idle-inhibit/REUSE.toml b/src/3rdparty/wayland/protocols/idle-inhibit/REUSE.toml new file mode 100644 index 00000000000..cfc112addff --- /dev/null +++ b/src/3rdparty/wayland/protocols/idle-inhibit/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "idle-inhibit-unstable-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2015 Samsung Electronics Co., Ltd" +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/idle-inhibit/idle-inhibit-unstable-v1.xml b/src/3rdparty/wayland/protocols/idle-inhibit/idle-inhibit-unstable-v1.xml new file mode 100644 index 00000000000..9c06cdcba6c --- /dev/null +++ b/src/3rdparty/wayland/protocols/idle-inhibit/idle-inhibit-unstable-v1.xml @@ -0,0 +1,83 @@ + + + + + Copyright © 2015 Samsung Electronics Co., Ltd + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This interface permits inhibiting the idle behavior such as screen + blanking, locking, and screensaving. The client binds the idle manager + globally, then creates idle-inhibitor objects for each surface. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Destroy the inhibit manager. + + + + + + Create a new inhibitor object associated with the given surface. + + + + + + + + + + An idle inhibitor prevents the output that the associated surface is + visible on from being set to a state where it is not visually usable due + to lack of user interaction (e.g. blanked, dimmed, locked, set to power + save, etc.) Any screensaver processes are also blocked from displaying. + + If the surface is destroyed, unmapped, becomes occluded, loses + visibility, or otherwise becomes not visually relevant for the user, the + idle inhibitor will not be honored by the compositor; if the surface + subsequently regains visibility the inhibitor takes effect once again. + Likewise, the inhibitor isn't honored if the system was already idled at + the time the inhibitor was established, although if the system later + de-idles and re-idles the inhibitor will take effect. + + + + + Remove the inhibitor effect from the associated wl_surface. + + + + + diff --git a/src/3rdparty/wayland/protocols/linux-dmabuf/REUSE.toml b/src/3rdparty/wayland/protocols/linux-dmabuf/REUSE.toml new file mode 100644 index 00000000000..ca0cba7cd40 --- /dev/null +++ b/src/3rdparty/wayland/protocols/linux-dmabuf/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "linux-dmabuf-unstable-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2014, 2015 Collabora, Ltd." +SPDX-License-Identifier = "MIT" \ No newline at end of file diff --git a/src/3rdparty/wayland/protocols/linux-dmabuf/linux-dmabuf-unstable-v1.xml b/src/3rdparty/wayland/protocols/linux-dmabuf/linux-dmabuf-unstable-v1.xml new file mode 100644 index 00000000000..b43e81ca0bc --- /dev/null +++ b/src/3rdparty/wayland/protocols/linux-dmabuf/linux-dmabuf-unstable-v1.xml @@ -0,0 +1,362 @@ + + + + + Copyright © 2014, 2015 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + Following the interfaces from: + https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt + https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt + and the Linux DRM sub-system's AddFb2 ioctl. + + This interface offers ways to create generic dmabuf-based + wl_buffers. Immediately after a client binds to this interface, + the set of supported formats and format modifiers is sent with + 'format' and 'modifier' events. + + The following are required from clients: + + - Clients must ensure that either all data in the dma-buf is + coherent for all subsequent read access or that coherency is + correctly handled by the underlying kernel-side dma-buf + implementation. + + - Don't make any more attachments after sending the buffer to the + compositor. Making more attachments later increases the risk of + the compositor not being able to use (re-import) an existing + dmabuf-based wl_buffer. + + The underlying graphics stack must ensure the following: + + - The dmabuf file descriptors relayed to the server will stay valid + for the whole lifetime of the wl_buffer. This means the server may + at any time use those fds to import the dmabuf into any kernel + sub-system that might accept it. + + To create a wl_buffer from one or more dmabufs, a client creates a + zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params + request. All planes required by the intended format are added with + the 'add' request. Finally, a 'create' or 'create_immed' request is + issued, which has the following outcome depending on the import success. + + The 'create' request, + - on success, triggers a 'created' event which provides the final + wl_buffer to the client. + - on failure, triggers a 'failed' event to convey that the server + cannot use the dmabufs received from the client. + + For the 'create_immed' request, + - on success, the server immediately imports the added dmabufs to + create a wl_buffer. No event is sent from the server in this case. + - on failure, the server can choose to either: + - terminate the client by raising a fatal error. + - mark the wl_buffer as failed, and send a 'failed' event to the + client. If the client uses a failed wl_buffer as an argument to any + request, the behaviour is compositor implementation-defined. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Objects created through this interface, especially wl_buffers, will + remain valid. + + + + + + This temporary object is used to collect multiple dmabuf handles into + a single batch to create a wl_buffer. It can only be used once and + should be destroyed after a 'created' or 'failed' event has been + received. + + + + + + + This event advertises one buffer format that the server supports. + All the supported formats are advertised once when the client + binds to this interface. A roundtrip after binding guarantees + that the client has received all supported formats. + + For the definition of the format codes, see the + zwp_linux_buffer_params_v1::create request. + + Warning: the 'format' event is likely to be deprecated and replaced + with the 'modifier' event introduced in zwp_linux_dmabuf_v1 + version 3, described below. Please refrain from using the information + received from this event. + + + + + + + This event advertises the formats that the server supports, along with + the modifiers supported for each format. All the supported modifiers + for all the supported formats are advertised once when the client + binds to this interface. A roundtrip after binding guarantees that + the client has received all supported format-modifier pairs. + + For legacy support, DRM_FORMAT_MOD_INVALID (that is, modifier_hi == + 0x00ffffff and modifier_lo == 0xffffffff) is allowed in this event. + It indicates that the server can support the format with an implicit + modifier. When a plane has DRM_FORMAT_MOD_INVALID as its modifier, it + is as if no explicit modifier is specified. The effective modifier + will be derived from the dmabuf. + + For the definition of the format and modifier codes, see the + zwp_linux_buffer_params_v1::create and zwp_linux_buffer_params_v1::add + requests. + + + + + + + + + + This temporary object is a collection of dmabufs and other + parameters that together form a single logical buffer. The temporary + object may eventually create one wl_buffer unless cancelled by + destroying it before requesting 'create'. + + Single-planar formats only require one dmabuf, however + multi-planar formats may require more than one dmabuf. For all + formats, an 'add' request must be called once per plane (even if the + underlying dmabuf fd is identical). + + You must use consecutive plane indices ('plane_idx' argument for 'add') + from zero to the number of planes used by the drm_fourcc format code. + All planes required by the format must be given exactly once, but can + be given in any order. Each plane index can be set only once. + + + + + + + + + + + + + + + + Cleans up the temporary data sent to the server for dmabuf-based + wl_buffer creation. + + + + + + This request adds one dmabuf to the set in this + zwp_linux_buffer_params_v1. + + The 64-bit unsigned value combined from modifier_hi and modifier_lo + is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the + fb modifier, which is defined in drm_mode.h of Linux UAPI. + This is an opaque token. Drivers use this token to express tiling, + compression, etc. driver-specific modifications to the base format + defined by the DRM fourcc code. + + Warning: It should be an error if the format/modifier pair was not + advertised with the modifier event. This is not enforced yet because + some implementations always accept DRM_FORMAT_MOD_INVALID. Also + version 2 of this protocol does not have the modifier event. + + This request raises the PLANE_IDX error if plane_idx is too large. + The error PLANE_SET is raised if attempting to set a plane that + was already set. + + + + + + + + + + + + + + + + + + This asks for creation of a wl_buffer from the added dmabuf + buffers. The wl_buffer is not created immediately but returned via + the 'created' event if the dmabuf sharing succeeds. The sharing + may fail at runtime for reasons a client cannot predict, in + which case the 'failed' event is triggered. + + The 'format' argument is a DRM_FORMAT code, as defined by the + libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the + authoritative source on how the format codes should work. + + The 'flags' is a bitfield of the flags defined in enum "flags". + 'y_invert' means the that the image needs to be y-flipped. + + Flag 'interlaced' means that the frame in the buffer is not + progressive as usual, but interlaced. An interlaced buffer as + supported here must always contain both top and bottom fields. + The top field always begins on the first pixel row. The temporal + ordering between the two fields is top field first, unless + 'bottom_first' is specified. It is undefined whether 'bottom_first' + is ignored if 'interlaced' is not set. + + This protocol does not convey any information about field rate, + duration, or timing, other than the relative ordering between the + two fields in one buffer. A compositor may have to estimate the + intended field rate from the incoming buffer rate. It is undefined + whether the time of receiving wl_surface.commit with a new buffer + attached, applying the wl_surface state, wl_surface.frame callback + trigger, presentation, or any other point in the compositor cycle + is used to measure the frame or field times. There is no support + for detecting missed or late frames/fields/buffers either, and + there is no support whatsoever for cooperating with interlaced + compositor output. + + The composited image quality resulting from the use of interlaced + buffers is explicitly undefined. A compositor may use elaborate + hardware features or software to deinterlace and create progressive + output frames from a sequence of interlaced input buffers, or it + may produce substandard image quality. However, compositors that + cannot guarantee reasonable image quality in all cases are recommended + to just reject all interlaced buffers. + + Any argument errors, including non-positive width or height, + mismatch between the number of planes and the format, bad + format, bad offset or stride, may be indicated by fatal protocol + errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, + OUT_OF_BOUNDS. + + Dmabuf import errors in the server that are not obvious client + bugs are returned via the 'failed' event as non-fatal. This + allows attempting dmabuf sharing and falling back in the client + if it fails. + + This request can be sent only once in the object's lifetime, after + which the only legal request is destroy. This object should be + destroyed after issuing a 'create' request. Attempting to use this + object after issuing 'create' raises ALREADY_USED protocol error. + + It is not mandatory to issue 'create'. If a client wants to + cancel the buffer creation, it can just destroy this object. + + + + + + + + + + This event indicates that the attempted buffer creation was + successful. It provides the new wl_buffer referencing the dmabuf(s). + + Upon receiving this event, the client should destroy the + zlinux_dmabuf_params object. + + + + + + + This event indicates that the attempted buffer creation has + failed. It usually means that one of the dmabuf constraints + has not been fulfilled. + + Upon receiving this event, the client should destroy the + zlinux_buffer_params object. + + + + + + This asks for immediate creation of a wl_buffer by importing the + added dmabufs. + + In case of import success, no event is sent from the server, and the + wl_buffer is ready to be used by the client. + + Upon import failure, either of the following may happen, as seen fit + by the implementation: + - the client is terminated with one of the following fatal protocol + errors: + - INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS, + in case of argument errors such as mismatch between the number + of planes and the format, bad format, non-positive width or + height, or bad offset or stride. + - INVALID_WL_BUFFER, in case the cause for failure is unknown or + plaform specific. + - the server creates an invalid wl_buffer, marks it as failed and + sends a 'failed' event to the client. The result of using this + invalid wl_buffer as an argument in any request by the client is + defined by the compositor implementation. + + This takes the same arguments as a 'create' request, and obeys the + same restrictions. + + + + + + + + + + + diff --git a/src/3rdparty/wayland/protocols/linux-dmabuf/qt_attribution.json b/src/3rdparty/wayland/protocols/linux-dmabuf/qt_attribution.json new file mode 100644 index 00000000000..e93e2e790b1 --- /dev/null +++ b/src/3rdparty/wayland/protocols/linux-dmabuf/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-linux-dmabuf-unstable-v1", + "Name": "Wayland Linux Dmabuf Unstable V1 Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor", + "Files": "linux-dmabuf-unstable-v1.xml", + + "Description": "The linux dmabuf protocol is a way to create dmabuf-based wl_buffers", + "Homepage": "https://wayland.freedesktop.org", + "Version": "unstable v1, version 3", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/raw/1.18/unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2014, 2015 Collabora, Ltd." + } +] diff --git a/src/3rdparty/wayland/protocols/pointer-gestures/REUSE.toml b/src/3rdparty/wayland/protocols/pointer-gestures/REUSE.toml new file mode 100644 index 00000000000..9eaccd5ee8d --- /dev/null +++ b/src/3rdparty/wayland/protocols/pointer-gestures/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "pointer-gestures-unstable-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2015, 2016 Red Hat" +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/pointer-gestures/pointer-gestures-unstable-v1.xml b/src/3rdparty/wayland/protocols/pointer-gestures/pointer-gestures-unstable-v1.xml new file mode 100644 index 00000000000..59502ac1161 --- /dev/null +++ b/src/3rdparty/wayland/protocols/pointer-gestures/pointer-gestures-unstable-v1.xml @@ -0,0 +1,186 @@ + + + + + + A global interface to provide semantic touchpad gestures for a given + pointer. + + Two gestures are currently supported: swipe and zoom/rotate. + All gestures follow a three-stage cycle: begin, update, end and + are identified by a unique id. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Create a swipe gesture object. See the + wl_pointer_gesture_swipe interface for details. + + + + + + + + Create a pinch gesture object. See the + wl_pointer_gesture_pinch interface for details. + + + + + + + + + + Destroy the pointer gesture object. Swipe and pinch objects created via this + gesture object remain valid. + + + + + + + A swipe gesture object notifies a client about a multi-finger swipe + gesture detected on an indirect input device such as a touchpad. + The gesture is usually initiated by multiple fingers moving in the + same direction but once initiated the direction may change. + The precise conditions of when such a gesture is detected are + implementation-dependent. + + A gesture consists of three stages: begin, update (optional) and end. + There cannot be multiple simultaneous pinch or swipe gestures on a + same pointer/seat, how compositors prevent these situations is + implementation-dependent. + + A gesture may be cancelled by the compositor or the hardware. + Clients should not consider performing permanent or irreversible + actions until the end of a gesture has been received. + + + + + + + + + This event is sent when a multi-finger swipe gesture is detected + on the device. + + + + + + + + + + This event is sent when a multi-finger swipe gesture changes the + position of the logical center. + + The dx and dy coordinates are relative coordinates of the logical + center of the gesture compared to the previous event. + + + + + + + + + This event is sent when a multi-finger swipe gesture ceases to + be valid. This may happen when one or more fingers are lifted or + the gesture is cancelled. + + When a gesture is cancelled, the client should undo state changes + caused by this gesture. What causes a gesture to be cancelled is + implementation-dependent. + + + + + + + + + + A pinch gesture object notifies a client about a multi-finger pinch + gesture detected on an indirect input device such as a touchpad. + The gesture is usually initiated by multiple fingers moving towards + each other or away from each other, or by two or more fingers rotating + around a logical center of gravity. The precise conditions of when + such a gesture is detected are implementation-dependent. + + A gesture consists of three stages: begin, update (optional) and end. + There cannot be multiple simultaneous pinch or swipe gestures on a + same pointer/seat, how compositors prevent these situations is + implementation-dependent. + + A gesture may be cancelled by the compositor or the hardware. + Clients should not consider performing permanent or irreversible + actions until the end of a gesture has been received. + + + + + + + + + This event is sent when a multi-finger pinch gesture is detected + on the device. + + + + + + + + + + This event is sent when a multi-finger pinch gesture changes the + position of the logical center, the rotation or the relative scale. + + The dx and dy coordinates are relative coordinates in the + surface coordinate space of the logical center of the gesture. + + The scale factor is an absolute scale compared to the + pointer_gesture_pinch.begin event, e.g. a scale of 2 means the fingers + are now twice as far apart as on pointer_gesture_pinch.begin. + + The rotation is the relative angle in degrees clockwise compared to the previous + pointer_gesture_pinch.begin or pointer_gesture_pinch.update event. + + + + + + + + + + + This event is sent when a multi-finger pinch gesture ceases to + be valid. This may happen when one or more fingers are lifted or + the gesture is cancelled. + + When a gesture is cancelled, the client should undo state changes + caused by this gesture. What causes a gesture to be cancelled is + implementation-dependent. + + + + + + + + diff --git a/src/3rdparty/wayland/protocols/pointer-gestures/qt_attribution.json b/src/3rdparty/wayland/protocols/pointer-gestures/qt_attribution.json new file mode 100644 index 00000000000..9cd44450e74 --- /dev/null +++ b/src/3rdparty/wayland/protocols/pointer-gestures/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-pointer-gestures-protocol", + "Name": "Wayland Pointer Gestures Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "pointer-gestures-unstable-v1.xml", + + "Description": "", + "Homepage": "https://wayland.freedesktop.org", + "Version": "unstable v1, version 2", + "DownloadLocation": "https://cgit.freedesktop.org/wayland/wayland-protocols/plain/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2015, 2016 Red Hat" + } +] diff --git a/src/3rdparty/wayland/protocols/presentation-time/REUSE.toml b/src/3rdparty/wayland/protocols/presentation-time/REUSE.toml new file mode 100644 index 00000000000..a818443e1fa --- /dev/null +++ b/src/3rdparty/wayland/protocols/presentation-time/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "presentation-time.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2013, 2014 Collabora, Ltd." +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/presentation-time/presentation-time.xml b/src/3rdparty/wayland/protocols/presentation-time/presentation-time.xml new file mode 100644 index 00000000000..d1731f0369c --- /dev/null +++ b/src/3rdparty/wayland/protocols/presentation-time/presentation-time.xml @@ -0,0 +1,266 @@ + + + + + + Copyright © 2013-2014 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + + + + The main feature of this interface is accurate presentation + timing feedback to ensure smooth video playback while maintaining + audio/video synchronization. Some features use the concept of a + presentation clock, which is defined in the + presentation.clock_id event. + + A content update for a wl_surface is submitted by a + wl_surface.commit request. Request 'feedback' associates with + the wl_surface.commit and provides feedback on the content + update, particularly the final realized presentation time. + + + + When the final realized presentation time is available, e.g. + after a framebuffer flip completes, the requested + presentation_feedback.presented events are sent. The final + presentation time can differ from the compositor's predicted + display update time and the update's target time, especially + when the compositor misses its target vertical blanking period. + + + + + These fatal protocol errors may be emitted in response to + illegal presentation requests. + + + + + + + + Informs the server that the client will no longer be using + this protocol object. Existing objects created by this object + are not affected. + + + + + + Request presentation feedback for the current content submission + on the given surface. This creates a new presentation_feedback + object, which will deliver the feedback information once. If + multiple presentation_feedback objects are created for the same + submission, they will all deliver the same information. + + For details on what information is returned, see the + presentation_feedback interface. + + + + + + + + This event tells the client in which clock domain the + compositor interprets the timestamps used by the presentation + extension. This clock is called the presentation clock. + + The compositor sends this event when the client binds to the + presentation interface. The presentation clock does not change + during the lifetime of the client connection. + + The clock identifier is platform dependent. On Linux/glibc, + the identifier value is one of the clockid_t values accepted + by clock_gettime(). clock_gettime() is defined by + POSIX.1-2001. + + Timestamps in this clock domain are expressed as tv_sec_hi, + tv_sec_lo, tv_nsec triples, each component being an unsigned + 32-bit value. Whole seconds are in tv_sec which is a 64-bit + value combined from tv_sec_hi and tv_sec_lo, and the + additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. + + Note that clock_id applies only to the presentation clock, + and implies nothing about e.g. the timestamps used in the + Wayland core protocol input events. + + Compositors should prefer a clock which does not jump and is + not slewed e.g. by NTP. The absolute value of the clock is + irrelevant. Precision of one millisecond or better is + recommended. Clients must be able to query the current clock + value directly, not by asking the compositor. + + + + + + + + + A presentation_feedback object returns an indication that a + wl_surface content update has become visible to the user. + One object corresponds to one content update submission + (wl_surface.commit). There are two possible outcomes: the + content update is presented to the user, and a presentation + timestamp delivered; or, the user did not see the content + update because it was superseded or its surface destroyed, + and the content update is discarded. + + Once a presentation_feedback object has delivered a 'presented' + or 'discarded' event it is automatically destroyed. + + + + + As presentation can be synchronized to only one output at a + time, this event tells which output it was. This event is only + sent prior to the presented event. + + As clients may bind to the same global wl_output multiple + times, this event is sent for each bound instance that matches + the synchronized output. If a client has not bound to the + right wl_output global at all, this event is not sent. + + + + + + + These flags provide information about how the presentation of + the related content update was done. The intent is to help + clients assess the reliability of the feedback and the visual + quality with respect to possible tearing and timings. The + flags are: + + VSYNC: + The presentation was synchronized to the "vertical retrace" by + the display hardware such that tearing does not happen. + Relying on user space scheduling is not acceptable for this + flag. If presentation is done by a copy to the active + frontbuffer, then it must guarantee that tearing cannot + happen. + + HW_CLOCK: + The display hardware provided measurements that the hardware + driver converted into a presentation timestamp. Sampling a + clock in user space is not acceptable for this flag. + + HW_COMPLETION: + The display hardware signalled that it started using the new + image content. The opposite of this is e.g. a timer being used + to guess when the display hardware has switched to the new + image content. + + ZERO_COPY: + The presentation of this update was done zero-copy. This means + the buffer from the client was given to display hardware as + is, without copying it. Compositing with OpenGL counts as + copying, even if textured directly from the client buffer. + Possible zero-copy cases include direct scanout of a + fullscreen surface and a surface on a hardware overlay. + + + + + + + + + + The associated content update was displayed to the user at the + indicated time (tv_sec_hi/lo, tv_nsec). For the interpretation of + the timestamp, see presentation.clock_id event. + + The timestamp corresponds to the time when the content update + turned into light the first time on the surface's main output. + Compositors may approximate this from the framebuffer flip + completion events from the system, and the latency of the + physical display path if known. + + This event is preceded by all related sync_output events + telling which output's refresh cycle the feedback corresponds + to, i.e. the main output for the surface. Compositors are + recommended to choose the output containing the largest part + of the wl_surface, or keeping the output they previously + chose. Having a stable presentation output association helps + clients predict future output refreshes (vblank). + + The 'refresh' argument gives the compositor's prediction of how + many nanoseconds after tv_sec, tv_nsec the very next output + refresh may occur. This is to further aid clients in + predicting future refreshes, i.e., estimating the timestamps + targeting the next few vblanks. If such prediction cannot + usefully be done, the argument is zero. + + If the output does not have a constant refresh rate, explicit + video mode switches excluded, then the refresh argument must + be zero. + + The 64-bit value combined from seq_hi and seq_lo is the value + of the output's vertical retrace counter when the content + update was first scanned out to the display. This value must + be compatible with the definition of MSC in + GLX_OML_sync_control specification. Note, that if the display + path has a non-zero latency, the time instant specified by + this counter may differ from the timestamp's. + + If the output does not have a concept of vertical retrace or a + refresh cycle, or the output device is self-refreshing without + a way to query the refresh count, then the arguments seq_hi + and seq_lo must be zero. + + + + + + + + + + + + + The content update was never displayed to the user. + + + + + diff --git a/src/3rdparty/wayland/protocols/presentation-time/qt_attribution.json b/src/3rdparty/wayland/protocols/presentation-time/qt_attribution.json new file mode 100644 index 00000000000..8d54400d334 --- /dev/null +++ b/src/3rdparty/wayland/protocols/presentation-time/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "presentation-time.xml", + "Name": "Presentation Time Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor", + "Files": "presentation-time.xml", + + "Description": "The presentaton time protocol is a way to get presentation timing feedback.", + "Homepage": "https://wayland.freedesktop.org", + "Version": "1", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/raw/1.18/stable/presentation-time/presentation-time.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2013, 2014 Collabora, Ltd." + } +] diff --git a/src/3rdparty/wayland/protocols/scaler/REUSE.toml b/src/3rdparty/wayland/protocols/scaler/REUSE.toml new file mode 100644 index 00000000000..3b85724eada --- /dev/null +++ b/src/3rdparty/wayland/protocols/scaler/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "scaler.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2013-2014 Collabora, Ltd." +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/scaler/qt_attribution.json b/src/3rdparty/wayland/protocols/scaler/qt_attribution.json new file mode 100644 index 00000000000..4b1f7303b8a --- /dev/null +++ b/src/3rdparty/wayland/protocols/scaler/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-scaler-protocol", + "Name": "Wayland Scaler Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor API", + "Files": "scaler.xml", + + "Description": "The Wayland scaler extension allows a client to scale or crop a surface without modifying the buffer", + "Homepage": "https://wayland.freedesktop.org", + "Version": "2", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/weston/raw/1.11/protocol/scaler.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2013-2014 Collabora, Ltd." + } +] diff --git a/src/3rdparty/wayland/protocols/scaler/scaler.xml b/src/3rdparty/wayland/protocols/scaler/scaler.xml new file mode 100644 index 00000000000..0e482a63518 --- /dev/null +++ b/src/3rdparty/wayland/protocols/scaler/scaler.xml @@ -0,0 +1,208 @@ + + + + + Copyright © 2013-2014 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The global interface exposing surface cropping and scaling + capabilities is used to instantiate an interface extension for a + wl_surface object. This extended interface will then allow + cropping and scaling the surface contents, effectively + disconnecting the direct relationship between the buffer and the + surface size. + + + + + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other objects, + wl_viewport objects included. + + + + + + + + + + Instantiate an interface extension for the given wl_surface to + crop and scale its content. If the given wl_surface already has + a wl_viewport object associated, the viewport_exists + protocol error is raised. + + + + + + + + + + An additional interface to a wl_surface object, which allows the + client to specify the cropping and scaling of the surface + contents. + + This interface allows to define the source rectangle (src_x, + src_y, src_width, src_height) from where to take the wl_buffer + contents, and scale that to destination size (dst_width, + dst_height). This state is double-buffered, and is applied on the + next wl_surface.commit. + + The two parts of crop and scale state are independent: the source + rectangle, and the destination size. Initially both are unset, that + is, no scaling is applied. The whole of the current wl_buffer is + used as the source, and the surface size is as defined in + wl_surface.attach. + + If the destination size is set, it causes the surface size to become + dst_width, dst_height. The source (rectangle) is scaled to exactly + this size. This overrides whatever the attached wl_buffer size is, + unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface + has no content and therefore no size. Otherwise, the size is always + at least 1x1 in surface coordinates. + + If the source rectangle is set, it defines what area of the + wl_buffer is taken as the source. If the source rectangle is set and + the destination size is not set, the surface size becomes the source + rectangle size rounded up to the nearest integer. If the source size + is already exactly integers, this results in cropping without scaling. + + The coordinate transformations from buffer pixel coordinates up to + the surface-local coordinates happen in the following order: + 1. buffer_transform (wl_surface.set_buffer_transform) + 2. buffer_scale (wl_surface.set_buffer_scale) + 3. crop and scale (wl_viewport.set*) + This means, that the source rectangle coordinates of crop and scale + are given in the coordinates after the buffer transform and scale, + i.e. in the coordinates that would be the surface-local coordinates + if the crop and scale was not applied. + + If the source rectangle is partially or completely outside of the + wl_buffer, then the surface contents are undefined (not void), and + the surface size is still dst_width, dst_height. + + The x, y arguments of wl_surface.attach are applied as normal to + the surface. They indicate how many pixels to remove from the + surface size from the left and the top. In other words, they are + still in the surface-local coordinate system, just like dst_width + and dst_height are. + + If the wl_surface associated with the wl_viewport is destroyed, + the wl_viewport object becomes inert. + + If the wl_viewport object is destroyed, the crop and scale + state is removed from the wl_surface. The change will be applied + on the next wl_surface.commit. + + + + + The associated wl_surface's crop and scale state is removed. + The change is applied on the next wl_surface.commit. + + + + + + + + + + Set both source rectangle and destination size of the associated + wl_surface. See wl_viewport for the description, and relation to + the wl_buffer size. + + The bad_value protocol error is raised if src_width or + src_height is negative, or if dst_width or dst_height is not + positive. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + Arguments dst_x and dst_y do not exist here, use the x and y + arguments to wl_surface.attach. The x, y, dst_width, and dst_height + define the surface-local coordinate system irrespective of the + attached wl_buffer size. + + + + + + + + + + + + + Set the source rectangle of the associated wl_surface. See + wl_viewport for the description, and relation to the wl_buffer + size. + + If width is -1.0 and height is -1.0, the source rectangle is unset + instead. Any other pair of values for width and height that + contains zero or negative values raises the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + + + + + + Set the destination size of the associated wl_surface. See + wl_viewport for the description, and relation to the wl_buffer + size. + + If width is -1 and height is -1, the destination size is unset + instead. Any other pair of values for width and height that + contains zero or negative values raises the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + Arguments x and y do not exist here, use the x and y arguments to + wl_surface.attach. The x, y, width, and height define the + surface-local coordinate system irrespective of the attached + wl_buffer size. + + + + + + + diff --git a/src/3rdparty/wayland/protocols/tablet/REUSE.toml b/src/3rdparty/wayland/protocols/tablet/REUSE.toml new file mode 100644 index 00000000000..76b350204df --- /dev/null +++ b/src/3rdparty/wayland/protocols/tablet/REUSE.toml @@ -0,0 +1,8 @@ +version = 1 + +[[annotations]] +path = "tablet-unstable-v2.xml" +precedence = "closest" +SPDX-FileCopyrightText = ["Copyright 2014 Stephen \"Lyude\" Chandler Paul", + "Copyright 2015-2016 Red Hat, Inc."] +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/tablet/qt_attribution.json b/src/3rdparty/wayland/protocols/tablet/qt_attribution.json new file mode 100644 index 00000000000..150eb2fbc4c --- /dev/null +++ b/src/3rdparty/wayland/protocols/tablet/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-tablet-protocol", + "Name": "Wayland Tablet Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "tablet-unstable-v2.xml", + + "Description": "", + "Homepage": "https://wayland.freedesktop.org", + "Version": "unstable v2, version 1", + "DownloadLocation": "https://cgit.freedesktop.org/wayland/wayland-protocols/plain/unstable/tablet/tablet-unstable-v2.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright 2014 © Stephen \"Lyude\" Chandler Paul\nCopyright 2015-2016 © Red Hat, Inc." + } +] diff --git a/src/3rdparty/wayland/protocols/tablet/tablet-unstable-v2.xml b/src/3rdparty/wayland/protocols/tablet/tablet-unstable-v2.xml new file mode 100644 index 00000000000..b286d964af8 --- /dev/null +++ b/src/3rdparty/wayland/protocols/tablet/tablet-unstable-v2.xml @@ -0,0 +1,1178 @@ + + + + + Copyright 2014 © Stephen "Lyude" Chandler Paul + Copyright 2015-2016 © Red Hat, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice (including the + next paragraph) shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + + + This description provides a high-level overview of the interplay between + the interfaces defined this protocol. For details, see the protocol + specification. + + More than one tablet may exist, and device-specifics matter. Tablets are + not represented by a single virtual device like wl_pointer. A client + binds to the tablet manager object which is just a proxy object. From + that, the client requests wp_tablet_manager.get_tablet_seat(wl_seat) + and that returns the actual interface that has all the tablets. With + this indirection, we can avoid merging wp_tablet into the actual Wayland + protocol, a long-term benefit. + + The wp_tablet_seat sends a "tablet added" event for each tablet + connected. That event is followed by descriptive events about the + hardware; currently that includes events for name, vid/pid and + a wp_tablet.path event that describes a local path. This path can be + used to uniquely identify a tablet or get more information through + libwacom. Emulated or nested tablets can skip any of those, e.g. a + virtual tablet may not have a vid/pid. The sequence of descriptive + events is terminated by a wp_tablet.done event to signal that a client + may now finalize any initialization for that tablet. + + Events from tablets require a tool in proximity. Tools are also managed + by the tablet seat; a "tool added" event is sent whenever a tool is new + to the compositor. That event is followed by a number of descriptive + events about the hardware; currently that includes capabilities, + hardware id and serial number, and tool type. Similar to the tablet + interface, a wp_tablet_tool.done event is sent to terminate that initial + sequence. + + Any event from a tool happens on the wp_tablet_tool interface. When the + tool gets into proximity of the tablet, a proximity_in event is sent on + the wp_tablet_tool interface, listing the tablet and the surface. That + event is followed by a motion event with the coordinates. After that, + it's the usual motion, axis, button, etc. events. The protocol's + serialisation means events are grouped by wp_tablet_tool.frame events. + + Two special events (that don't exist in X) are down and up. They signal + "tip touching the surface". For tablets without real proximity + detection, the sequence is: proximity_in, motion, down, frame. + + When the tool leaves proximity, a proximity_out event is sent. If any + button is still down, a button release event is sent before this + proximity event. These button events are sent in the same frame as the + proximity event to signal to the client that the buttons were held when + the tool left proximity. + + If the tool moves out of the surface but stays in proximity (i.e. + between windows), compositor-specific grab policies apply. This usually + means that the proximity-out is delayed until all buttons are released. + + Moving a tool physically from one tablet to the other has no real effect + on the protocol, since we already have the tool object from the "tool + added" event. All the information is already there and the proximity + events on both tablets are all a client needs to reconstruct what + happened. + + Some extra axes are normalized, i.e. the client knows the range as + specified in the protocol (e.g. [0, 65535]), the granularity however is + unknown. The current normalized axes are pressure, distance, and slider. + + Other extra axes are in physical units as specified in the protocol. + The current extra axes with physical units are tilt, rotation and + wheel rotation. + + Since tablets work independently of the pointer controlled by the mouse, + the focus handling is independent too and controlled by proximity. + The wp_tablet_tool.set_cursor request sets a tool-specific cursor. + This cursor surface may be the same as the mouse cursor, and it may be + the same across tools but it is possible to be more fine-grained. For + example, a client may set different cursors for the pen and eraser. + + Tools are generally independent of tablets and it is + compositor-specific policy when a tool can be removed. Common approaches + will likely include some form of removing a tool when all tablets the + tool was used on are removed. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + An object that provides access to the graphics tablets available on this + system. All tablets are associated with a seat, to get access to the + actual tablets, use wp_tablet_manager.get_tablet_seat. + + + + + Get the wp_tablet_seat object for the given seat. This object + provides access to all graphics tablets in this seat. + + + + + + + + Destroy the wp_tablet_manager object. Objects created from this + object are unaffected and should be destroyed separately. + + + + + + + An object that provides access to the graphics tablets available on this + seat. After binding to this interface, the compositor sends a set of + wp_tablet_seat.tablet_added and wp_tablet_seat.tool_added events. + + + + + Destroy the wp_tablet_seat object. Objects created from this + object are unaffected and should be destroyed separately. + + + + + + This event is sent whenever a new tablet becomes available on this + seat. This event only provides the object id of the tablet, any + static information about the tablet (device name, vid/pid, etc.) is + sent through the wp_tablet interface. + + + + + + + This event is sent whenever a tool that has not previously been used + with a tablet comes into use. This event only provides the object id + of the tool; any static information about the tool (capabilities, + type, etc.) is sent through the wp_tablet_tool interface. + + + + + + + This event is sent whenever a new pad is known to the system. Typically, + pads are physically attached to tablets and a pad_added event is + sent immediately after the wp_tablet_seat.tablet_added. + However, some standalone pad devices logically attach to tablets at + runtime, and the client must wait for wp_tablet_pad.enter to know + the tablet a pad is attached to. + + This event only provides the object id of the pad. All further + features (buttons, strips, rings) are sent through the wp_tablet_pad + interface. + + + + + + + + An object that represents a physical tool that has been, or is + currently in use with a tablet in this seat. Each wp_tablet_tool + object stays valid until the client destroys it; the compositor + reuses the wp_tablet_tool object to indicate that the object's + respective physical tool has come into proximity of a tablet again. + + A wp_tablet_tool object's relation to a physical tool depends on the + tablet's ability to report serial numbers. If the tablet supports + this capability, then the object represents a specific physical tool + and can be identified even when used on multiple tablets. + + A tablet tool has a number of static characteristics, e.g. tool type, + hardware_serial and capabilities. These capabilities are sent in an + event sequence after the wp_tablet_seat.tool_added event before any + actual events from this tool. This initial event sequence is + terminated by a wp_tablet_tool.done event. + + Tablet tool events are grouped by wp_tablet_tool.frame events. + Any events received before a wp_tablet_tool.frame event should be + considered part of the same hardware state change. + + + + + Sets the surface of the cursor used for this tool on the given + tablet. This request only takes effect if the tool is in proximity + of one of the requesting client's surfaces or the surface parameter + is the current pointer surface. If there was a previous surface set + with this request it is replaced. If surface is NULL, the cursor + image is hidden. + + The parameters hotspot_x and hotspot_y define the position of the + pointer surface relative to the pointer location. Its top-left corner + is always at (x, y) - (hotspot_x, hotspot_y), where (x, y) are the + coordinates of the pointer location, in surface-local coordinates. + + On surface.attach requests to the pointer surface, hotspot_x and + hotspot_y are decremented by the x and y parameters passed to the + request. Attach must be confirmed by wl_surface.commit as usual. + + The hotspot can also be updated by passing the currently set pointer + surface to this request with new values for hotspot_x and hotspot_y. + + The current and pending input regions of the wl_surface are cleared, + and wl_surface.set_input_region is ignored until the wl_surface is no + longer used as the cursor. When the use as a cursor ends, the current + and pending input regions become undefined, and the wl_surface is + unmapped. + + This request gives the surface the role of a wp_tablet_tool cursor. A + surface may only ever be used as the cursor surface for one + wp_tablet_tool. If the surface already has another role or has + previously been used as cursor surface for a different tool, a + protocol error is raised. + + + + + + + + + + This destroys the client's resource for this tool object. + + + + + + Describes the physical type of a tool. The physical type of a tool + generally defines its base usage. + + The mouse tool represents a mouse-shaped tool that is not a relative + device but bound to the tablet's surface, providing absolute + coordinates. + + The lens tool is a mouse-shaped tool with an attached lens to + provide precision focus. + + + + + + + + + + + + + + The tool type is the high-level type of the tool and usually decides + the interaction expected from this tool. + + This event is sent in the initial burst of events before the + wp_tablet_tool.done event. + + + + + + + If the physical tool can be identified by a unique 64-bit serial + number, this event notifies the client of this serial number. + + If multiple tablets are available in the same seat and the tool is + uniquely identifiable by the serial number, that tool may move + between tablets. + + Otherwise, if the tool has no serial number and this event is + missing, the tool is tied to the tablet it first comes into + proximity with. Even if the physical tool is used on multiple + tablets, separate wp_tablet_tool objects will be created, one per + tablet. + + This event is sent in the initial burst of events before the + wp_tablet_tool.done event. + + + + + + + + This event notifies the client of a hardware id available on this tool. + + The hardware id is a device-specific 64-bit id that provides extra + information about the tool in use, beyond the wl_tool.type + enumeration. The format of the id is specific to tablets made by + Wacom Inc. For example, the hardware id of a Wacom Grip + Pen (a stylus) is 0x802. + + This event is sent in the initial burst of events before the + wp_tablet_tool.done event. + + + + + + + + Describes extra capabilities on a tablet. + + Any tool must provide x and y values, extra axes are + device-specific. + + + + + + + + + + + + This event notifies the client of any capabilities of this tool, + beyond the main set of x/y axes and tip up/down detection. + + One event is sent for each extra capability available on this tool. + + This event is sent in the initial burst of events before the + wp_tablet_tool.done event. + + + + + + + This event signals the end of the initial burst of descriptive + events. A client may consider the static description of the tool to + be complete and finalize initialization of the tool. + + + + + + This event is sent when the tool is removed from the system and will + send no further events. Should the physical tool come back into + proximity later, a new wp_tablet_tool object will be created. + + It is compositor-dependent when a tool is removed. A compositor may + remove a tool on proximity out, tablet removal or any other reason. + A compositor may also keep a tool alive until shutdown. + + If the tool is currently in proximity, a proximity_out event will be + sent before the removed event. See wp_tablet_tool.proximity_out for + the handling of any buttons logically down. + + When this event is received, the client must wp_tablet_tool.destroy + the object. + + + + + + Notification that this tool is focused on a certain surface. + + This event can be received when the tool has moved from one surface to + another, or when the tool has come back into proximity above the + surface. + + If any button is logically down when the tool comes into proximity, + the respective button event is sent after the proximity_in event but + within the same frame as the proximity_in event. + + + + + + + + + Notification that this tool has either left proximity, or is no + longer focused on a certain surface. + + When the tablet tool leaves proximity of the tablet, button release + events are sent for each button that was held down at the time of + leaving proximity. These events are sent before the proximity_out + event but within the same wp_tablet.frame. + + If the tool stays within proximity of the tablet, but the focus + changes from one surface to another, a button release event may not + be sent until the button is actually released or the tool leaves the + proximity of the tablet. + + + + + + Sent whenever the tablet tool comes in contact with the surface of the + tablet. + + If the tool is already in contact with the tablet when entering the + input region, the client owning said region will receive a + wp_tablet.proximity_in event, followed by a wp_tablet.down + event and a wp_tablet.frame event. + + Note that this event describes logical contact, not physical + contact. On some devices, a compositor may not consider a tool in + logical contact until a minimum physical pressure threshold is + exceeded. + + + + + + + Sent whenever the tablet tool stops making contact with the surface of + the tablet, or when the tablet tool moves out of the input region + and the compositor grab (if any) is dismissed. + + If the tablet tool moves out of the input region while in contact + with the surface of the tablet and the compositor does not have an + ongoing grab on the surface, the client owning said region will + receive a wp_tablet.up event, followed by a wp_tablet.proximity_out + event and a wp_tablet.frame event. If the compositor has an ongoing + grab on this device, this event sequence is sent whenever the grab + is dismissed in the future. + + Note that this event describes logical contact, not physical + contact. On some devices, a compositor may not consider a tool out + of logical contact until physical pressure falls below a specific + threshold. + + + + + + Sent whenever a tablet tool moves. + + + + + + + + Sent whenever the pressure axis on a tool changes. The value of this + event is normalized to a value between 0 and 65535. + + Note that pressure may be nonzero even when a tool is not in logical + contact. See the down and up events for more details. + + + + + + + Sent whenever the distance axis on a tool changes. The value of this + event is normalized to a value between 0 and 65535. + + Note that distance may be nonzero even when a tool is not in logical + contact. See the down and up events for more details. + + + + + + + Sent whenever one or both of the tilt axes on a tool change. Each tilt + value is in degrees, relative to the z-axis of the tablet. + The angle is positive when the top of a tool tilts along the + positive x or y axis. + + + + + + + + Sent whenever the z-rotation axis on the tool changes. The + rotation value is in degrees clockwise from the tool's + logical neutral position. + + + + + + + Sent whenever the slider position on the tool changes. The + value is normalized between -65535 and 65535, with 0 as the logical + neutral position of the slider. + + The slider is available on e.g. the Wacom Airbrush tool. + + + + + + + Sent whenever the wheel on the tool emits an event. This event + contains two values for the same axis change. The degrees value is + in the same orientation as the wl_pointer.vertical_scroll axis. The + clicks value is in discrete logical clicks of the mouse wheel. This + value may be zero if the movement of the wheel was less + than one logical click. + + Clients should choose either value and avoid mixing degrees and + clicks. The compositor may accumulate values smaller than a logical + click and emulate click events when a certain threshold is met. + Thus, wl_tablet_tool.wheel events with non-zero clicks values may + have different degrees values. + + + + + + + + Describes the physical state of a button that produced the button event. + + + + + + + + Sent whenever a button on the tool is pressed or released. + + If a button is held down when the tool moves in or out of proximity, + button events are generated by the compositor. See + wp_tablet_tool.proximity_in and wp_tablet_tool.proximity_out for + details. + + + + + + + + + Marks the end of a series of axis and/or button updates from the + tablet. The Wayland protocol requires axis updates to be sent + sequentially, however all events within a frame should be considered + one hardware event. + + + + + + + + + + + + The wp_tablet interface represents one graphics tablet device. The + tablet interface itself does not generate events; all events are + generated by wp_tablet_tool objects when in proximity above a tablet. + + A tablet has a number of static characteristics, e.g. device name and + pid/vid. These capabilities are sent in an event sequence after the + wp_tablet_seat.tablet_added event. This initial event sequence is + terminated by a wp_tablet.done event. + + + + + This destroys the client's resource for this tablet object. + + + + + + This event is sent in the initial burst of events before the + wp_tablet.done event. + + + + + + + This event is sent in the initial burst of events before the + wp_tablet.done event. + + + + + + + + A system-specific device path that indicates which device is behind + this wp_tablet. This information may be used to gather additional + information about the device, e.g. through libwacom. + + A device may have more than one device path. If so, multiple + wp_tablet.path events are sent. A device may be emulated and not + have a device path, and in that case this event will not be sent. + + The format of the path is unspecified, it may be a device node, a + sysfs path, or some other identifier. It is up to the client to + identify the string provided. + + This event is sent in the initial burst of events before the + wp_tablet.done event. + + + + + + + This event is sent immediately to signal the end of the initial + burst of descriptive events. A client may consider the static + description of the tablet to be complete and finalize initialization + of the tablet. + + + + + + Sent when the tablet has been removed from the system. When a tablet + is removed, some tools may be removed. + + When this event is received, the client must wp_tablet.destroy + the object. + + + + + + + A circular interaction area, such as the touch ring on the Wacom Intuos + Pro series tablets. + + Events on a ring are logically grouped by the wl_tablet_pad_ring.frame + event. + + + + + Request that the compositor use the provided feedback string + associated with this ring. This request should be issued immediately + after a wp_tablet_pad_group.mode_switch event from the corresponding + group is received, or whenever the ring is mapped to a different + action. See wp_tablet_pad_group.mode_switch for more details. + + Clients are encouraged to provide context-aware descriptions for + the actions associated with the ring; compositors may use this + information to offer visual feedback about the button layout + (eg. on-screen displays). + + The provided string 'description' is a UTF-8 encoded string to be + associated with this ring, and is considered user-visible; general + internationalization rules apply. + + The serial argument will be that of the last + wp_tablet_pad_group.mode_switch event received for the group of this + ring. Requests providing other serials than the most recent one will be + ignored. + + + + + + + + This destroys the client's resource for this ring object. + + + + + + Describes the source types for ring events. This indicates to the + client how a ring event was physically generated; a client may + adjust the user interface accordingly. For example, events + from a "finger" source may trigger kinetic scrolling. + + + + + + + Source information for ring events. + + This event does not occur on its own. It is sent before a + wp_tablet_pad_ring.frame event and carries the source information + for all events within that frame. + + The source specifies how this event was generated. If the source is + wp_tablet_pad_ring.source.finger, a wp_tablet_pad_ring.stop event + will be sent when the user lifts the finger off the device. + + This event is optional. If the source is unknown for an interaction, + no event is sent. + + + + + + + Sent whenever the angle on a ring changes. + + The angle is provided in degrees clockwise from the logical + north of the ring in the pad's current rotation. + + + + + + + Stop notification for ring events. + + For some wp_tablet_pad_ring.source types, a wp_tablet_pad_ring.stop + event is sent to notify a client that the interaction with the ring + has terminated. This enables the client to implement kinetic scrolling. + See the wp_tablet_pad_ring.source documentation for information on + when this event may be generated. + + Any wp_tablet_pad_ring.angle events with the same source after this + event should be considered as the start of a new interaction. + + + + + + Indicates the end of a set of ring events that logically belong + together. A client is expected to accumulate the data in all events + within the frame before proceeding. + + All wp_tablet_pad_ring events before a wp_tablet_pad_ring.frame event belong + logically together. For example, on termination of a finger interaction + on a ring the compositor will send a wp_tablet_pad_ring.source event, + a wp_tablet_pad_ring.stop event and a wp_tablet_pad_ring.frame event. + + A wp_tablet_pad_ring.frame event is sent for every logical event + group, even if the group only contains a single wp_tablet_pad_ring + event. Specifically, a client may get a sequence: angle, frame, + angle, frame, etc. + + + + + + + + A linear interaction area, such as the strips found in Wacom Cintiq + models. + + Events on a strip are logically grouped by the wl_tablet_pad_strip.frame + event. + + + + + Requests the compositor to use the provided feedback string + associated with this strip. This request should be issued immediately + after a wp_tablet_pad_group.mode_switch event from the corresponding + group is received, or whenever the strip is mapped to a different + action. See wp_tablet_pad_group.mode_switch for more details. + + Clients are encouraged to provide context-aware descriptions for + the actions associated with the strip, and compositors may use this + information to offer visual feedback about the button layout + (eg. on-screen displays). + + The provided string 'description' is a UTF-8 encoded string to be + associated with this ring, and is considered user-visible; general + internationalization rules apply. + + The serial argument will be that of the last + wp_tablet_pad_group.mode_switch event received for the group of this + strip. Requests providing other serials than the most recent one will be + ignored. + + + + + + + + This destroys the client's resource for this strip object. + + + + + + Describes the source types for strip events. This indicates to the + client how a strip event was physically generated; a client may + adjust the user interface accordingly. For example, events + from a "finger" source may trigger kinetic scrolling. + + + + + + + Source information for strip events. + + This event does not occur on its own. It is sent before a + wp_tablet_pad_strip.frame event and carries the source information + for all events within that frame. + + The source specifies how this event was generated. If the source is + wp_tablet_pad_strip.source.finger, a wp_tablet_pad_strip.stop event + will be sent when the user lifts their finger off the device. + + This event is optional. If the source is unknown for an interaction, + no event is sent. + + + + + + + Sent whenever the position on a strip changes. + + The position is normalized to a range of [0, 65535], the 0-value + represents the top-most and/or left-most position of the strip in + the pad's current rotation. + + + + + + + Stop notification for strip events. + + For some wp_tablet_pad_strip.source types, a wp_tablet_pad_strip.stop + event is sent to notify a client that the interaction with the strip + has terminated. This enables the client to implement kinetic + scrolling. See the wp_tablet_pad_strip.source documentation for + information on when this event may be generated. + + Any wp_tablet_pad_strip.position events with the same source after this + event should be considered as the start of a new interaction. + + + + + + Indicates the end of a set of events that represent one logical + hardware strip event. A client is expected to accumulate the data + in all events within the frame before proceeding. + + All wp_tablet_pad_strip events before a wp_tablet_pad_strip.frame event belong + logically together. For example, on termination of a finger interaction + on a strip the compositor will send a wp_tablet_pad_strip.source event, + a wp_tablet_pad_strip.stop event and a wp_tablet_pad_strip.frame + event. + + A wp_tablet_pad_strip.frame event is sent for every logical event + group, even if the group only contains a single wp_tablet_pad_strip + event. Specifically, a client may get a sequence: position, frame, + position, frame, etc. + + + + + + + + A pad group describes a distinct (sub)set of buttons, rings and strips + present in the tablet. The criteria of this grouping is usually positional, + eg. if a tablet has buttons on the left and right side, 2 groups will be + presented. The physical arrangement of groups is undisclosed and may + change on the fly. + + Pad groups will announce their features during pad initialization. Between + the corresponding wp_tablet_pad.group event and wp_tablet_pad_group.done, the + pad group will announce the buttons, rings and strips contained in it, + plus the number of supported modes. + + Modes are a mechanism to allow multiple groups of actions for every element + in the pad group. The number of groups and available modes in each is + persistent across device plugs. The current mode is user-switchable, it + will be announced through the wp_tablet_pad_group.mode_switch event both + whenever it is switched, and after wp_tablet_pad.enter. + + The current mode logically applies to all elements in the pad group, + although it is at clients' discretion whether to actually perform different + actions, and/or issue the respective .set_feedback requests to notify the + compositor. See the wp_tablet_pad_group.mode_switch event for more details. + + + + + Destroy the wp_tablet_pad_group object. Objects created from this object + are unaffected and should be destroyed separately. + + + + + + Sent on wp_tablet_pad_group initialization to announce the available + buttons in the group. Button indices start at 0, a button may only be + in one group at a time. + + This event is first sent in the initial burst of events before the + wp_tablet_pad_group.done event. + + Some buttons are reserved by the compositor. These buttons may not be + assigned to any wp_tablet_pad_group. Compositors may broadcast this + event in the case of changes to the mapping of these reserved buttons. + If the compositor happens to reserve all buttons in a group, this event + will be sent with an empty array. + + + + + + + Sent on wp_tablet_pad_group initialization to announce available rings. + One event is sent for each ring available on this pad group. + + This event is sent in the initial burst of events before the + wp_tablet_pad_group.done event. + + + + + + + Sent on wp_tablet_pad initialization to announce available strips. + One event is sent for each strip available on this pad group. + + This event is sent in the initial burst of events before the + wp_tablet_pad_group.done event. + + + + + + + Sent on wp_tablet_pad_group initialization to announce that the pad + group may switch between modes. A client may use a mode to store a + specific configuration for buttons, rings and strips and use the + wl_tablet_pad_group.mode_switch event to toggle between these + configurations. Mode indices start at 0. + + Switching modes is compositor-dependent. See the + wp_tablet_pad_group.mode_switch event for more details. + + This event is sent in the initial burst of events before the + wp_tablet_pad_group.done event. This event is only sent when more than + more than one mode is available. + + + + + + + This event is sent immediately to signal the end of the initial + burst of descriptive events. A client may consider the static + description of the tablet to be complete and finalize initialization + of the tablet group. + + + + + + Notification that the mode was switched. + + A mode applies to all buttons, rings and strips in a group + simultaneously, but a client is not required to assign different actions + for each mode. For example, a client may have mode-specific button + mappings but map the ring to vertical scrolling in all modes. Mode + indices start at 0. + + Switching modes is compositor-dependent. The compositor may provide + visual cues to the client about the mode, e.g. by toggling LEDs on + the tablet device. Mode-switching may be software-controlled or + controlled by one or more physical buttons. For example, on a Wacom + Intuos Pro, the button inside the ring may be assigned to switch + between modes. + + The compositor will also send this event after wp_tablet_pad.enter on + each group in order to notify of the current mode. Groups that only + feature one mode will use mode=0 when emitting this event. + + If a button action in the new mode differs from the action in the + previous mode, the client should immediately issue a + wp_tablet_pad.set_feedback request for each changed button. + + If a ring or strip action in the new mode differs from the action + in the previous mode, the client should immediately issue a + wp_tablet_ring.set_feedback or wp_tablet_strip.set_feedback request + for each changed ring or strip. + + + + + + + + + + A pad device is a set of buttons, rings and strips + usually physically present on the tablet device itself. Some + exceptions exist where the pad device is physically detached, e.g. the + Wacom ExpressKey Remote. + + Pad devices have no axes that control the cursor and are generally + auxiliary devices to the tool devices used on the tablet surface. + + A pad device has a number of static characteristics, e.g. the number + of rings. These capabilities are sent in an event sequence after the + wp_tablet_seat.pad_added event before any actual events from this pad. + This initial event sequence is terminated by a wp_tablet_pad.done + event. + + All pad features (buttons, rings and strips) are logically divided into + groups and all pads have at least one group. The available groups are + notified through the wp_tablet_pad.group event; the compositor will + emit one event per group before emitting wp_tablet_pad.done. + + Groups may have multiple modes. Modes allow clients to map multiple + actions to a single pad feature. Only one mode can be active per group, + although different groups may have different active modes. + + + + + Requests the compositor to use the provided feedback string + associated with this button. This request should be issued immediately + after a wp_tablet_pad_group.mode_switch event from the corresponding + group is received, or whenever a button is mapped to a different + action. See wp_tablet_pad_group.mode_switch for more details. + + Clients are encouraged to provide context-aware descriptions for + the actions associated with each button, and compositors may use + this information to offer visual feedback on the button layout + (e.g. on-screen displays). + + Button indices start at 0. Setting the feedback string on a button + that is reserved by the compositor (i.e. not belonging to any + wp_tablet_pad_group) does not generate an error but the compositor + is free to ignore the request. + + The provided string 'description' is a UTF-8 encoded string to be + associated with this ring, and is considered user-visible; general + internationalization rules apply. + + The serial argument will be that of the last + wp_tablet_pad_group.mode_switch event received for the group of this + button. Requests providing other serials than the most recent one will + be ignored. + + + + + + + + + Destroy the wp_tablet_pad object. Objects created from this object + are unaffected and should be destroyed separately. + + + + + + Sent on wp_tablet_pad initialization to announce available groups. + One event is sent for each pad group available. + + This event is sent in the initial burst of events before the + wp_tablet_pad.done event. At least one group will be announced. + + + + + + + A system-specific device path that indicates which device is behind + this wp_tablet_pad. This information may be used to gather additional + information about the device, e.g. through libwacom. + + The format of the path is unspecified, it may be a device node, a + sysfs path, or some other identifier. It is up to the client to + identify the string provided. + + This event is sent in the initial burst of events before the + wp_tablet_pad.done event. + + + + + + + Sent on wp_tablet_pad initialization to announce the available + buttons. + + This event is sent in the initial burst of events before the + wp_tablet_pad.done event. This event is only sent when at least one + button is available. + + + + + + + This event signals the end of the initial burst of descriptive + events. A client may consider the static description of the pad to + be complete and finalize initialization of the pad. + + + + + + Describes the physical state of a button that caused the button + event. + + + + + + + + Sent whenever the physical state of a button changes. + + + + + + + + + Notification that this pad is focused on the specified surface. + + + + + + + + + Notification that this pad is no longer focused on the specified + surface. + + + + + + + + Sent when the pad has been removed from the system. When a tablet + is removed its pad(s) will be removed too. + + When this event is received, the client must destroy all rings, strips + and groups that were offered by this pad, and issue wp_tablet_pad.destroy + the pad itself. + + + + diff --git a/src/3rdparty/wayland/protocols/text-input/v1/REUSE.toml b/src/3rdparty/wayland/protocols/text-input/v1/REUSE.toml new file mode 100644 index 00000000000..cb531189ce9 --- /dev/null +++ b/src/3rdparty/wayland/protocols/text-input/v1/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "text-input-unstable-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2012, 2013 Intel Corporation" +SPDX-License-Identifier = "MIT" \ No newline at end of file diff --git a/src/3rdparty/wayland/protocols/text-input/v1/qt_attribution.json b/src/3rdparty/wayland/protocols/text-input/v1/qt_attribution.json new file mode 100644 index 00000000000..ea0d59237c2 --- /dev/null +++ b/src/3rdparty/wayland/protocols/text-input/v1/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-text-input-unstable-v1", + "Name": "Wayland Text Input Protocol v1", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "text-input-unstable-v1.xml", + + "Description": "Adds support for text input and input methods to applications running on Wayland servers that only support text-input-unstable-v1.", + "Homepage": "https://wayland.freedesktop.org", + "Version": "unstable v1", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/unstable/text-input/text-input-unstable-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../../MIT_LICENSE.txt", + "Copyright": "Copyright © 2012, 2013 Intel Corporation" + } +] diff --git a/src/3rdparty/wayland/protocols/text-input/v1/text-input-unstable-v1.xml b/src/3rdparty/wayland/protocols/text-input/v1/text-input-unstable-v1.xml new file mode 100644 index 00000000000..6ee266522a5 --- /dev/null +++ b/src/3rdparty/wayland/protocols/text-input/v1/text-input-unstable-v1.xml @@ -0,0 +1,385 @@ + + + + + Copyright © 2012, 2013 Intel Corporation + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + An object used for text input. Adds support for text input and input + methods to applications. A text_input object is created from a + wl_text_input_manager and corresponds typically to a text entry in an + application. + + Requests are used to activate/deactivate the text_input object and set + state information like surrounding and selected text or the content type. + The information about entered text is sent to the text_input object via + the pre-edit and commit events. Using this interface removes the need + for applications to directly process hardware key events and compose text + out of them. + + Text is generally UTF-8 encoded, indices and lengths are in bytes. + + Serials are used to synchronize the state between the text input and + an input method. New serials are sent by the text input in the + commit_state request and are used by the input method to indicate + the known text input state in events like preedit_string, commit_string, + and keysym. The text input can then ignore events from the input method + which are based on an outdated state (for example after a reset). + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Requests the text_input object to be activated (typically when the + text entry gets focus). + + The seat argument is a wl_seat which maintains the focus for this + activation. The surface argument is a wl_surface assigned to the + text_input object and tracked for focus lost. The enter event + is emitted on successful activation. + + + + + + + + Requests the text_input object to be deactivated (typically when the + text entry lost focus). The seat argument is a wl_seat which was used + for activation. + + + + + + + Requests input panels (virtual keyboard) to show. + + + + + + Requests input panels (virtual keyboard) to hide. + + + + + + Should be called by an editor widget when the input state should be + reset, for example after the text was changed outside of the normal + input method flow. + + + + + + Sets the plain surrounding text around the input position. Text is + UTF-8 encoded. Cursor is the byte offset within the + surrounding text. Anchor is the byte offset of the + selection anchor within the surrounding text. If there is no selected + text anchor, then it is the same as cursor. + + + + + + + + + Content hint is a bitmask to allow to modify the behavior of the text + input. + + + + + + + + + + + + + + + + + + + The content purpose allows to specify the primary purpose of a text + input. + + This allows an input method to show special purpose input panels with + extra characters or to disallow some characters. + + + + + + + + + + + + + + + + + + + Sets the content purpose and content hint. While the purpose is the + basic purpose of an input field, the hint flags allow to modify some + of the behavior. + + When no content type is explicitly set, a normal content purpose with + default hints (auto completion, auto correction, auto capitalization) + should be assumed. + + + + + + + + + + + + + + + Sets a specific language. This allows for example a virtual keyboard to + show a language specific layout. The "language" argument is an RFC-3066 + format language tag. + + It could be used for example in a word processor to indicate the + language of the currently edited document or in an instant message + application which tracks languages of contacts. + + + + + + + + + + + + + + + + Notify the text_input object when it received focus. Typically in + response to an activate request. + + + + + + + Notify the text_input object when it lost focus. Either in response + to a deactivate request or when the assigned surface lost focus or was + destroyed. + + + + + + Transfer an array of 0-terminated modifier names. The position in + the array is the index of the modifier as used in the modifiers + bitmask in the keysym event. + + + + + + + Notify when the visibility state of the input panel changed. + + + + + + + Notify when a new composing text (pre-edit) should be set around the + current cursor position. Any previously set composing text should + be removed. + + The commit text can be used to replace the preedit text on reset + (for example on unfocus). + + The text input should also handle all preedit_style and preedit_cursor + events occurring directly before preedit_string. + + + + + + + + + + + + + + + + + + + + Sets styling information on composing text. The style is applied for + length bytes from index relative to the beginning of the composing + text (as byte offset). Multiple styles can + be applied to a composing text by sending multiple preedit_styling + events. + + This event is handled as part of a following preedit_string event. + + + + + + + + + Sets the cursor position inside the composing text (as byte + offset) relative to the start of the composing text. When index is a + negative number no cursor is shown. + + This event is handled as part of a following preedit_string event. + + + + + + + Notify when text should be inserted into the editor widget. The text to + commit could be either just a single character after a key press or the + result of some composing (pre-edit). It could also be an empty text + when some text should be removed (see delete_surrounding_text) or when + the input cursor should be moved (see cursor_position). + + Any previously set composing text should be removed. + + + + + + + + Notify when the cursor or anchor position should be modified. + + This event should be handled as part of a following commit_string + event. + + + + + + + + Notify when the text around the current cursor position should be + deleted. + + Index is relative to the current cursor (in bytes). + Length is the length of deleted text (in bytes). + + This event should be handled as part of a following commit_string + event. + + + + + + + + Notify when a key event was sent. Key events should not be used + for normal text input operations, which should be done with + commit_string, delete_surrounding_text, etc. The key event follows + the wl_keyboard key event convention. Sym is an XKB keysym, state a + wl_keyboard key_state. Modifiers are a mask for effective modifiers + (where the modifier indices are set by the modifiers_map event) + + + + + + + + + + + Sets the language of the input text. The "language" argument is an + RFC-3066 format language tag. + + + + + + + + + + + + + + Sets the text direction of input text. + + It is mainly needed for showing an input cursor on the correct side of + the editor when there is no input done yet and making sure neutral + direction text is laid out properly. + + + + + + + + + A factory for text_input objects. This object is a global singleton. + + + + + Creates a new text_input object. + + + + + + diff --git a/src/3rdparty/wayland/protocols/text-input/v2/HPND_LICENSE.txt b/src/3rdparty/wayland/protocols/text-input/v2/HPND_LICENSE.txt new file mode 100644 index 00000000000..e7a4915da0a --- /dev/null +++ b/src/3rdparty/wayland/protocols/text-input/v2/HPND_LICENSE.txt @@ -0,0 +1,20 @@ +Permission to use, copy, modify, distribute, and sell this +software and its documentation for any purpose is hereby granted +without fee, provided that the above copyright notice appear in +all copies and that both that copyright notice and this permission +notice appear in supporting documentation, and that the name of +the copyright holders not be used in advertising or publicity +pertaining to distribution of the software without specific, +written prior permission. The copyright holders make no +representations about the suitability of this software for any +purpose. It is provided "as is" without express or implied +warranty. + +THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/src/3rdparty/wayland/protocols/text-input/v2/REUSE.toml b/src/3rdparty/wayland/protocols/text-input/v2/REUSE.toml new file mode 100644 index 00000000000..3b826274165 --- /dev/null +++ b/src/3rdparty/wayland/protocols/text-input/v2/REUSE.toml @@ -0,0 +1,8 @@ +version = 1 + +[[annotations]] +path = "text-input-unstable-v2.xml" +precedence = "closest" +SPDX-FileCopyrightText = ["Copyright © 2012, 2013 Intel Corporation", + "Copyright © 2015, 2016 Jan Arne Petersen"] +SPDX-License-Identifier = "HPND" diff --git a/src/3rdparty/wayland/protocols/text-input/v2/qt_attribution.json b/src/3rdparty/wayland/protocols/text-input/v2/qt_attribution.json new file mode 100644 index 00000000000..f0be562662a --- /dev/null +++ b/src/3rdparty/wayland/protocols/text-input/v2/qt_attribution.json @@ -0,0 +1,17 @@ +[ + { + "Id": "wayland-text-input-unstable-v2", + "Name": "Wayland Text Input Protocol v2", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor, and the Qt Wayland platform plugin.", + "Files": "text-input-unstable-v2.xml", + + "Description": "Adds support for text input and input methods to applications.", + "Homepage": "https://wayland.freedesktop.org", + "Version": "unstable v2", + "LicenseId": "HPND", + "License": "HPND License", + "LicenseFile": "HPND_LICENSE.txt", + "Copyright": "Copyright © 2012, 2013 Intel Corporation\nCopyright © 2015, 2016 Jan Arne Petersen" + } +] diff --git a/src/3rdparty/wayland/protocols/text-input/v2/text-input-unstable-v2.xml b/src/3rdparty/wayland/protocols/text-input/v2/text-input-unstable-v2.xml new file mode 100644 index 00000000000..bb366c9215c --- /dev/null +++ b/src/3rdparty/wayland/protocols/text-input/v2/text-input-unstable-v2.xml @@ -0,0 +1,478 @@ + + + + + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The zwp_text_input_v2 interface represents text input and input methods + associated with a seat. It provides enter/leave events to follow the + text input focus for a seat. + + Requests are used to enable/disable the text-input object and set + state information like surrounding and selected text or the content type. + The information about the entered text is sent to the text-input object + via the pre-edit and commit events. Using this interface removes the need + for applications to directly process hardware key events and compose text + out of them. + + Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + have to always point to the first byte of an UTF-8 encoded code point. + Lengths are not allowed to contain just a part of an UTF-8 encoded code + point. + + State is sent by the state requests (set_surrounding_text, + set_content_type, set_cursor_rectangle and set_preferred_language) and + an update_state request. After an enter or an input_method_change event + all state information is invalidated and needs to be resent from the + client. A reset or entering a new widget on client side also + invalidates all current state information. + + + + + Destroy the wp_text_input object. Also disables all surfaces enabled + through this wp_text_input object + + + + + + Enable text input in a surface (usually when a text entry inside of it + has focus). + + This can be called before or after a surface gets text (or keyboard) + focus via the enter event. Text input to a surface is only active + when it has the current text (or keyboard) focus and is enabled. + + + + + + + Disable text input in a surface (typically when there is no focus on any + text entry inside the surface). + + + + + + + Requests input panels (virtual keyboard) to show. + + This should be used for example to show a virtual keyboard again + (with a tap) after it was closed by pressing on a close button on the + keyboard. + + + + + + Requests input panels (virtual keyboard) to hide. + + + + + + Sets the plain surrounding text around the input position. Text is + UTF-8 encoded. Cursor is the byte offset within the surrounding text. + Anchor is the byte offset of the selection anchor within the + surrounding text. If there is no selected text, anchor is the same as + cursor. + + Make sure to always send some text before and after the cursor + except when the cursor is at the beginning or end of text. + + When there was a configure_surrounding_text event take the + before_cursor and after_cursor arguments into account for picking how + much surrounding text to send. + + There is a maximum length of wayland messages so text can not be + longer than 4000 bytes. + + + + + + + + + Content hint is a bitmask to allow to modify the behavior of the text + input. + + + + + + + + + + + + + + + + + The content purpose allows to specify the primary purpose of a text + input. + + This allows an input method to show special purpose input panels with + extra characters or to disallow some characters. + + + + + + + + + + + + + + + + + + + Sets the content purpose and content hint. While the purpose is the + basic purpose of an input field, the hint flags allow to modify some + of the behavior. + + When no content type is explicitly set, a normal content purpose with + none hint should be assumed. + + + + + + + + Sets the cursor outline as a x, y, width, height rectangle in surface + local coordinates. + + Allows the compositor to put a window with word suggestions near the + cursor. + + + + + + + + + + Sets a specific language. This allows for example a virtual keyboard to + show a language specific layout. The "language" argument is a RFC-3066 + format language tag. + + It could be used for example in a word processor to indicate language of + currently edited document or in an instant message application which + tracks languages of contacts. + + + + + + + Defines the reason for sending an updated state. + + + + + + + + + + Allows to atomically send state updates from client. + + This request should follow after a batch of state updating requests + like set_surrounding_text, set_content_type, set_cursor_rectangle and + set_preferred_language. + + The flags field indicates why an updated state is sent to the input + method. + + Reset should be used by an editor widget after the text was changed + outside of the normal input method flow. + + For "change" it is enough to send the changed state, else the full + state should be send. + + Serial should be set to the serial from the last enter or + input_method_changed event. + + To make sure to not receive outdated input method events after a + reset or switching to a new widget wl_display_sync() should be used + after update_state in these cases. + + + + + + + + Notification that this seat's text-input focus is on a certain surface. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. + + + + + + + + Notification that this seat's text-input focus is no longer on + a certain surface. + + The leave notification is sent before the enter notification + for the new focus. + + When the seat has the keyboard capabillity the text-input focus follows + the keyboard focus. + + + + + + + + + + + + + Notification that the visibility of the input panel (virtual keyboard) + changed. + + The rectangle x, y, width, height defines the area overlapped by the + input panel (virtual keyboard) on the surface having the text + focus in surface local coordinates. + + That can be used to make sure widgets are visible and not covered by + a virtual keyboard. + + + + + + + + + + + Notify when a new composing text (pre-edit) should be set around the + current cursor position. Any previously set composing text should + be removed. + + The commit text can be used to replace the composing text in some cases + (for example when losing focus). + + The text input should also handle all preedit_style and preedit_cursor + events occurring directly before preedit_string. + + + + + + + + + + + + + + + + + + + Sets styling information on composing text. The style is applied for + length bytes from index relative to the beginning of the composing + text (as byte offset). Multiple styles can be applied to a composing + text by sending multiple preedit_styling events. + + This event is handled as part of a following preedit_string event. + + + + + + + + + Sets the cursor position inside the composing text (as byte + offset) relative to the start of the composing text. When index is a + negative number no cursor is shown. + + When no preedit_cursor event is sent the cursor will be at the end of + the composing text by default. + + This event is handled as part of a following preedit_string event. + + + + + + + Notify when text should be inserted into the editor widget. The text to + commit could be either just a single character after a key press or the + result of some composing (pre-edit). It could be also an empty text + when some text should be removed (see delete_surrounding_text) or when + the input cursor should be moved (see cursor_position). + + Any previously set composing text should be removed. + + + + + + + Notify when the cursor or anchor position should be modified. + + This event should be handled as part of a following commit_string + event. + + The text between anchor and index should be selected. + + + + + + + + Notify when the text around the current cursor position should be + deleted. BeforeLength and afterLength is the length (in bytes) of text + before and after the current cursor position (excluding the selection) + to delete. + + This event should be handled as part of a following commit_string + or preedit_string event. + + + + + + + + Transfer an array of 0-terminated modifiers names. The position in + the array is the index of the modifier as used in the modifiers + bitmask in the keysym event. + + + + + + + Notify when a key event was sent. Key events should not be used + for normal text input operations, which should be done with + commit_string, delete_surrounding_text, etc. The key event follows + the wl_keyboard key event convention. Sym is a XKB keysym, state a + wl_keyboard key_state. Modifiers are a mask for effective modifiers + (where the modifier indices are set by the modifiers_map event) + + + + + + + + + + Sets the language of the input text. The "language" argument is a RFC-3066 + format language tag. + + + + + + + + + + + + + Sets the text direction of input text. + + It is mainly needed for showing input cursor on correct side of the + editor when there is no input yet done and making sure neutral + direction text is laid out properly. + + + + + + + Configure what amount of surrounding text is expected by the + input method. The surrounding text will be sent in the + set_surrounding_text request on the following state information updates. + + + + + + + + The input method changed on compositor side, which invalidates all + current state information. New state information should be sent from + the client via state requests (set_surrounding_text, + set_content_hint, ...) and update_state. + + + + + + + + + A factory for text-input objects. This object is a global singleton. + + + + + Destroy the wp_text_input_manager object. + + + + + + Creates a new text-input object for a given seat. + + + + + + diff --git a/src/3rdparty/wayland/protocols/text-input/v3/REUSE.toml b/src/3rdparty/wayland/protocols/text-input/v3/REUSE.toml new file mode 100644 index 00000000000..3e243736483 --- /dev/null +++ b/src/3rdparty/wayland/protocols/text-input/v3/REUSE.toml @@ -0,0 +1,10 @@ +version = 1 + +[[annotations]] +path = "text-input-unstable-v3.xml" +precedence = "closest" +SPDX-FileCopyrightText = ["Copyright 2012, 2013 Intel Corporation", + "Copyright 2015, 2016 Jan Arne Petersen", + "Copyright 2017, 2018 Red Hat, Inc.", + "Copyright 2018 Purism SPC"] +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/text-input/v3/qt_attribution.json b/src/3rdparty/wayland/protocols/text-input/v3/qt_attribution.json new file mode 100644 index 00000000000..2a9226e3ea4 --- /dev/null +++ b/src/3rdparty/wayland/protocols/text-input/v3/qt_attribution.json @@ -0,0 +1,17 @@ +[ + { + "Id": "wayland-text-input-unstable-v3", + "Name": "Wayland Text Input Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor, and the Qt Wayland platform plugin.", + "Files": "text-input-unstable-v3.xml", + "Description": "Adds support for compositors to act as input methods and send text to applications.", + "Homepage": "https://wayland.freedesktop.org", + "Version": "unstable v3", + "DownloadLocation": "https://cgit.freedesktop.org/wayland/wayland-protocols/plain/unstable/text-input/text-input-unstable-v3.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../../MIT_LICENSE.txt", + "Copyright": "Copyright © 2012, 2013 Intel Corporation\nCopyright © 2015, 2016 Jan Arne Petersen\nCopyright © 2017, 2018 Red Hat, Inc.\nCopyright © 2018 Purism SPC" + } +] diff --git a/src/3rdparty/wayland/protocols/text-input/v3/text-input-unstable-v3.xml b/src/3rdparty/wayland/protocols/text-input/v3/text-input-unstable-v3.xml new file mode 100644 index 00000000000..1fae54d7ba7 --- /dev/null +++ b/src/3rdparty/wayland/protocols/text-input/v3/text-input-unstable-v3.xml @@ -0,0 +1,457 @@ + + + + + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + Copyright © 2017, 2018 Red Hat, Inc. + Copyright © 2018 Purism SPC + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol allows compositors to act as input methods and to send text + to applications. A text input object is used to manage state of what are + typically text entry fields in the application. + + This document adheres to the RFC 2119 when using words like "must", + "should", "may", etc. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + The zwp_text_input_v3 interface represents text input and input methods + associated with a seat. It provides enter/leave events to follow the + text input focus for a seat. + + Requests are used to enable/disable the text-input object and set + state information like surrounding and selected text or the content type. + The information about the entered text is sent to the text-input object + via the preedit_string and commit_string events. + + Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + must not point to middle bytes inside a code point: they must either + point to the first byte of a code point or to the end of the buffer. + Lengths must be measured between two valid indices. + + Focus moving throughout surfaces will result in the emission of + zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused + surface must commit zwp_text_input_v3.enable and + zwp_text_input_v3.disable requests as the keyboard focus moves across + editable and non-editable elements of the UI. Those two requests are not + expected to be paired with each other, the compositor must be able to + handle consecutive series of the same request. + + State is sent by the state requests (set_surrounding_text, + set_content_type and set_cursor_rectangle) and a commit request. After an + enter event or disable request all state information is invalidated and + needs to be resent by the client. + + + + + Destroy the wp_text_input object. Also disables all surfaces enabled + through this wp_text_input object. + + + + + + Requests text input on the surface previously obtained from the enter + event. + + This request must be issued every time the active text input changes + to a new one, including within the current surface. Use + zwp_text_input_v3.disable when there is no longer any input focus on + the current surface. + + Clients must not enable more than one text input on the single seat + and should disable the current text input before enabling the new one. + At most one instance of text input may be in enabled state per instance, + Requests to enable the another text input when some text input is active + must be ignored by compositor. + + This request resets all state associated with previous enable, disable, + set_surrounding_text, set_text_change_cause, set_content_type, and + set_cursor_rectangle requests, as well as the state associated with + preedit_string, commit_string, and delete_surrounding_text events. + + The set_surrounding_text, set_content_type and set_cursor_rectangle + requests must follow if the text input supports the necessary + functionality. + + State set with this request is double-buffered. It will get applied on + the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The changes must be applied by the compositor after issuing a + zwp_text_input_v3.commit request. + + + + + + Explicitly disable text input on the current surface (typically when + there is no focus on any text entry inside the surface). + + State set with this request is double-buffered. It will get applied on + the next zwp_text_input_v3.commit request. + + + + + + Sets the surrounding plain text around the input, excluding the preedit + text. + + The client should notify the compositor of any changes in any of the + values carried with this request, including changes caused by handling + incoming text-input events as well as changes caused by other + mechanisms like keyboard typing. + + If the client is unaware of the text around the cursor, it should not + issue this request, to signify lack of support to the compositor. + + Text is UTF-8 encoded, and should include the cursor position, the + complete selection and additional characters before and after them. + There is a maximum length of wayland messages, so text can not be + longer than 4000 bytes. + + Cursor is the byte offset of the cursor within text buffer. + + Anchor is the byte offset of the selection anchor within text buffer. + If there is no selected text, anchor is the same as cursor. + + If any preedit text is present, it is replaced with a cursor for the + purpose of this event. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The initial state for affected fields is empty, meaning that the text + input does not support sending surrounding text. If the empty values + get applied, subsequent attempts to change them may have no effect. + + + + + + + + + Reason for the change of surrounding text or cursor posision. + + + + + + + + Tells the compositor why the text surrounding the cursor changed. + + Whenever the client detects an external change in text, cursor, or + anchor posision, it must issue this request to the compositor. This + request is intended to give the input method a chance to update the + preedit text in an appropriate way, e.g. by removing it when the user + starts typing with a keyboard. + + cause describes the source of the change. + + The value set with this request is double-buffered. It must be applied + and reset to initial at the next zwp_text_input_v3.commit request. + + The initial value of cause is input_method. + + + + + + + Content hint is a bitmask to allow to modify the behavior of the text + input. + + + + + + + + + + + + + + + + + The content purpose allows to specify the primary purpose of a text + input. + + This allows an input method to show special purpose input panels with + extra characters or to disallow some characters. + + + + + + + + + + + + + + + + + + + + Sets the content purpose and content hint. While the purpose is the + basic purpose of an input field, the hint flags allow to modify some of + the behavior. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request. + Subsequent attempts to update them may have no effect. The values + remain valid until the next committed enable or disable request. + + The initial value for hint is none, and the initial value for purpose + is normal. + + + + + + + + Marks an area around the cursor as a x, y, width, height rectangle in + surface local coordinates. + + Allows the compositor to put a window with word suggestions near the + cursor, without obstructing the text being input. + + If the client is unaware of the position of edited text, it should not + issue this request, to signify lack of support to the compositor. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The initial values describing a cursor rectangle are empty. That means + the text input does not support describing the cursor area. If the + empty values get applied, subsequent attempts to change them may have + no effect. + + + + + + + + + + Atomically applies state changes recently sent to the compositor. + + The commit request establishes and updates the state of the client, and + must be issued after any changes to apply them. + + Text input state (enabled status, content purpose, content hint, + surrounding text and change cause, cursor rectangle) is conceptually + double-buffered within the context of a text input, i.e. between a + committed enable request and the following committed enable or disable + request. + + Protocol requests modify the pending state, as opposed to the current + state in use by the input method. A commit request atomically applies + all pending state, replacing the current state. After commit, the new + pending state is as documented for each related request. + + Requests are applied in the order of arrival. + + Neither current nor pending state are modified unless noted otherwise. + + The compositor must count the number of commit requests coming from + each zwp_text_input_v3 object and use the count as the serial in done + events. + + + + + + Notification that this seat's text-input focus is on a certain surface. + + If client has created multiple text input objects, compositor must send + this event to all of them. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. This event sets the current surface for the + text-input object. + + + + + + + Notification that this seat's text-input focus is no longer on a + certain surface. The client should reset any preedit string previously + set. + + The leave notification clears the current surface. It is sent before + the enter notification for the new focus. After leave event, compositor + must ignore requests from any text input instances until next enter + event. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. + + + + + + + Notify when a new composing text (pre-edit) should be set at the + current cursor position. Any previously set composing text must be + removed. Any previously existing selected text must be removed. + + The argument text contains the pre-edit string buffer. + + The parameters cursor_begin and cursor_end are counted in bytes + relative to the beginning of the submitted text buffer. Cursor should + be hidden when both are equal to -1. + + They could be represented by the client as a line if both values are + the same, or as a text highlight otherwise. + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial value of text is an empty string, and cursor_begin, + cursor_end and cursor_hidden are all 0. + + + + + + + + + Notify when text should be inserted into the editor widget. The text to + commit could be either just a single character after a key press or the + result of some composing (pre-edit). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial value of text is an empty string. + + + + + + + Notify when the text around the current cursor position should be + deleted. + + Before_length and after_length are the number of bytes before and after + the current cursor index (excluding the selection) to delete. + + If a preedit text is present, in effect before_length is counted from + the beginning of it, and after_length from its end (see done event + sequence). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial values of both before_length and after_length are 0. + + + + + + + + Instruct the application to apply changes to state requested by the + preedit_string, commit_string and delete_surrounding_text events. The + state relating to these events is double-buffered, and each one + modifies the pending state. This event replaces the current state with + the pending state. + + The application must proceed by evaluating the changes in the following + order: + + 1. Replace existing preedit string with the cursor. + 2. Delete requested surrounding text. + 3. Insert commit string with the cursor at its end. + 4. Calculate surrounding text to send. + 5. Insert new preedit text in cursor position. + 6. Place cursor inside preedit text. + + The serial number reflects the last state of the zwp_text_input_v3 + object known to the compositor. The value of the serial argument must + be equal to the number of commit requests already issued on that object. + + When the client receives a done event with a serial different than the + number of past commit requests, it must proceed with evaluating and + applying the changes as normal, except it should not change the current + state of the zwp_text_input_v3 object. All pending state requests + (set_surrounding_text, set_content_type and set_cursor_rectangle) on + the zwp_text_input_v3 object should be sent and committed after + receiving a zwp_text_input_v3.done event with a matching serial. + + + + + + + + A factory for text-input objects. This object is a global singleton. + + + + + Destroy the wp_text_input_manager object. + + + + + + Creates a new text-input object for a given seat. + + + + + + diff --git a/src/3rdparty/wayland/protocols/viewporter/REUSE.toml b/src/3rdparty/wayland/protocols/viewporter/REUSE.toml new file mode 100644 index 00000000000..692f7dcb045 --- /dev/null +++ b/src/3rdparty/wayland/protocols/viewporter/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "viewporter.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2013-2016 Collabora, Ltd." +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/viewporter/qt_attribution.json b/src/3rdparty/wayland/protocols/viewporter/qt_attribution.json new file mode 100644 index 00000000000..fbdda2c0294 --- /dev/null +++ b/src/3rdparty/wayland/protocols/viewporter/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-viewporter-protocol", + "Name": "Wayland Viewporter Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor API", + "Files": "viewporter.xml", + + "Description": "The Wayland viewporter extension allows a client to scale or crop a surface without modifying the buffer", + "Homepage": "https://wayland.freedesktop.org", + "Version": "1", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/raw/1.18/stable/viewporter/viewporter.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2013-2016 Collabora, Ltd." + } +] diff --git a/src/3rdparty/wayland/protocols/viewporter/viewporter.xml b/src/3rdparty/wayland/protocols/viewporter/viewporter.xml new file mode 100644 index 00000000000..c732d8c35bc --- /dev/null +++ b/src/3rdparty/wayland/protocols/viewporter/viewporter.xml @@ -0,0 +1,186 @@ + + + + + Copyright © 2013-2016 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The global interface exposing surface cropping and scaling + capabilities is used to instantiate an interface extension for a + wl_surface object. This extended interface will then allow + cropping and scaling the surface contents, effectively + disconnecting the direct relationship between the buffer and the + surface size. + + + + + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other objects, + wp_viewport objects included. + + + + + + + + + + Instantiate an interface extension for the given wl_surface to + crop and scale its content. If the given wl_surface already has + a wp_viewport object associated, the viewport_exists + protocol error is raised. + + + + + + + + + An additional interface to a wl_surface object, which allows the + client to specify the cropping and scaling of the surface + contents. + + This interface works with two concepts: the source rectangle (src_x, + src_y, src_width, src_height), and the destination size (dst_width, + dst_height). The contents of the source rectangle are scaled to the + destination size, and content outside the source rectangle is ignored. + This state is double-buffered, and is applied on the next + wl_surface.commit. + + The two parts of crop and scale state are independent: the source + rectangle, and the destination size. Initially both are unset, that + is, no scaling is applied. The whole of the current wl_buffer is + used as the source, and the surface size is as defined in + wl_surface.attach. + + If the destination size is set, it causes the surface size to become + dst_width, dst_height. The source (rectangle) is scaled to exactly + this size. This overrides whatever the attached wl_buffer size is, + unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface + has no content and therefore no size. Otherwise, the size is always + at least 1x1 in surface local coordinates. + + If the source rectangle is set, it defines what area of the wl_buffer is + taken as the source. If the source rectangle is set and the destination + size is not set, then src_width and src_height must be integers, and the + surface size becomes the source rectangle size. This results in cropping + without scaling. If src_width or src_height are not integers and + destination size is not set, the bad_size protocol error is raised when + the surface state is applied. + + The coordinate transformations from buffer pixel coordinates up to + the surface-local coordinates happen in the following order: + 1. buffer_transform (wl_surface.set_buffer_transform) + 2. buffer_scale (wl_surface.set_buffer_scale) + 3. crop and scale (wp_viewport.set*) + This means, that the source rectangle coordinates of crop and scale + are given in the coordinates after the buffer transform and scale, + i.e. in the coordinates that would be the surface-local coordinates + if the crop and scale was not applied. + + If src_x or src_y are negative, the bad_value protocol error is raised. + Otherwise, if the source rectangle is partially or completely outside of + the non-NULL wl_buffer, then the out_of_buffer protocol error is raised + when the surface state is applied. A NULL wl_buffer does not raise the + out_of_buffer error. + + The x, y arguments of wl_surface.attach are applied as normal to + the surface. They indicate how many pixels to remove from the + surface size from the left and the top. In other words, they are + still in the surface-local coordinate system, just like dst_width + and dst_height are. + + If the wl_surface associated with the wp_viewport is destroyed, + all wp_viewport requests except 'destroy' raise the protocol error + no_surface. + + If the wp_viewport object is destroyed, the crop and scale + state is removed from the wl_surface. The change will be applied + on the next wl_surface.commit. + + + + + The associated wl_surface's crop and scale state is removed. + The change is applied on the next wl_surface.commit. + + + + + + + + + + + + + Set the source rectangle of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If all of x, y, width and height are -1.0, the source rectangle is + unset instead. Any other set of values where width or height are zero + or negative, or x or y are negative, raise the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + + + + + Set the destination size of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If width is -1 and height is -1, the destination size is unset + instead. Any other pair of values for width and height that + contains zero or negative values raises the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + + diff --git a/src/3rdparty/wayland/protocols/wayland/README b/src/3rdparty/wayland/protocols/wayland/README new file mode 100644 index 00000000000..d37bad09f63 --- /dev/null +++ b/src/3rdparty/wayland/protocols/wayland/README @@ -0,0 +1 @@ +wayland.xml from wayland version defined in qtwayland/README diff --git a/src/3rdparty/wayland/protocols/wayland/REUSE.toml b/src/3rdparty/wayland/protocols/wayland/REUSE.toml new file mode 100644 index 00000000000..c15f4e8275a --- /dev/null +++ b/src/3rdparty/wayland/protocols/wayland/REUSE.toml @@ -0,0 +1,9 @@ +version = 1 + +[[annotations]] +path = "wayland.xml" +precedence = "closest" +SPDX-FileCopyrightText = ["Copyright © 2008-2011 Kristian Høgsberg", + "Copyright © 2010-2011 Intel Corporation", + "Copyright © 2012-2013 Collabora, Ltd."] +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/wayland/qt_attribution.json b/src/3rdparty/wayland/protocols/wayland/qt_attribution.json new file mode 100644 index 00000000000..413d1503767 --- /dev/null +++ b/src/3rdparty/wayland/protocols/wayland/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-protocol", + "Name": "Wayland Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor, and the Qt Wayland platform plugin.", + "Files": "wayland.xml", + + "Description": "Wayland is a protocol for a compositor to talk to its clients.", + "Homepage": "https://wayland.freedesktop.org", + "Version": "1.23.0", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland/raw/1.23.0/protocol/wayland.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2008-2011 Kristian Høgsberg\nCopyright © 2010-2011 Intel Corporation\nCopyright © 2012-2013 Collabora, Ltd." + } +] diff --git a/src/3rdparty/wayland/protocols/wayland/wayland.xml b/src/3rdparty/wayland/protocols/wayland/wayland.xml new file mode 100644 index 00000000000..9418c62f3f6 --- /dev/null +++ b/src/3rdparty/wayland/protocols/wayland/wayland.xml @@ -0,0 +1,3248 @@ + + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2011 Intel Corporation + Copyright © 2012-2013 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice (including the + next paragraph) shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + + + + The core global object. This is a special singleton object. It + is used for internal Wayland protocol features. + + + + + The sync request asks the server to emit the 'done' event + on the returned wl_callback object. Since requests are + handled in-order and events are delivered in-order, this can + be used as a barrier to ensure all previous requests and the + resulting events have been handled. + + The object returned by this request will be destroyed by the + compositor after the callback is fired and as such the client must not + attempt to use it after that point. + + The callback_data passed in the callback is undefined and should be ignored. + + + + + + + This request creates a registry object that allows the client + to list and bind the global objects available from the + compositor. + + It should be noted that the server side resources consumed in + response to a get_registry request can only be released when the + client disconnects, not when the client side proxy is destroyed. + Therefore, clients should invoke get_registry as infrequently as + possible to avoid wasting memory. + + + + + + + The error event is sent out when a fatal (non-recoverable) + error has occurred. The object_id argument is the object + where the error occurred, most often in response to a request + to that object. The code identifies the error and is defined + by the object interface. As such, each interface defines its + own set of error codes. The message is a brief description + of the error, for (debugging) convenience. + + + + + + + + + These errors are global and can be emitted in response to any + server request. + + + + + + + + + + This event is used internally by the object ID management + logic. When a client deletes an object that it had created, + the server will send this event to acknowledge that it has + seen the delete request. When the client receives this event, + it will know that it can safely reuse the object ID. + + + + + + + + The singleton global registry object. The server has a number of + global objects that are available to all clients. These objects + typically represent an actual object in the server (for example, + an input device) or they are singleton objects that provide + extension functionality. + + When a client creates a registry object, the registry object + will emit a global event for each global currently in the + registry. Globals come and go as a result of device or + monitor hotplugs, reconfiguration or other events, and the + registry will send out global and global_remove events to + keep the client up to date with the changes. To mark the end + of the initial burst of events, the client can use the + wl_display.sync request immediately after calling + wl_display.get_registry. + + A client can bind to a global object by using the bind + request. This creates a client-side handle that lets the object + emit events to the client and lets the client invoke requests on + the object. + + + + + Binds a new, client-created object to the server using the + specified name as the identifier. + + + + + + + + Notify the client of global objects. + + The event notifies the client that a global object with + the given name is now available, and it implements the + given version of the given interface. + + + + + + + + + Notify the client of removed global objects. + + This event notifies the client that the global identified + by name is no longer available. If the client bound to + the global using the bind request, the client should now + destroy that object. + + The object remains valid and requests to the object will be + ignored until the client destroys it, to avoid races between + the global going away and a client sending a request to it. + + + + + + + + Clients can handle the 'done' event to get notified when + the related request is done. + + Note, because wl_callback objects are created from multiple independent + factory interfaces, the wl_callback interface is frozen at version 1. + + + + + Notify the client when the related request is done. + + + + + + + + A compositor. This object is a singleton global. The + compositor is in charge of combining the contents of multiple + surfaces into one displayable output. + + + + + Ask the compositor to create a new surface. + + + + + + + Ask the compositor to create a new region. + + + + + + + + The wl_shm_pool object encapsulates a piece of memory shared + between the compositor and client. Through the wl_shm_pool + object, the client can allocate shared memory wl_buffer objects. + All objects created through the same pool share the same + underlying mapped memory. Reusing the mapped memory avoids the + setup/teardown overhead and is useful when interactively resizing + a surface or for many small buffers. + + + + + Create a wl_buffer object from the pool. + + The buffer is created offset bytes into the pool and has + width and height as specified. The stride argument specifies + the number of bytes from the beginning of one row to the beginning + of the next. The format is the pixel format of the buffer and + must be one of those advertised through the wl_shm.format event. + + A buffer will keep a reference to the pool it was created from + so it is valid to destroy the pool immediately after creating + a buffer from it. + + + + + + + + + + + + Destroy the shared memory pool. + + The mmapped memory will be released when all + buffers that have been created from this pool + are gone. + + + + + + This request will cause the server to remap the backing memory + for the pool from the file descriptor passed when the pool was + created, but using the new size. This request can only be + used to make the pool bigger. + + This request only changes the amount of bytes that are mmapped + by the server and does not touch the file corresponding to the + file descriptor passed at creation time. It is the client's + responsibility to ensure that the file is at least as big as + the new pool size. + + + + + + + + A singleton global object that provides support for shared + memory. + + Clients can create wl_shm_pool objects using the create_pool + request. + + On binding the wl_shm object one or more format events + are emitted to inform clients about the valid pixel formats + that can be used for buffers. + + + + + These errors can be emitted in response to wl_shm requests. + + + + + + + + + This describes the memory layout of an individual pixel. + + All renderers should support argb8888 and xrgb8888 but any other + formats are optional and may not be supported by the particular + renderer in use. + + The drm format codes match the macros defined in drm_fourcc.h, except + argb8888 and xrgb8888. The formats actually supported by the compositor + will be reported by the format event. + + For all wl_shm formats and unless specified in another protocol + extension, pre-multiplied alpha is used for pixel values. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create a new wl_shm_pool object. + + The pool can be used to create shared memory based buffer + objects. The server will mmap size bytes of the passed file + descriptor, to use as backing memory for the pool. + + + + + + + + + Informs the client about a valid pixel format that + can be used for buffers. Known formats include + argb8888 and xrgb8888. + + + + + + + + + Using this request a client can tell the server that it is not going to + use the shm object anymore. + + Objects created via this interface remain unaffected. + + + + + + + A buffer provides the content for a wl_surface. Buffers are + created through factory interfaces such as wl_shm, wp_linux_buffer_params + (from the linux-dmabuf protocol extension) or similar. It has a width and + a height and can be attached to a wl_surface, but the mechanism by which a + client provides and updates the contents is defined by the buffer factory + interface. + + Color channels are assumed to be electrical rather than optical (in other + words, encoded with a transfer function) unless otherwise specified. If + the buffer uses a format that has an alpha channel, the alpha channel is + assumed to be premultiplied into the electrical color channel values + (after transfer function encoding) unless otherwise specified. + + Note, because wl_buffer objects are created from multiple independent + factory interfaces, the wl_buffer interface is frozen at version 1. + + + + + Destroy a buffer. If and how you need to release the backing + storage is defined by the buffer factory interface. + + For possible side-effects to a surface, see wl_surface.attach. + + + + + + Sent when this wl_buffer is no longer used by the compositor. + The client is now free to reuse or destroy this buffer and its + backing storage. + + If a client receives a release event before the frame callback + requested in the same wl_surface.commit that attaches this + wl_buffer to a surface, then the client is immediately free to + reuse the buffer and its backing storage, and does not need a + second buffer for the next surface content update. Typically + this is possible, when the compositor maintains a copy of the + wl_surface contents, e.g. as a GL texture. This is an important + optimization for GL(ES) compositors with wl_shm clients. + + + + + + + A wl_data_offer represents a piece of data offered for transfer + by another client (the source client). It is used by the + copy-and-paste and drag-and-drop mechanisms. The offer + describes the different mime types that the data can be + converted to and provides the mechanism for transferring the + data directly from the source client. + + + + + + + + + + + + Indicate that the client can accept the given mime type, or + NULL for not accepted. + + For objects of version 2 or older, this request is used by the + client to give feedback whether the client can receive the given + mime type, or NULL if none is accepted; the feedback does not + determine whether the drag-and-drop operation succeeds or not. + + For objects of version 3 or newer, this request determines the + final result of the drag-and-drop operation. If the end result + is that no mime types were accepted, the drag-and-drop operation + will be cancelled and the corresponding drag source will receive + wl_data_source.cancelled. Clients may still use this event in + conjunction with wl_data_source.action for feedback. + + + + + + + + To transfer the offered data, the client issues this request + and indicates the mime type it wants to receive. The transfer + happens through the passed file descriptor (typically created + with the pipe system call). The source client writes the data + in the mime type representation requested and then closes the + file descriptor. + + The receiving client reads from the read end of the pipe until + EOF and then closes its end, at which point the transfer is + complete. + + This request may happen multiple times for different mime types, + both before and after wl_data_device.drop. Drag-and-drop destination + clients may preemptively fetch data or examine it more closely to + determine acceptance. + + + + + + + + Destroy the data offer. + + + + + + Sent immediately after creating the wl_data_offer object. One + event per offered mime type. + + + + + + + + + Notifies the compositor that the drag destination successfully + finished the drag-and-drop operation. + + Upon receiving this request, the compositor will emit + wl_data_source.dnd_finished on the drag source client. + + It is a client error to perform other requests than + wl_data_offer.destroy after this one. It is also an error to perform + this request after a NULL mime type has been set in + wl_data_offer.accept or no action was received through + wl_data_offer.action. + + If wl_data_offer.finish request is received for a non drag and drop + operation, the invalid_finish protocol error is raised. + + + + + + Sets the actions that the destination side client supports for + this operation. This request may trigger the emission of + wl_data_source.action and wl_data_offer.action events if the compositor + needs to change the selected action. + + This request can be called multiple times throughout the + drag-and-drop operation, typically in response to wl_data_device.enter + or wl_data_device.motion events. + + This request determines the final result of the drag-and-drop + operation. If the end result is that no action is accepted, + the drag source will receive wl_data_source.cancelled. + + The dnd_actions argument must contain only values expressed in the + wl_data_device_manager.dnd_actions enum, and the preferred_action + argument must only contain one of those values set, otherwise it + will result in a protocol error. + + While managing an "ask" action, the destination drag-and-drop client + may perform further wl_data_offer.receive requests, and is expected + to perform one last wl_data_offer.set_actions request with a preferred + action other than "ask" (and optionally wl_data_offer.accept) before + requesting wl_data_offer.finish, in order to convey the action selected + by the user. If the preferred action is not in the + wl_data_offer.source_actions mask, an error will be raised. + + If the "ask" action is dismissed (e.g. user cancellation), the client + is expected to perform wl_data_offer.destroy right away. + + This request can only be made on drag-and-drop offers, a protocol error + will be raised otherwise. + + + + + + + + This event indicates the actions offered by the data source. It + will be sent immediately after creating the wl_data_offer object, + or anytime the source side changes its offered actions through + wl_data_source.set_actions. + + + + + + + This event indicates the action selected by the compositor after + matching the source/destination side actions. Only one action (or + none) will be offered here. + + This event can be emitted multiple times during the drag-and-drop + operation in response to destination side action changes through + wl_data_offer.set_actions. + + This event will no longer be emitted after wl_data_device.drop + happened on the drag-and-drop destination, the client must + honor the last action received, or the last preferred one set + through wl_data_offer.set_actions when handling an "ask" action. + + Compositors may also change the selected action on the fly, mainly + in response to keyboard modifier changes during the drag-and-drop + operation. + + The most recent action received is always the valid one. Prior to + receiving wl_data_device.drop, the chosen action may change (e.g. + due to keyboard modifiers being pressed). At the time of receiving + wl_data_device.drop the drag-and-drop destination must honor the + last action received. + + Action changes may still happen after wl_data_device.drop, + especially on "ask" actions, where the drag-and-drop destination + may choose another action afterwards. Action changes happening + at this stage are always the result of inter-client negotiation, the + compositor shall no longer be able to induce a different action. + + Upon "ask" actions, it is expected that the drag-and-drop destination + may potentially choose a different action and/or mime type, + based on wl_data_offer.source_actions and finally chosen by the + user (e.g. popping up a menu with the available options). The + final wl_data_offer.set_actions and wl_data_offer.accept requests + must happen before the call to wl_data_offer.finish. + + + + + + + + The wl_data_source object is the source side of a wl_data_offer. + It is created by the source client in a data transfer and + provides a way to describe the offered data and a way to respond + to requests to transfer the data. + + + + + + + + + + This request adds a mime type to the set of mime types + advertised to targets. Can be called several times to offer + multiple types. + + + + + + + Destroy the data source. + + + + + + Sent when a target accepts pointer_focus or motion events. If + a target does not accept any of the offered types, type is NULL. + + Used for feedback during drag-and-drop. + + + + + + + Request for data from the client. Send the data as the + specified mime type over the passed file descriptor, then + close it. + + + + + + + + This data source is no longer valid. There are several reasons why + this could happen: + + - The data source has been replaced by another data source. + - The drag-and-drop operation was performed, but the drop destination + did not accept any of the mime types offered through + wl_data_source.target. + - The drag-and-drop operation was performed, but the drop destination + did not select any of the actions present in the mask offered through + wl_data_source.action. + - The drag-and-drop operation was performed but didn't happen over a + surface. + - The compositor cancelled the drag-and-drop operation (e.g. compositor + dependent timeouts to avoid stale drag-and-drop transfers). + + The client should clean up and destroy this data source. + + For objects of version 2 or older, wl_data_source.cancelled will + only be emitted if the data source was replaced by another data + source. + + + + + + + + Sets the actions that the source side client supports for this + operation. This request may trigger wl_data_source.action and + wl_data_offer.action events if the compositor needs to change the + selected action. + + The dnd_actions argument must contain only values expressed in the + wl_data_device_manager.dnd_actions enum, otherwise it will result + in a protocol error. + + This request must be made once only, and can only be made on sources + used in drag-and-drop, so it must be performed before + wl_data_device.start_drag. Attempting to use the source other than + for drag-and-drop will raise a protocol error. + + + + + + + The user performed the drop action. This event does not indicate + acceptance, wl_data_source.cancelled may still be emitted afterwards + if the drop destination does not accept any mime type. + + However, this event might however not be received if the compositor + cancelled the drag-and-drop operation before this event could happen. + + Note that the data_source may still be used in the future and should + not be destroyed here. + + + + + + The drop destination finished interoperating with this data + source, so the client is now free to destroy this data source and + free all associated data. + + If the action used to perform the operation was "move", the + source can now delete the transferred data. + + + + + + This event indicates the action selected by the compositor after + matching the source/destination side actions. Only one action (or + none) will be offered here. + + This event can be emitted multiple times during the drag-and-drop + operation, mainly in response to destination side changes through + wl_data_offer.set_actions, and as the data device enters/leaves + surfaces. + + It is only possible to receive this event after + wl_data_source.dnd_drop_performed if the drag-and-drop operation + ended in an "ask" action, in which case the final wl_data_source.action + event will happen immediately before wl_data_source.dnd_finished. + + Compositors may also change the selected action on the fly, mainly + in response to keyboard modifier changes during the drag-and-drop + operation. + + The most recent action received is always the valid one. The chosen + action may change alongside negotiation (e.g. an "ask" action can turn + into a "move" operation), so the effects of the final action must + always be applied in wl_data_offer.dnd_finished. + + Clients can trigger cursor surface changes from this point, so + they reflect the current action. + + + + + + + + There is one wl_data_device per seat which can be obtained + from the global wl_data_device_manager singleton. + + A wl_data_device provides access to inter-client data transfer + mechanisms such as copy-and-paste and drag-and-drop. + + + + + + + + + + This request asks the compositor to start a drag-and-drop + operation on behalf of the client. + + The source argument is the data source that provides the data + for the eventual data transfer. If source is NULL, enter, leave + and motion events are sent only to the client that initiated the + drag and the client is expected to handle the data passing + internally. If source is destroyed, the drag-and-drop session will be + cancelled. + + The origin surface is the surface where the drag originates and + the client must have an active implicit grab that matches the + serial. + + The icon surface is an optional (can be NULL) surface that + provides an icon to be moved around with the cursor. Initially, + the top-left corner of the icon surface is placed at the cursor + hotspot, but subsequent wl_surface.offset requests can move the + relative position. Attach requests must be confirmed with + wl_surface.commit as usual. The icon surface is given the role of + a drag-and-drop icon. If the icon surface already has another role, + it raises a protocol error. + + The input region is ignored for wl_surfaces with the role of a + drag-and-drop icon. + + The given source may not be used in any further set_selection or + start_drag requests. Attempting to reuse a previously-used source + may send a used_source error. + + + + + + + + + + This request asks the compositor to set the selection + to the data from the source on behalf of the client. + + To unset the selection, set the source to NULL. + + The given source may not be used in any further set_selection or + start_drag requests. Attempting to reuse a previously-used source + may send a used_source error. + + + + + + + + The data_offer event introduces a new wl_data_offer object, + which will subsequently be used in either the + data_device.enter event (for drag-and-drop) or the + data_device.selection event (for selections). Immediately + following the data_device.data_offer event, the new data_offer + object will send out data_offer.offer events to describe the + mime types it offers. + + + + + + + This event is sent when an active drag-and-drop pointer enters + a surface owned by the client. The position of the pointer at + enter time is provided by the x and y arguments, in surface-local + coordinates. + + + + + + + + + + + This event is sent when the drag-and-drop pointer leaves the + surface and the session ends. The client must destroy the + wl_data_offer introduced at enter time at this point. + + + + + + This event is sent when the drag-and-drop pointer moves within + the currently focused surface. The new position of the pointer + is provided by the x and y arguments, in surface-local + coordinates. + + + + + + + + + The event is sent when a drag-and-drop operation is ended + because the implicit grab is removed. + + The drag-and-drop destination is expected to honor the last action + received through wl_data_offer.action, if the resulting action is + "copy" or "move", the destination can still perform + wl_data_offer.receive requests, and is expected to end all + transfers with a wl_data_offer.finish request. + + If the resulting action is "ask", the action will not be considered + final. The drag-and-drop destination is expected to perform one last + wl_data_offer.set_actions request, or wl_data_offer.destroy in order + to cancel the operation. + + + + + + The selection event is sent out to notify the client of a new + wl_data_offer for the selection for this device. The + data_device.data_offer and the data_offer.offer events are + sent out immediately before this event to introduce the data + offer object. The selection event is sent to a client + immediately before receiving keyboard focus and when a new + selection is set while the client has keyboard focus. The + data_offer is valid until a new data_offer or NULL is received + or until the client loses keyboard focus. Switching surface with + keyboard focus within the same client doesn't mean a new selection + will be sent. The client must destroy the previous selection + data_offer, if any, upon receiving this event. + + + + + + + + + This request destroys the data device. + + + + + + + The wl_data_device_manager is a singleton global object that + provides access to inter-client data transfer mechanisms such as + copy-and-paste and drag-and-drop. These mechanisms are tied to + a wl_seat and this interface lets a client get a wl_data_device + corresponding to a wl_seat. + + Depending on the version bound, the objects created from the bound + wl_data_device_manager object will have different requirements for + functioning properly. See wl_data_source.set_actions, + wl_data_offer.accept and wl_data_offer.finish for details. + + + + + Create a new data source. + + + + + + + Create a new data device for a given seat. + + + + + + + + + + This is a bitmask of the available/preferred actions in a + drag-and-drop operation. + + In the compositor, the selected action is a result of matching the + actions offered by the source and destination sides. "action" events + with a "none" action will be sent to both source and destination if + there is no match. All further checks will effectively happen on + (source actions ∩ destination actions). + + In addition, compositors may also pick different actions in + reaction to key modifiers being pressed. One common design that + is used in major toolkits (and the behavior recommended for + compositors) is: + + - If no modifiers are pressed, the first match (in bit order) + will be used. + - Pressing Shift selects "move", if enabled in the mask. + - Pressing Control selects "copy", if enabled in the mask. + + Behavior beyond that is considered implementation-dependent. + Compositors may for example bind other modifiers (like Alt/Meta) + or drags initiated with other buttons than BTN_LEFT to specific + actions (e.g. "ask"). + + + + + + + + + + + This interface is implemented by servers that provide + desktop-style user interfaces. + + It allows clients to associate a wl_shell_surface with + a basic surface. + + Note! This protocol is deprecated and not intended for production use. + For desktop-style user interfaces, use xdg_shell. Compositors and clients + should not implement this interface. + + + + + + + + + Create a shell surface for an existing surface. This gives + the wl_surface the role of a shell surface. If the wl_surface + already has another role, it raises a protocol error. + + Only one shell surface can be associated with a given surface. + + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides requests to treat surfaces like toplevel, fullscreen + or popup windows, move, resize or maximize them, associate + metadata like title and class, etc. + + On the server side the object is automatically destroyed when + the related wl_surface is destroyed. On the client side, + wl_shell_surface_destroy() must be called before destroying + the wl_surface object. + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. + + + + + + + Start a pointer-driven move of the surface. + + This request must be used in response to a button press event. + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized). + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. The server may + use this information to adapt its behavior, e.g. choose + an appropriate cursor image. + + + + + + + + + + + + + + + Start a pointer-driven resizing of the surface. + + This request must be used in response to a button press event. + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + + + + + + + + Map the surface as a toplevel surface. + + A toplevel surface is not fullscreen, maximized or transient. + + + + + + These flags specify details of the expected behaviour + of transient surfaces. Used in the set_transient request. + + + + + + + Map the surface relative to an existing surface. + + The x and y arguments specify the location of the upper left + corner of the surface relative to the upper left corner of the + parent surface, in surface-local coordinates. + + The flags argument controls details of the transient behaviour. + + + + + + + + + + Hints to indicate to the compositor how to deal with a conflict + between the dimensions of the surface and the dimensions of the + output. The compositor is free to ignore this parameter. + + + + + + + + + + Map the surface as a fullscreen surface. + + If an output parameter is given then the surface will be made + fullscreen on that output. If the client does not specify the + output then the compositor will apply its policy - usually + choosing the output on which the surface has the biggest surface + area. + + The client may specify a method to resolve a size conflict + between the output size and the surface size - this is provided + through the method parameter. + + The framerate parameter is used only when the method is set + to "driver", to indicate the preferred framerate. A value of 0 + indicates that the client does not care about framerate. The + framerate is specified in mHz, that is framerate of 60000 is 60Hz. + + A method of "scale" or "driver" implies a scaling operation of + the surface, either via a direct scaling operation or a change of + the output mode. This will override any kind of output scaling, so + that mapping a surface with a buffer size equal to the mode can + fill the screen independent of buffer_scale. + + A method of "fill" means we don't scale up the buffer, however + any output scale is applied. This means that you may run into + an edge case where the application maps a buffer with the same + size of the output mode but buffer_scale 1 (thus making a + surface larger than the output). In this case it is allowed to + downscale the results to fit the screen. + + The compositor must reply to this request with a configure event + with the dimensions for the output on which the surface will + be made fullscreen. + + + + + + + + + Map the surface as a popup. + + A popup surface is a transient surface with an added pointer + grab. + + An existing implicit grab will be changed to owner-events mode, + and the popup grab will continue after the implicit grab ends + (i.e. releasing the mouse button does not cause the popup to + be unmapped). + + The popup grab continues until the window is destroyed or a + mouse button is pressed in any other client's window. A click + in any of the client's surfaces is reported as normal, however, + clicks in other clients' surfaces will be discarded and trigger + the callback. + + The x and y arguments specify the location of the upper left + corner of the surface relative to the upper left corner of the + parent surface, in surface-local coordinates. + + + + + + + + + + + + Map the surface as a maximized surface. + + If an output parameter is given then the surface will be + maximized on that output. If the client does not specify the + output then the compositor will apply its policy - usually + choosing the output on which the surface has the biggest surface + area. + + The compositor will reply with a configure event telling + the expected new surface size. The operation is completed + on the next buffer attach to this surface. + + A maximized surface typically fills the entire output it is + bound to, except for desktop elements such as panels. This is + the main difference between a maximized shell surface and a + fullscreen shell surface. + + The details depend on the compositor implementation. + + + + + + + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + + + + + + + Set a class for the surface. + + The surface class identifies the general class of applications + to which the surface belongs. A common convention is to use the + file name (or the full path if it is a non-standard location) of + the application's .desktop file as the class. + + + + + + + Ping a client to check if it is receiving events and sending + requests. A client is expected to reply with a pong request. + + + + + + + The configure event asks the client to resize its surface. + + The size is a hint, in the sense that the client is free to + ignore it if it doesn't resize, pick a smaller size (to + satisfy aspect ratio or resize in steps of NxM pixels). + + The edges parameter provides a hint about how the surface + was resized. The client may use this information to decide + how to adjust its content to the new size (e.g. a scrolling + area might adjust its content position to leave the viewable + content unmoved). + + The client is free to dismiss all but the last configure + event it received. + + The width and height arguments specify the size of the window + in surface-local coordinates. + + + + + + + + + The popup_done event is sent out when a popup grab is broken, + that is, when the user clicks a surface that doesn't belong + to the client owning the popup surface. + + + + + + + A surface is a rectangular area that may be displayed on zero + or more outputs, and shown any number of times at the compositor's + discretion. They can present wl_buffers, receive user input, and + define a local coordinate system. + + The size of a surface (and relative positions on it) is described + in surface-local coordinates, which may differ from the buffer + coordinates of the pixel content, in case a buffer_transform + or a buffer_scale is used. + + A surface without a "role" is fairly useless: a compositor does + not know where, when or how to present it. The role is the + purpose of a wl_surface. Examples of roles are a cursor for a + pointer (as set by wl_pointer.set_cursor), a drag icon + (wl_data_device.start_drag), a sub-surface + (wl_subcompositor.get_subsurface), and a window as defined by a + shell protocol (e.g. wl_shell.get_shell_surface). + + A surface can have only one role at a time. Initially a + wl_surface does not have a role. Once a wl_surface is given a + role, it is set permanently for the whole lifetime of the + wl_surface object. Giving the current role again is allowed, + unless explicitly forbidden by the relevant interface + specification. + + Surface roles are given by requests in other interfaces such as + wl_pointer.set_cursor. The request should explicitly mention + that this request gives a role to a wl_surface. Often, this + request also creates a new protocol object that represents the + role and adds additional functionality to wl_surface. When a + client wants to destroy a wl_surface, they must destroy this role + object before the wl_surface, otherwise a defunct_role_object error is + sent. + + Destroying the role object does not remove the role from the + wl_surface, but it may stop the wl_surface from "playing the role". + For instance, if a wl_subsurface object is destroyed, the wl_surface + it was created for will be unmapped and forget its position and + z-order. It is allowed to create a wl_subsurface for the same + wl_surface again, but it is not allowed to use the wl_surface as + a cursor (cursor is a different role than sub-surface, and role + switching is not allowed). + + + + + These errors can be emitted in response to wl_surface requests. + + + + + + + + + + + Deletes the surface and invalidates its object ID. + + + + + + Set a buffer as the content of this surface. + + The new size of the surface is calculated based on the buffer + size transformed by the inverse buffer_transform and the + inverse buffer_scale. This means that at commit time the supplied + buffer size must be an integer multiple of the buffer_scale. If + that's not the case, an invalid_size error is sent. + + The x and y arguments specify the location of the new pending + buffer's upper left corner, relative to the current buffer's upper + left corner, in surface-local coordinates. In other words, the + x and y, combined with the new surface size define in which + directions the surface's size changes. Setting anything other than 0 + as x and y arguments is discouraged, and should instead be replaced + with using the separate wl_surface.offset request. + + When the bound wl_surface version is 5 or higher, passing any + non-zero x or y is a protocol violation, and will result in an + 'invalid_offset' error being raised. The x and y arguments are ignored + and do not change the pending state. To achieve equivalent semantics, + use wl_surface.offset. + + Surface contents are double-buffered state, see wl_surface.commit. + + The initial surface contents are void; there is no content. + wl_surface.attach assigns the given wl_buffer as the pending + wl_buffer. wl_surface.commit makes the pending wl_buffer the new + surface contents, and the size of the surface becomes the size + calculated from the wl_buffer, as described above. After commit, + there is no pending buffer until the next attach. + + Committing a pending wl_buffer allows the compositor to read the + pixels in the wl_buffer. The compositor may access the pixels at + any time after the wl_surface.commit request. When the compositor + will not access the pixels anymore, it will send the + wl_buffer.release event. Only after receiving wl_buffer.release, + the client may reuse the wl_buffer. A wl_buffer that has been + attached and then replaced by another attach instead of committed + will not receive a release event, and is not used by the + compositor. + + If a pending wl_buffer has been committed to more than one wl_surface, + the delivery of wl_buffer.release events becomes undefined. A well + behaved client should not rely on wl_buffer.release events in this + case. Alternatively, a client could create multiple wl_buffer objects + from the same backing storage or use wp_linux_buffer_release. + + Destroying the wl_buffer after wl_buffer.release does not change + the surface contents. Destroying the wl_buffer before wl_buffer.release + is allowed as long as the underlying buffer storage isn't re-used (this + can happen e.g. on client process termination). However, if the client + destroys the wl_buffer before receiving the wl_buffer.release event and + mutates the underlying buffer storage, the surface contents become + undefined immediately. + + If wl_surface.attach is sent with a NULL wl_buffer, the + following wl_surface.commit will remove the surface content. + + If a pending wl_buffer has been destroyed, the result is not specified. + Many compositors are known to remove the surface content on the following + wl_surface.commit, but this behaviour is not universal. Clients seeking to + maximise compatibility should not destroy pending buffers and should + ensure that they explicitly remove content from surfaces, even after + destroying buffers. + + + + + + + + + This request is used to describe the regions where the pending + buffer is different from the current surface contents, and where + the surface therefore needs to be repainted. The compositor + ignores the parts of the damage that fall outside of the surface. + + Damage is double-buffered state, see wl_surface.commit. + + The damage rectangle is specified in surface-local coordinates, + where x and y specify the upper left corner of the damage rectangle. + + The initial value for pending damage is empty: no damage. + wl_surface.damage adds pending damage: the new pending damage + is the union of old pending damage and the given rectangle. + + wl_surface.commit assigns pending damage as the current damage, + and clears pending damage. The server will clear the current + damage as it repaints the surface. + + Note! New clients should not use this request. Instead damage can be + posted with wl_surface.damage_buffer which uses buffer coordinates + instead of surface coordinates. + + + + + + + + + + Request a notification when it is a good time to start drawing a new + frame, by creating a frame callback. This is useful for throttling + redrawing operations, and driving animations. + + When a client is animating on a wl_surface, it can use the 'frame' + request to get notified when it is a good time to draw and commit the + next frame of animation. If the client commits an update earlier than + that, it is likely that some updates will not make it to the display, + and the client is wasting resources by drawing too often. + + The frame request will take effect on the next wl_surface.commit. + The notification will only be posted for one frame unless + requested again. For a wl_surface, the notifications are posted in + the order the frame requests were committed. + + The server must send the notifications so that a client + will not send excessive updates, while still allowing + the highest possible update rate for clients that wait for the reply + before drawing again. The server should give some time for the client + to draw and commit after sending the frame callback events to let it + hit the next output refresh. + + A server should avoid signaling the frame callbacks if the + surface is not visible in any way, e.g. the surface is off-screen, + or completely obscured by other opaque surfaces. + + The object returned by this request will be destroyed by the + compositor after the callback is fired and as such the client must not + attempt to use it after that point. + + The callback_data passed in the callback is the current time, in + milliseconds, with an undefined base. + + + + + + + This request sets the region of the surface that contains + opaque content. + + The opaque region is an optimization hint for the compositor + that lets it optimize the redrawing of content behind opaque + regions. Setting an opaque region is not required for correct + behaviour, but marking transparent content as opaque will result + in repaint artifacts. + + The opaque region is specified in surface-local coordinates. + + The compositor ignores the parts of the opaque region that fall + outside of the surface. + + Opaque region is double-buffered state, see wl_surface.commit. + + wl_surface.set_opaque_region changes the pending opaque region. + wl_surface.commit copies the pending region to the current region. + Otherwise, the pending and current regions are never changed. + + The initial value for an opaque region is empty. Setting the pending + opaque region has copy semantics, and the wl_region object can be + destroyed immediately. A NULL wl_region causes the pending opaque + region to be set to empty. + + + + + + + This request sets the region of the surface that can receive + pointer and touch events. + + Input events happening outside of this region will try the next + surface in the server surface stack. The compositor ignores the + parts of the input region that fall outside of the surface. + + The input region is specified in surface-local coordinates. + + Input region is double-buffered state, see wl_surface.commit. + + wl_surface.set_input_region changes the pending input region. + wl_surface.commit copies the pending region to the current region. + Otherwise the pending and current regions are never changed, + except cursor and icon surfaces are special cases, see + wl_pointer.set_cursor and wl_data_device.start_drag. + + The initial value for an input region is infinite. That means the + whole surface will accept input. Setting the pending input region + has copy semantics, and the wl_region object can be destroyed + immediately. A NULL wl_region causes the input region to be set + to infinite. + + + + + + + Surface state (input, opaque, and damage regions, attached buffers, + etc.) is double-buffered. Protocol requests modify the pending state, + as opposed to the active state in use by the compositor. + + A commit request atomically creates a content update from the pending + state, even if the pending state has not been touched. The content + update is placed in a queue until it becomes active. After commit, the + new pending state is as documented for each related request. + + When the content update is applied, the wl_buffer is applied before all + other state. This means that all coordinates in double-buffered state + are relative to the newly attached wl_buffers, except for + wl_surface.attach itself. If there is no newly attached wl_buffer, the + coordinates are relative to the previous content update. + + All requests that need a commit to become effective are documented + to affect double-buffered state. + + Other interfaces may add further double-buffered surface state. + + + + + + This is emitted whenever a surface's creation, movement, or resizing + results in some part of it being within the scanout region of an + output. + + Note that a surface may be overlapping with zero or more outputs. + + + + + + + This is emitted whenever a surface's creation, movement, or resizing + results in it no longer having any part of it within the scanout region + of an output. + + Clients should not use the number of outputs the surface is on for frame + throttling purposes. The surface might be hidden even if no leave event + has been sent, and the compositor might expect new surface content + updates even if no enter event has been sent. The frame event should be + used instead. + + + + + + + + + This request sets the transformation that the client has already applied + to the content of the buffer. The accepted values for the transform + parameter are the values for wl_output.transform. + + The compositor applies the inverse of this transformation whenever it + uses the buffer contents. + + Buffer transform is double-buffered state, see wl_surface.commit. + + A newly created surface has its buffer transformation set to normal. + + wl_surface.set_buffer_transform changes the pending buffer + transformation. wl_surface.commit copies the pending buffer + transformation to the current one. Otherwise, the pending and current + values are never changed. + + The purpose of this request is to allow clients to render content + according to the output transform, thus permitting the compositor to + use certain optimizations even if the display is rotated. Using + hardware overlays and scanning out a client buffer for fullscreen + surfaces are examples of such optimizations. Those optimizations are + highly dependent on the compositor implementation, so the use of this + request should be considered on a case-by-case basis. + + Note that if the transform value includes 90 or 270 degree rotation, + the width of the buffer will become the surface height and the height + of the buffer will become the surface width. + + If transform is not one of the values from the + wl_output.transform enum the invalid_transform protocol error + is raised. + + + + + + + + + This request sets an optional scaling factor on how the compositor + interprets the contents of the buffer attached to the window. + + Buffer scale is double-buffered state, see wl_surface.commit. + + A newly created surface has its buffer scale set to 1. + + wl_surface.set_buffer_scale changes the pending buffer scale. + wl_surface.commit copies the pending buffer scale to the current one. + Otherwise, the pending and current values are never changed. + + The purpose of this request is to allow clients to supply higher + resolution buffer data for use on high resolution outputs. It is + intended that you pick the same buffer scale as the scale of the + output that the surface is displayed on. This means the compositor + can avoid scaling when rendering the surface on that output. + + Note that if the scale is larger than 1, then you have to attach + a buffer that is larger (by a factor of scale in each dimension) + than the desired surface size. + + If scale is not greater than 0 the invalid_scale protocol error is + raised. + + + + + + + + This request is used to describe the regions where the pending + buffer is different from the current surface contents, and where + the surface therefore needs to be repainted. The compositor + ignores the parts of the damage that fall outside of the surface. + + Damage is double-buffered state, see wl_surface.commit. + + The damage rectangle is specified in buffer coordinates, + where x and y specify the upper left corner of the damage rectangle. + + The initial value for pending damage is empty: no damage. + wl_surface.damage_buffer adds pending damage: the new pending + damage is the union of old pending damage and the given rectangle. + + wl_surface.commit assigns pending damage as the current damage, + and clears pending damage. The server will clear the current + damage as it repaints the surface. + + This request differs from wl_surface.damage in only one way - it + takes damage in buffer coordinates instead of surface-local + coordinates. While this generally is more intuitive than surface + coordinates, it is especially desirable when using wp_viewport + or when a drawing library (like EGL) is unaware of buffer scale + and buffer transform. + + Note: Because buffer transformation changes and damage requests may + be interleaved in the protocol stream, it is impossible to determine + the actual mapping between surface and buffer damage until + wl_surface.commit time. Therefore, compositors wishing to take both + kinds of damage into account will have to accumulate damage from the + two requests separately and only transform from one to the other + after receiving the wl_surface.commit. + + + + + + + + + + + + The x and y arguments specify the location of the new pending + buffer's upper left corner, relative to the current buffer's upper + left corner, in surface-local coordinates. In other words, the + x and y, combined with the new surface size define in which + directions the surface's size changes. + + Surface location offset is double-buffered state, see + wl_surface.commit. + + This request is semantically equivalent to and the replaces the x and y + arguments in the wl_surface.attach request in wl_surface versions prior + to 5. See wl_surface.attach for details. + + + + + + + + + + This event indicates the preferred buffer scale for this surface. It is + sent whenever the compositor's preference changes. + + Before receiving this event the preferred buffer scale for this surface + is 1. + + It is intended that scaling aware clients use this event to scale their + content and use wl_surface.set_buffer_scale to indicate the scale they + have rendered with. This allows clients to supply a higher detail + buffer. + + The compositor shall emit a scale value greater than 0. + + + + + + + This event indicates the preferred buffer transform for this surface. + It is sent whenever the compositor's preference changes. + + Before receiving this event the preferred buffer transform for this + surface is normal. + + Applying this transformation to the surface buffer contents and using + wl_surface.set_buffer_transform might allow the compositor to use the + surface buffer more efficiently. + + + + + + + + A seat is a group of keyboards, pointer and touch devices. This + object is published as a global during start up, or when such a + device is hot plugged. A seat typically has a pointer and + maintains a keyboard focus and a pointer focus. + + + + + This is a bitmask of capabilities this seat has; if a member is + set, then it is present on the seat. + + + + + + + + + These errors can be emitted in response to wl_seat requests. + + + + + + + This is emitted whenever a seat gains or loses the pointer, + keyboard or touch capabilities. The argument is a capability + enum containing the complete set of capabilities this seat has. + + When the pointer capability is added, a client may create a + wl_pointer object using the wl_seat.get_pointer request. This object + will receive pointer events until the capability is removed in the + future. + + When the pointer capability is removed, a client should destroy the + wl_pointer objects associated with the seat where the capability was + removed, using the wl_pointer.release request. No further pointer + events will be received on these objects. + + In some compositors, if a seat regains the pointer capability and a + client has a previously obtained wl_pointer object of version 4 or + less, that object may start sending pointer events again. This + behavior is considered a misinterpretation of the intended behavior + and must not be relied upon by the client. wl_pointer objects of + version 5 or later must not send events if created before the most + recent event notifying the client of an added pointer capability. + + The above behavior also applies to wl_keyboard and wl_touch with the + keyboard and touch capabilities, respectively. + + + + + + + The ID provided will be initialized to the wl_pointer interface + for this seat. + + This request only takes effect if the seat has the pointer + capability, or has had the pointer capability in the past. + It is a protocol violation to issue this request on a seat that has + never had the pointer capability. The missing_capability error will + be sent in this case. + + + + + + + The ID provided will be initialized to the wl_keyboard interface + for this seat. + + This request only takes effect if the seat has the keyboard + capability, or has had the keyboard capability in the past. + It is a protocol violation to issue this request on a seat that has + never had the keyboard capability. The missing_capability error will + be sent in this case. + + + + + + + The ID provided will be initialized to the wl_touch interface + for this seat. + + This request only takes effect if the seat has the touch + capability, or has had the touch capability in the past. + It is a protocol violation to issue this request on a seat that has + never had the touch capability. The missing_capability error will + be sent in this case. + + + + + + + + + In a multi-seat configuration the seat name can be used by clients to + help identify which physical devices the seat represents. + + The seat name is a UTF-8 string with no convention defined for its + contents. Each name is unique among all wl_seat globals. The name is + only guaranteed to be unique for the current compositor instance. + + The same seat names are used for all clients. Thus, the name can be + shared across processes to refer to a specific wl_seat global. + + The name event is sent after binding to the seat global. This event is + only sent once per seat object, and the name does not change over the + lifetime of the wl_seat global. + + Compositors may re-use the same seat name if the wl_seat global is + destroyed and re-created later. + + + + + + + + + Using this request a client can tell the server that it is not going to + use the seat object anymore. + + + + + + + + The wl_pointer interface represents one or more input devices, + such as mice, which control the pointer location and pointer_focus + of a seat. + + The wl_pointer interface generates motion, enter and leave + events for the surfaces that the pointer is located over, + and button and axis events for button presses, button releases + and scrolling. + + + + + + + + + Set the pointer surface, i.e., the surface that contains the + pointer image (cursor). This request gives the surface the role + of a cursor. If the surface already has another role, it raises + a protocol error. + + The cursor actually changes only if the pointer + focus for this device is one of the requesting client's surfaces + or the surface parameter is the current pointer surface. If + there was a previous surface set with this request it is + replaced. If surface is NULL, the pointer image is hidden. + + The parameters hotspot_x and hotspot_y define the position of + the pointer surface relative to the pointer location. Its + top-left corner is always at (x, y) - (hotspot_x, hotspot_y), + where (x, y) are the coordinates of the pointer location, in + surface-local coordinates. + + On wl_surface.offset requests to the pointer surface, hotspot_x + and hotspot_y are decremented by the x and y parameters + passed to the request. The offset must be applied by + wl_surface.commit as usual. + + The hotspot can also be updated by passing the currently set + pointer surface to this request with new values for hotspot_x + and hotspot_y. + + The input region is ignored for wl_surfaces with the role of + a cursor. When the use as a cursor ends, the wl_surface is + unmapped. + + The serial parameter must match the latest wl_pointer.enter + serial number sent to the client. Otherwise the request will be + ignored. + + + + + + + + + + Notification that this seat's pointer is focused on a certain + surface. + + When a seat's focus enters a surface, the pointer image + is undefined and a client should respond to this event by setting + an appropriate pointer image with the set_cursor request. + + + + + + + + + + Notification that this seat's pointer is no longer focused on + a certain surface. + + The leave notification is sent before the enter notification + for the new focus. + + + + + + + + Notification of pointer location change. The arguments + surface_x and surface_y are the location relative to the + focused surface. + + + + + + + + + Describes the physical state of a button that produced the button + event. + + + + + + + + Mouse button click and release notifications. + + The location of the click is given by the last motion or + enter event. + The time argument is a timestamp with millisecond + granularity, with an undefined base. + + The button is a button code as defined in the Linux kernel's + linux/input-event-codes.h header file, e.g. BTN_LEFT. + + Any 16-bit button code value is reserved for future additions to the + kernel's event code list. All other button codes above 0xFFFF are + currently undefined but may be used in future versions of this + protocol. + + + + + + + + + + Describes the axis types of scroll events. + + + + + + + + Scroll and other axis notifications. + + For scroll events (vertical and horizontal scroll axes), the + value parameter is the length of a vector along the specified + axis in a coordinate space identical to those of motion events, + representing a relative movement along the specified axis. + + For devices that support movements non-parallel to axes multiple + axis events will be emitted. + + When applicable, for example for touch pads, the server can + choose to emit scroll events where the motion vector is + equivalent to a motion event vector. + + When applicable, a client can transform its content relative to the + scroll distance. + + + + + + + + + + + Using this request a client can tell the server that it is not going to + use the pointer object anymore. + + This request destroys the pointer proxy object, so clients must not call + wl_pointer_destroy() after using this request. + + + + + + + + Indicates the end of a set of events that logically belong together. + A client is expected to accumulate the data in all events within the + frame before proceeding. + + All wl_pointer events before a wl_pointer.frame event belong + logically together. For example, in a diagonal scroll motion the + compositor will send an optional wl_pointer.axis_source event, two + wl_pointer.axis events (horizontal and vertical) and finally a + wl_pointer.frame event. The client may use this information to + calculate a diagonal vector for scrolling. + + When multiple wl_pointer.axis events occur within the same frame, + the motion vector is the combined motion of all events. + When a wl_pointer.axis and a wl_pointer.axis_stop event occur within + the same frame, this indicates that axis movement in one axis has + stopped but continues in the other axis. + When multiple wl_pointer.axis_stop events occur within the same + frame, this indicates that these axes stopped in the same instance. + + A wl_pointer.frame event is sent for every logical event group, + even if the group only contains a single wl_pointer event. + Specifically, a client may get a sequence: motion, frame, button, + frame, axis, frame, axis_stop, frame. + + The wl_pointer.enter and wl_pointer.leave events are logical events + generated by the compositor and not the hardware. These events are + also grouped by a wl_pointer.frame. When a pointer moves from one + surface to another, a compositor should group the + wl_pointer.leave event within the same wl_pointer.frame. + However, a client must not rely on wl_pointer.leave and + wl_pointer.enter being in the same wl_pointer.frame. + Compositor-specific policies may require the wl_pointer.leave and + wl_pointer.enter event being split across multiple wl_pointer.frame + groups. + + + + + + Describes the source types for axis events. This indicates to the + client how an axis event was physically generated; a client may + adjust the user interface accordingly. For example, scroll events + from a "finger" source may be in a smooth coordinate space with + kinetic scrolling whereas a "wheel" source may be in discrete steps + of a number of lines. + + The "continuous" axis source is a device generating events in a + continuous coordinate space, but using something other than a + finger. One example for this source is button-based scrolling where + the vertical motion of a device is converted to scroll events while + a button is held down. + + The "wheel tilt" axis source indicates that the actual device is a + wheel but the scroll event is not caused by a rotation but a + (usually sideways) tilt of the wheel. + + + + + + + + + + Source information for scroll and other axes. + + This event does not occur on its own. It is sent before a + wl_pointer.frame event and carries the source information for + all events within that frame. + + The source specifies how this event was generated. If the source is + wl_pointer.axis_source.finger, a wl_pointer.axis_stop event will be + sent when the user lifts the finger off the device. + + If the source is wl_pointer.axis_source.wheel, + wl_pointer.axis_source.wheel_tilt or + wl_pointer.axis_source.continuous, a wl_pointer.axis_stop event may + or may not be sent. Whether a compositor sends an axis_stop event + for these sources is hardware-specific and implementation-dependent; + clients must not rely on receiving an axis_stop event for these + scroll sources and should treat scroll sequences from these scroll + sources as unterminated by default. + + This event is optional. If the source is unknown for a particular + axis event sequence, no event is sent. + Only one wl_pointer.axis_source event is permitted per frame. + + The order of wl_pointer.axis_discrete and wl_pointer.axis_source is + not guaranteed. + + + + + + + Stop notification for scroll and other axes. + + For some wl_pointer.axis_source types, a wl_pointer.axis_stop event + is sent to notify a client that the axis sequence has terminated. + This enables the client to implement kinetic scrolling. + See the wl_pointer.axis_source documentation for information on when + this event may be generated. + + Any wl_pointer.axis events with the same axis_source after this + event should be considered as the start of a new axis motion. + + The timestamp is to be interpreted identical to the timestamp in the + wl_pointer.axis event. The timestamp value may be the same as a + preceding wl_pointer.axis event. + + + + + + + + Discrete step information for scroll and other axes. + + This event carries the axis value of the wl_pointer.axis event in + discrete steps (e.g. mouse wheel clicks). + + This event is deprecated with wl_pointer version 8 - this event is not + sent to clients supporting version 8 or later. + + This event does not occur on its own, it is coupled with a + wl_pointer.axis event that represents this axis value on a + continuous scale. The protocol guarantees that each axis_discrete + event is always followed by exactly one axis event with the same + axis number within the same wl_pointer.frame. Note that the protocol + allows for other events to occur between the axis_discrete and + its coupled axis event, including other axis_discrete or axis + events. A wl_pointer.frame must not contain more than one axis_discrete + event per axis type. + + This event is optional; continuous scrolling devices + like two-finger scrolling on touchpads do not have discrete + steps and do not generate this event. + + The discrete value carries the directional information. e.g. a value + of -2 is two steps towards the negative direction of this axis. + + The axis number is identical to the axis number in the associated + axis event. + + The order of wl_pointer.axis_discrete and wl_pointer.axis_source is + not guaranteed. + + + + + + + + Discrete high-resolution scroll information. + + This event carries high-resolution wheel scroll information, + with each multiple of 120 representing one logical scroll step + (a wheel detent). For example, an axis_value120 of 30 is one quarter of + a logical scroll step in the positive direction, a value120 of + -240 are two logical scroll steps in the negative direction within the + same hardware event. + Clients that rely on discrete scrolling should accumulate the + value120 to multiples of 120 before processing the event. + + The value120 must not be zero. + + This event replaces the wl_pointer.axis_discrete event in clients + supporting wl_pointer version 8 or later. + + Where a wl_pointer.axis_source event occurs in the same + wl_pointer.frame, the axis source applies to this event. + + The order of wl_pointer.axis_value120 and wl_pointer.axis_source is + not guaranteed. + + + + + + + + + + This specifies the direction of the physical motion that caused a + wl_pointer.axis event, relative to the wl_pointer.axis direction. + + + + + + + + Relative directional information of the entity causing the axis + motion. + + For a wl_pointer.axis event, the wl_pointer.axis_relative_direction + event specifies the movement direction of the entity causing the + wl_pointer.axis event. For example: + - if a user's fingers on a touchpad move down and this + causes a wl_pointer.axis vertical_scroll down event, the physical + direction is 'identical' + - if a user's fingers on a touchpad move down and this causes a + wl_pointer.axis vertical_scroll up scroll up event ('natural + scrolling'), the physical direction is 'inverted'. + + A client may use this information to adjust scroll motion of + components. Specifically, enabling natural scrolling causes the + content to change direction compared to traditional scrolling. + Some widgets like volume control sliders should usually match the + physical direction regardless of whether natural scrolling is + active. This event enables clients to match the scroll direction of + a widget to the physical direction. + + This event does not occur on its own, it is coupled with a + wl_pointer.axis event that represents this axis value. + The protocol guarantees that each axis_relative_direction event is + always followed by exactly one axis event with the same + axis number within the same wl_pointer.frame. Note that the protocol + allows for other events to occur between the axis_relative_direction + and its coupled axis event. + + The axis number is identical to the axis number in the associated + axis event. + + The order of wl_pointer.axis_relative_direction, + wl_pointer.axis_discrete and wl_pointer.axis_source is not + guaranteed. + + + + + + + + + The wl_keyboard interface represents one or more keyboards + associated with a seat. + + Each wl_keyboard has the following logical state: + + - an active surface (possibly null), + - the keys currently logically down, + - the active modifiers, + - the active group. + + By default, the active surface is null, the keys currently logically down + are empty, the active modifiers and the active group are 0. + + + + + This specifies the format of the keymap provided to the + client with the wl_keyboard.keymap event. + + + + + + + + This event provides a file descriptor to the client which can be + memory-mapped in read-only mode to provide a keyboard mapping + description. + + From version 7 onwards, the fd must be mapped with MAP_PRIVATE by + the recipient, as MAP_SHARED may fail. + + + + + + + + + Notification that this seat's keyboard focus is on a certain + surface. + + The compositor must send the wl_keyboard.modifiers event after this + event. + + In the wl_keyboard logical state, this event sets the active surface to + the surface argument and the keys currently logically down to the keys + in the keys argument. The compositor must not send this event if the + wl_keyboard already had an active surface immediately before this event. + + + + + + + + + Notification that this seat's keyboard focus is no longer on + a certain surface. + + The leave notification is sent before the enter notification + for the new focus. + + In the wl_keyboard logical state, this event resets all values to their + defaults. The compositor must not send this event if the active surface + of the wl_keyboard was not equal to the surface argument immediately + before this event. + + + + + + + + Describes the physical state of a key that produced the key event. + + + + + + + + A key was pressed or released. + The time argument is a timestamp with millisecond + granularity, with an undefined base. + + The key is a platform-specific key code that can be interpreted + by feeding it to the keyboard mapping (see the keymap event). + + If this event produces a change in modifiers, then the resulting + wl_keyboard.modifiers event must be sent after this event. + + In the wl_keyboard logical state, this event adds the key to the keys + currently logically down (if the state argument is pressed) or removes + the key from the keys currently logically down (if the state argument is + released). The compositor must not send this event if the wl_keyboard + did not have an active surface immediately before this event. The + compositor must not send this event if state is pressed (resp. released) + and the key was already logically down (resp. was not logically down) + immediately before this event. + + + + + + + + + + Notifies clients that the modifier and/or group state has + changed, and it should update its local state. + + The compositor may send this event without a surface of the client + having keyboard focus, for example to tie modifier information to + pointer focus instead. If a modifier event with pressed modifiers is sent + without a prior enter event, the client can assume the modifier state is + valid until it receives the next wl_keyboard.modifiers event. In order to + reset the modifier state again, the compositor can send a + wl_keyboard.modifiers event with no pressed modifiers. + + In the wl_keyboard logical state, this event updates the modifiers and + group. + + + + + + + + + + + + + + + + + + + Informs the client about the keyboard's repeat rate and delay. + + This event is sent as soon as the wl_keyboard object has been created, + and is guaranteed to be received by the client before any key press + event. + + Negative values for either rate or delay are illegal. A rate of zero + will disable any repeating (regardless of the value of delay). + + This event can be sent later on as well with a new value if necessary, + so clients should continue listening for the event past the creation + of wl_keyboard. + + + + + + + + + The wl_touch interface represents a touchscreen + associated with a seat. + + Touch interactions can consist of one or more contacts. + For each contact, a series of events is generated, starting + with a down event, followed by zero or more motion events, + and ending with an up event. Events relating to the same + contact point can be identified by the ID of the sequence. + + + + + A new touch point has appeared on the surface. This touch point is + assigned a unique ID. Future events from this touch point reference + this ID. The ID ceases to be valid after a touch up event and may be + reused in the future. + + + + + + + + + + + + The touch point has disappeared. No further events will be sent for + this touch point and the touch point's ID is released and may be + reused in a future touch down event. + + + + + + + + + A touch point has changed coordinates. + + + + + + + + + + Indicates the end of a set of events that logically belong together. + A client is expected to accumulate the data in all events within the + frame before proceeding. + + A wl_touch.frame terminates at least one event but otherwise no + guarantee is provided about the set of events within a frame. A client + must assume that any state not updated in a frame is unchanged from the + previously known state. + + + + + + Sent if the compositor decides the touch stream is a global + gesture. No further events are sent to the clients from that + particular gesture. Touch cancellation applies to all touch points + currently active on this client's surface. The client is + responsible for finalizing the touch points, future touch points on + this surface may reuse the touch point ID. + + No frame event is required after the cancel event. + + + + + + + + + + + + + + Sent when a touchpoint has changed its shape. + + This event does not occur on its own. It is sent before a + wl_touch.frame event and carries the new shape information for + any previously reported, or new touch points of that frame. + + Other events describing the touch point such as wl_touch.down, + wl_touch.motion or wl_touch.orientation may be sent within the + same wl_touch.frame. A client should treat these events as a single + logical touch point update. The order of wl_touch.shape, + wl_touch.orientation and wl_touch.motion is not guaranteed. + A wl_touch.down event is guaranteed to occur before the first + wl_touch.shape event for this touch ID but both events may occur within + the same wl_touch.frame. + + A touchpoint shape is approximated by an ellipse through the major and + minor axis length. The major axis length describes the longer diameter + of the ellipse, while the minor axis length describes the shorter + diameter. Major and minor are orthogonal and both are specified in + surface-local coordinates. The center of the ellipse is always at the + touchpoint location as reported by wl_touch.down or wl_touch.move. + + This event is only sent by the compositor if the touch device supports + shape reports. The client has to make reasonable assumptions about the + shape if it did not receive this event. + + + + + + + + + Sent when a touchpoint has changed its orientation. + + This event does not occur on its own. It is sent before a + wl_touch.frame event and carries the new shape information for + any previously reported, or new touch points of that frame. + + Other events describing the touch point such as wl_touch.down, + wl_touch.motion or wl_touch.shape may be sent within the + same wl_touch.frame. A client should treat these events as a single + logical touch point update. The order of wl_touch.shape, + wl_touch.orientation and wl_touch.motion is not guaranteed. + A wl_touch.down event is guaranteed to occur before the first + wl_touch.orientation event for this touch ID but both events may occur + within the same wl_touch.frame. + + The orientation describes the clockwise angle of a touchpoint's major + axis to the positive surface y-axis and is normalized to the -180 to + +180 degree range. The granularity of orientation depends on the touch + device, some devices only support binary rotation values between 0 and + 90 degrees. + + This event is only sent by the compositor if the touch device supports + orientation reports. + + + + + + + + + An output describes part of the compositor geometry. The + compositor works in the 'compositor coordinate system' and an + output corresponds to a rectangular area in that space that is + actually visible. This typically corresponds to a monitor that + displays part of the compositor space. This object is published + as global during start up, or when a monitor is hotplugged. + + + + + This enumeration describes how the physical + pixels on an output are laid out. + + + + + + + + + + + + This describes transformations that clients and compositors apply to + buffer contents. + + The flipped values correspond to an initial flip around a + vertical axis followed by rotation. + + The purpose is mainly to allow clients to render accordingly and + tell the compositor, so that for fullscreen surfaces, the + compositor will still be able to scan out directly from client + surfaces. + + + + + + + + + + + + + + The geometry event describes geometric properties of the output. + The event is sent when binding to the output object and whenever + any of the properties change. + + The physical size can be set to zero if it doesn't make sense for this + output (e.g. for projectors or virtual outputs). + + The geometry event will be followed by a done event (starting from + version 2). + + Clients should use wl_surface.preferred_buffer_transform instead of the + transform advertised by this event to find the preferred buffer + transform to use for a surface. + + Note: wl_output only advertises partial information about the output + position and identification. Some compositors, for instance those not + implementing a desktop-style output layout or those exposing virtual + outputs, might fake this information. Instead of using x and y, clients + should use xdg_output.logical_position. Instead of using make and model, + clients should use name and description. + + + + + + + + + + + + + + These flags describe properties of an output mode. + They are used in the flags bitfield of the mode event. + + + + + + + + The mode event describes an available mode for the output. + + The event is sent when binding to the output object and there + will always be one mode, the current mode. The event is sent + again if an output changes mode, for the mode that is now + current. In other words, the current mode is always the last + mode that was received with the current flag set. + + Non-current modes are deprecated. A compositor can decide to only + advertise the current mode and never send other modes. Clients + should not rely on non-current modes. + + The size of a mode is given in physical hardware units of + the output device. This is not necessarily the same as + the output size in the global compositor space. For instance, + the output may be scaled, as described in wl_output.scale, + or transformed, as described in wl_output.transform. Clients + willing to retrieve the output size in the global compositor + space should use xdg_output.logical_size instead. + + The vertical refresh rate can be set to zero if it doesn't make + sense for this output (e.g. for virtual outputs). + + The mode event will be followed by a done event (starting from + version 2). + + Clients should not use the refresh rate to schedule frames. Instead, + they should use the wl_surface.frame event or the presentation-time + protocol. + + Note: this information is not always meaningful for all outputs. Some + compositors, such as those exposing virtual outputs, might fake the + refresh rate or the size. + + + + + + + + + + + + This event is sent after all other properties have been + sent after binding to the output object and after any + other property changes done after that. This allows + changes to the output properties to be seen as + atomic, even if they happen via multiple events. + + + + + + This event contains scaling geometry information + that is not in the geometry event. It may be sent after + binding the output object or if the output scale changes + later. The compositor will emit a non-zero, positive + value for scale. If it is not sent, the client should + assume a scale of 1. + + A scale larger than 1 means that the compositor will + automatically scale surface buffers by this amount + when rendering. This is used for very high resolution + displays where applications rendering at the native + resolution would be too small to be legible. + + Clients should use wl_surface.preferred_buffer_scale + instead of this event to find the preferred buffer + scale to use for a surface. + + The scale event will be followed by a done event. + + + + + + + + + Using this request a client can tell the server that it is not going to + use the output object anymore. + + + + + + + + Many compositors will assign user-friendly names to their outputs, show + them to the user, allow the user to refer to an output, etc. The client + may wish to know this name as well to offer the user similar behaviors. + + The name is a UTF-8 string with no convention defined for its contents. + Each name is unique among all wl_output globals. The name is only + guaranteed to be unique for the compositor instance. + + The same output name is used for all clients for a given wl_output + global. Thus, the name can be shared across processes to refer to a + specific wl_output global. + + The name is not guaranteed to be persistent across sessions, thus cannot + be used to reliably identify an output in e.g. configuration files. + + Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do + not assume that the name is a reflection of an underlying DRM connector, + X11 connection, etc. + + The name event is sent after binding the output object. This event is + only sent once per output object, and the name does not change over the + lifetime of the wl_output global. + + Compositors may re-use the same output name if the wl_output global is + destroyed and re-created later. Compositors should avoid re-using the + same name if possible. + + The name event will be followed by a done event. + + + + + + + Many compositors can produce human-readable descriptions of their + outputs. The client may wish to know this description as well, e.g. for + output selection purposes. + + The description is a UTF-8 string with no convention defined for its + contents. The description is not guaranteed to be unique among all + wl_output globals. Examples might include 'Foocorp 11" Display' or + 'Virtual X11 output via :1'. + + The description event is sent after binding the output object and + whenever the description changes. The description is optional, and may + not be sent at all. + + The description event will be followed by a done event. + + + + + + + + A region object describes an area. + + Region objects are used to describe the opaque and input + regions of a surface. + + + + + Destroy the region. This will invalidate the object ID. + + + + + + Add the specified rectangle to the region. + + + + + + + + + + Subtract the specified rectangle from the region. + + + + + + + + + + + The global interface exposing sub-surface compositing capabilities. + A wl_surface, that has sub-surfaces associated, is called the + parent surface. Sub-surfaces can be arbitrarily nested and create + a tree of sub-surfaces. + + The root surface in a tree of sub-surfaces is the main + surface. The main surface cannot be a sub-surface, because + sub-surfaces must always have a parent. + + A main surface with its sub-surfaces forms a (compound) window. + For window management purposes, this set of wl_surface objects is + to be considered as a single window, and it should also behave as + such. + + The aim of sub-surfaces is to offload some of the compositing work + within a window from clients to the compositor. A prime example is + a video player with decorations and video in separate wl_surface + objects. This should allow the compositor to pass YUV video buffer + processing to dedicated overlay hardware when possible. + + + + + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other + objects, wl_subsurface objects included. + + + + + + + + + + + Create a sub-surface interface for the given surface, and + associate it with the given parent surface. This turns a + plain wl_surface into a sub-surface. + + The to-be sub-surface must not already have another role, and it + must not have an existing wl_subsurface object. Otherwise the + bad_surface protocol error is raised. + + Adding sub-surfaces to a parent is a double-buffered operation on the + parent (see wl_surface.commit). The effect of adding a sub-surface + becomes visible on the next time the state of the parent surface is + applied. + + The parent surface must not be one of the child surface's descendants, + and the parent must be different from the child surface, otherwise the + bad_parent protocol error is raised. + + This request modifies the behaviour of wl_surface.commit request on + the sub-surface, see the documentation on wl_subsurface interface. + + + + + + + + + + An additional interface to a wl_surface object, which has been + made a sub-surface. A sub-surface has one parent surface. A + sub-surface's size and position are not limited to that of the parent. + Particularly, a sub-surface is not automatically clipped to its + parent's area. + + A sub-surface becomes mapped, when a non-NULL wl_buffer is applied + and the parent surface is mapped. The order of which one happens + first is irrelevant. A sub-surface is hidden if the parent becomes + hidden, or if a NULL wl_buffer is applied. These rules apply + recursively through the tree of surfaces. + + The behaviour of a wl_surface.commit request on a sub-surface + depends on the sub-surface's mode. The possible modes are + synchronized and desynchronized, see methods + wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized + mode caches the wl_surface state to be applied when the parent's + state gets applied, and desynchronized mode applies the pending + wl_surface state directly. A sub-surface is initially in the + synchronized mode. + + Sub-surfaces also have another kind of state, which is managed by + wl_subsurface requests, as opposed to wl_surface requests. This + state includes the sub-surface position relative to the parent + surface (wl_subsurface.set_position), and the stacking order of + the parent and its sub-surfaces (wl_subsurface.place_above and + .place_below). This state is applied when the parent surface's + wl_surface state is applied, regardless of the sub-surface's mode. + As the exception, set_sync and set_desync are effective immediately. + + The main surface can be thought to be always in desynchronized mode, + since it does not have a parent in the sub-surfaces sense. + + Even if a sub-surface is in desynchronized mode, it will behave as + in synchronized mode, if its parent surface behaves as in + synchronized mode. This rule is applied recursively throughout the + tree of surfaces. This means, that one can set a sub-surface into + synchronized mode, and then assume that all its child and grand-child + sub-surfaces are synchronized, too, without explicitly setting them. + + Destroying a sub-surface takes effect immediately. If you need to + synchronize the removal of a sub-surface to the parent surface update, + unmap the sub-surface first by attaching a NULL wl_buffer, update parent, + and then destroy the sub-surface. + + If the parent wl_surface object is destroyed, the sub-surface is + unmapped. + + A sub-surface never has the keyboard focus of any seat. + + The wl_surface.offset request is ignored: clients must use set_position + instead to move the sub-surface. + + + + + The sub-surface interface is removed from the wl_surface object + that was turned into a sub-surface with a + wl_subcompositor.get_subsurface request. The wl_surface's association + to the parent is deleted. The wl_surface is unmapped immediately. + + + + + + + + + + This schedules a sub-surface position change. + The sub-surface will be moved so that its origin (top left + corner pixel) will be at the location x, y of the parent surface + coordinate system. The coordinates are not restricted to the parent + surface area. Negative values are allowed. + + The scheduled coordinates will take effect whenever the state of the + parent surface is applied. + + If more than one set_position request is invoked by the client before + the commit of the parent surface, the position of a new request always + replaces the scheduled position from any previous request. + + The initial position is 0, 0. + + + + + + + + This sub-surface is taken from the stack, and put back just + above the reference surface, changing the z-order of the sub-surfaces. + The reference surface must be one of the sibling surfaces, or the + parent surface. Using any other surface, including this sub-surface, + will cause a protocol error. + + The z-order is double-buffered. Requests are handled in order and + applied immediately to a pending state. The final pending state is + copied to the active state the next time the state of the parent + surface is applied. + + A new sub-surface is initially added as the top-most in the stack + of its siblings and parent. + + + + + + + The sub-surface is placed just below the reference surface. + See wl_subsurface.place_above. + + + + + + + Change the commit behaviour of the sub-surface to synchronized + mode, also described as the parent dependent mode. + + In synchronized mode, wl_surface.commit on a sub-surface will + accumulate the committed state in a cache, but the state will + not be applied and hence will not change the compositor output. + The cached state is applied to the sub-surface immediately after + the parent surface's state is applied. This ensures atomic + updates of the parent and all its synchronized sub-surfaces. + Applying the cached state will invalidate the cache, so further + parent surface commits do not (re-)apply old state. + + See wl_subsurface for the recursive effect of this mode. + + + + + + Change the commit behaviour of the sub-surface to desynchronized + mode, also described as independent or freely running mode. + + In desynchronized mode, wl_surface.commit on a sub-surface will + apply the pending state directly, without caching, as happens + normally with a wl_surface. Calling wl_surface.commit on the + parent surface has no effect on the sub-surface's wl_surface + state. This mode allows a sub-surface to be updated on its own. + + If cached state exists when wl_surface.commit is called in + desynchronized mode, the pending state is added to the cached + state, and applied as a whole. This invalidates the cache. + + Note: even if a sub-surface is set to desynchronized, a parent + sub-surface may override it to behave as synchronized. For details, + see wl_subsurface. + + If a surface's parent surface behaves as desynchronized, then + the cached state is applied on set_desync. + + + + + diff --git a/src/3rdparty/wayland/protocols/wl-eglstream/REUSE.toml b/src/3rdparty/wayland/protocols/wl-eglstream/REUSE.toml new file mode 100644 index 00000000000..2f1369425bf --- /dev/null +++ b/src/3rdparty/wayland/protocols/wl-eglstream/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "wl-eglstream-controller.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved." +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/wl-eglstream/qt_attribution.json b/src/3rdparty/wayland/protocols/wl-eglstream/qt_attribution.json new file mode 100644 index 00000000000..f11023f2681 --- /dev/null +++ b/src/3rdparty/wayland/protocols/wl-eglstream/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-eglstream-controller", + "Name": "Wayland EGLStream Controller Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor", + "Files": "wl-eglstream-controller.xml", + + "Description": "Allows clients to request that the compositor creates its EGLStream.", + "Homepage": "https://github.com/NVIDIA/egl-wayland", + "Version": "1.1.1", + "DownloadLocation": "https://raw.githubusercontent.com/NVIDIA/egl-wayland/1.1.1/wayland-eglstream/wayland-eglstream-controller.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved." + } +] diff --git a/src/3rdparty/wayland/protocols/wl-eglstream/wl-eglstream-controller.xml b/src/3rdparty/wayland/protocols/wl-eglstream/wl-eglstream-controller.xml new file mode 100644 index 00000000000..dea072e64b5 --- /dev/null +++ b/src/3rdparty/wayland/protocols/wl-eglstream/wl-eglstream-controller.xml @@ -0,0 +1,37 @@ + + + + Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + Creates the corresponding server side EGLStream from the given wl_buffer + and attaches a consumer to it. + + + + + + diff --git a/src/3rdparty/wayland/protocols/wlr-data-control/REUSE.toml b/src/3rdparty/wayland/protocols/wlr-data-control/REUSE.toml new file mode 100644 index 00000000000..6ddacfa1486 --- /dev/null +++ b/src/3rdparty/wayland/protocols/wlr-data-control/REUSE.toml @@ -0,0 +1,8 @@ +version = 1 + +[[annotations]] +path = "wlr-data-control-unstable-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = ["Copyright (c) 2018 Simon Ser", + "Copyright (c) 2019 Ivan Molodetskikht"] +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/wlr-data-control/qt_attribution.json b/src/3rdparty/wayland/protocols/wlr-data-control/qt_attribution.json new file mode 100644 index 00000000000..eea2dde6ef6 --- /dev/null +++ b/src/3rdparty/wayland/protocols/wlr-data-control/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wlr-data-control-unstable-v1-protocol", + "Name": "Wlr Data Control Unstable V1 Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "wlr-data-control-unstable-v1.xml", + + "Description": "This protocol allows a privileged client to control data devices.", + "Homepage": "https://gitlab.freedesktop.org/wlroots/wlr-protocols/", + "Version": "2", + "DownloadLocation": "https://gitlab.freedesktop.org/wlroots/wlr-protocols/-/raw/master/unstable/wlr-data-control-unstable-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2018 Simon Ser\nCopyright © 2019 Ivan Molodetskikht" + } +] \ No newline at end of file diff --git a/src/3rdparty/wayland/protocols/wlr-data-control/wlr-data-control-unstable-v1.xml b/src/3rdparty/wayland/protocols/wlr-data-control/wlr-data-control-unstable-v1.xml new file mode 100644 index 00000000000..75e8671b0de --- /dev/null +++ b/src/3rdparty/wayland/protocols/wlr-data-control/wlr-data-control-unstable-v1.xml @@ -0,0 +1,278 @@ + + + + Copyright © 2018 Simon Ser + Copyright © 2019 Ivan Molodetskikh + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol allows a privileged client to control data devices. In + particular, the client will be able to manage the current selection and take + the role of a clipboard manager. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-seat data device + controls. + + + + + Create a new data source. + + + + + + + Create a data device that can be used to manage a seat's selection. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This interface allows a client to manage a seat's selection. + + When the seat is destroyed, this object becomes inert. + + + + + This request asks the compositor to set the selection to the data from + the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the selection, set the source to NULL. + + + + + + + Destroys the data device object. + + + + + + The data_offer event introduces a new wlr_data_control_offer object, + which will subsequently be used in either the + wlr_data_control_device.selection event (for the regular clipboard + selections) or the wlr_data_control_device.primary_selection event (for + the primary clipboard selections). Immediately following the + wlr_data_control_device.data_offer event, the new data_offer object + will send out wlr_data_control_offer.offer events to describe the MIME + types it offers. + + + + + + + The selection event is sent out to notify the client of a new + wlr_data_control_offer for the selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The selection event is sent to a client when a new + selection is set. The wlr_data_control_offer is valid until a new + wlr_data_control_offer or NULL is received. The client must destroy the + previous selection wlr_data_control_offer, if any, upon receiving this + event. + + The first selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This data control object is no longer valid and should be destroyed by + the client. + + + + + + + + The primary_selection event is sent out to notify the client of a new + wlr_data_control_offer for the primary selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The primary_selection event is sent to a client when a + new primary selection is set. The wlr_data_control_offer is valid until + a new wlr_data_control_offer or NULL is received. The client must + destroy the previous primary selection wlr_data_control_offer, if any, + upon receiving this event. + + If the compositor supports primary selection, the first + primary_selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This request asks the compositor to set the primary selection to the + data from the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the primary selection, set the source to NULL. + + The compositor will ignore this request if it does not support primary + selection. + + + + + + + + + + + + The wlr_data_control_source object is the source side of a + wlr_data_control_offer. It is created by the source client in a data + transfer and provides a way to describe the offered data and a way to + respond to requests to transfer the data. + + + + + + + + + This request adds a MIME type to the set of MIME types advertised to + targets. Can be called several times to offer multiple types. + + Calling this after wlr_data_control_device.set_selection is a protocol + error. + + + + + + + Destroys the data source object. + + + + + + Request for data from the client. Send the data as the specified MIME + type over the passed file descriptor, then close it. + + + + + + + + This data source is no longer valid. The data source has been replaced + by another data source. + + The client should clean up and destroy this data source. + + + + + + + A wlr_data_control_offer represents a piece of data offered for transfer + by another client (the source client). The offer describes the different + MIME types that the data can be converted to and provides the mechanism + for transferring the data directly from the source client. + + + + + To transfer the offered data, the client issues this request and + indicates the MIME type it wants to receive. The transfer happens + through the passed file descriptor (typically created with the pipe + system call). The source client writes the data in the MIME type + representation requested and then closes the file descriptor. + + The receiving client reads from the read end of the pipe until EOF and + then closes its end, at which point the transfer is complete. + + This request may happen multiple times for different MIME types. + + + + + + + + Destroys the data offer object. + + + + + + Sent immediately after creating the wlr_data_control_offer object. + One event per offered MIME type. + + + + + diff --git a/src/3rdparty/wayland/protocols/wp-primary-selection/REUSE.toml b/src/3rdparty/wayland/protocols/wp-primary-selection/REUSE.toml new file mode 100644 index 00000000000..89a49187677 --- /dev/null +++ b/src/3rdparty/wayland/protocols/wp-primary-selection/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "wp-primary-selection-unstable-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2015, 2016 Red Hat" +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/wp-primary-selection/qt_attribution.json b/src/3rdparty/wayland/protocols/wp-primary-selection/qt_attribution.json new file mode 100644 index 00000000000..ab305bb6924 --- /dev/null +++ b/src/3rdparty/wayland/protocols/wp-primary-selection/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-primary-selection-protocol", + "Name": "Wayland Primary Selection Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "wp-primary-selection-unstable-v1.xml", + + "Description": "The primary selection extension allows copying text by selecting it and pasting it with the middle mouse button.", + "Homepage": "https://wayland.freedesktop.org", + "Version": "1", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/raw/1.18/unstable/primary-selection/primary-selection-unstable-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2015, 2016 Red Hat" + } +] diff --git a/src/3rdparty/wayland/protocols/wp-primary-selection/wp-primary-selection-unstable-v1.xml b/src/3rdparty/wayland/protocols/wp-primary-selection/wp-primary-selection-unstable-v1.xml new file mode 100644 index 00000000000..e5a39e34cec --- /dev/null +++ b/src/3rdparty/wayland/protocols/wp-primary-selection/wp-primary-selection-unstable-v1.xml @@ -0,0 +1,225 @@ + + + + Copyright © 2015, 2016 Red Hat + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol provides the ability to have a primary selection device to + match that of the X server. This primary selection is a shortcut to the + common clipboard selection, where text just needs to be selected in order + to allow copying it elsewhere. The de facto way to perform this action + is the middle mouse button, although it is not limited to this one. + + Clients wishing to honor primary selection should create a primary + selection source and set it as the selection through + wp_primary_selection_device.set_selection whenever the text selection + changes. In order to minimize calls in pointer-driven text selection, + it should happen only once after the operation finished. Similarly, + a NULL source should be set when text is unselected. + + wp_primary_selection_offer objects are first announced through the + wp_primary_selection_device.data_offer event. Immediately after this event, + the primary data offer will emit wp_primary_selection_offer.offer events + to let know of the mime types being offered. + + When the primary selection changes, the client with the keyboard focus + will receive wp_primary_selection_device.selection events. Only the client + with the keyboard focus will receive such events with a non-NULL + wp_primary_selection_offer. Across keyboard focus changes, previously + focused clients will receive wp_primary_selection_device.events with a + NULL wp_primary_selection_offer. + + In order to request the primary selection data, the client must pass + a recent serial pertaining to the press event that is triggering the + operation, if the compositor deems the serial valid and recent, the + wp_primary_selection_source.send event will happen in the other end + to let the transfer begin. The client owning the primary selection + should write the requested data, and close the file descriptor + immediately. + + If the primary selection owner client disappeared during the transfer, + the client reading the data will receive a + wp_primary_selection_device.selection event with a NULL + wp_primary_selection_offer, the client should take this as a hint + to finish the reads related to the no longer existing offer. + + The primary selection owner should be checking for errors during + writes, merely cancelling the ongoing transfer if any happened. + + + + + The primary selection device manager is a singleton global object that + provides access to the primary selection. It allows to create + wp_primary_selection_source objects, as well as retrieving the per-seat + wp_primary_selection_device objects. + + + + + Create a new primary selection source. + + + + + + + Create a new data device for a given seat. + + + + + + + + Destroy the primary selection device manager. + + + + + + + + Replaces the current selection. The previous owner of the primary + selection will receive a wp_primary_selection_source.cancelled event. + + To unset the selection, set the source to NULL. + + + + + + + + Introduces a new wp_primary_selection_offer object that may be used + to receive the current primary selection. Immediately following this + event, the new wp_primary_selection_offer object will send + wp_primary_selection_offer.offer events to describe the offered mime + types. + + + + + + + The wp_primary_selection_device.selection event is sent to notify the + client of a new primary selection. This event is sent after the + wp_primary_selection.data_offer event introducing this object, and after + the offer has announced its mimetypes through + wp_primary_selection_offer.offer. + + The data_offer is valid until a new offer or NULL is received + or until the client loses keyboard focus. The client must destroy the + previous selection data_offer, if any, upon receiving this event. + + + + + + + Destroy the primary selection device. + + + + + + + A wp_primary_selection_offer represents an offer to transfer the contents + of the primary selection clipboard to the client. Similar to + wl_data_offer, the offer also describes the mime types that the data can + be converted to and provides the mechanisms for transferring the data + directly to the client. + + + + + To transfer the contents of the primary selection clipboard, the client + issues this request and indicates the mime type that it wants to + receive. The transfer happens through the passed file descriptor + (typically created with the pipe system call). The source client writes + the data in the mime type representation requested and then closes the + file descriptor. + + The receiving client reads from the read end of the pipe until EOF and + closes its end, at which point the transfer is complete. + + + + + + + + Destroy the primary selection offer. + + + + + + Sent immediately after creating announcing the + wp_primary_selection_offer through + wp_primary_selection_device.data_offer. One event is sent per offered + mime type. + + + + + + + + The source side of a wp_primary_selection_offer, it provides a way to + describe the offered data and respond to requests to transfer the + requested contents of the primary selection clipboard. + + + + + This request adds a mime type to the set of mime types advertised to + targets. Can be called several times to offer multiple types. + + + + + + + Destroy the primary selection source. + + + + + + Request for the current primary selection contents from the client. + Send the specified mime type over the passed file descriptor, then + close it. + + + + + + + + This primary selection source is no longer valid. The client should + clean up and destroy this primary selection source. + + + + diff --git a/src/3rdparty/wayland/protocols/xdg-activation/REUSE.toml b/src/3rdparty/wayland/protocols/xdg-activation/REUSE.toml new file mode 100644 index 00000000000..b076dabb7a9 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-activation/REUSE.toml @@ -0,0 +1,8 @@ +version = 1 + +[[annotations]] +path = "xdg-activation-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = ["Copyright 2020 Aleix Pol Gonzalez", + "Copyright 2020 Carlos Garnacho"] +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/xdg-activation/qt_attribution.json b/src/3rdparty/wayland/protocols/xdg-activation/qt_attribution.json new file mode 100644 index 00000000000..1f55ad27b61 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-activation/qt_attribution.json @@ -0,0 +1,19 @@ +[ + { + "Id": "wayland-xdg-activation", + "Name": "Wayland xdg-activation Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "xdg-activation-v1.xml", + + "Description": "The xdg-activation protocol provides a way for one client to pass focus to another.", + "Homepage": "https://wayland.freedesktop.org", + "Version": "unstable v1, version 1", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/staging/xdg-activation/xdg-activation-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>\nCopyright © 2020 Carlos Garnacho <carlosg@gnome.org>" + + } +] diff --git a/src/3rdparty/wayland/protocols/xdg-activation/xdg-activation-v1.xml b/src/3rdparty/wayland/protocols/xdg-activation/xdg-activation-v1.xml new file mode 100644 index 00000000000..d87e633745b --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-activation/xdg-activation-v1.xml @@ -0,0 +1,186 @@ + + + + + Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org> + Copyright © 2020 Carlos Garnacho <carlosg@gnome.org> + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + The way for a client to pass focus to another toplevel is as follows. + + The client that intends to activate another toplevel uses the + xdg_activation_v1.get_activation_token request to get an activation token. + This token is then passed to the client to be activated through a separate + band of communication. The client to be activated will then pass the token + it received to the xdg_activation_v1.activate request. The compositor can + then use this token to decide how to react to the activation request. + + The token the activating client gets may be ineffective either already at + the time it receives it, for example if it was not focused, for focus + stealing prevention. The activating client will have no way to discover + the validity of the token, and may still forward it to the to be activated + client. + + The created activation token may optionally get information attached to it + that can be used by the compositor to identify the application that we + intend to activate. This can for example be used to display a visual hint + about what application is being started. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + A global interface used for informing the compositor about applications + being activated or started, or for applications to request to be + activated. + + + + + Notify the compositor that the xdg_activation object will no longer be + used. + + The child objects created via this interface are unaffected and should + be destroyed separately. + + + + + + Creates an xdg_activation_token_v1 object that will provide + the initiating client with a unique token for this activation. This + token should be offered to the clients to be activated. + + + + + + + + Requests surface activation. It's up to the compositor to display + this information as desired, for example by placing the surface above + the rest. + + The compositor may know who requested this by checking the activation + token and might decide not to follow through with the activation if it's + considered unwanted. + + Compositors can ignore unknown presentation tokens when an invalid + token is passed. + + + + + + + + + An object for setting up a token and receiving a token handle that can + be passed as an activation token to another client. + + The object is created using the xdg_activation_v1.get_activation_token + request. This object should then be populated with the app_id, surface + and serial information and committed. The compositor shall then issue a + done event with the token. In case the request's parameters are invalid, + the compositor will provide an invalid token. + + + + + + + + + Provides information about the seat and serial event that requested the + token. + + Must be sent before commit. This information is optional. + + + + + + + + The requesting client can specify an app_id to associate the token + being created with it. + + Must be sent before commit. This information is optional. + + + + + + + The requesting client can specify a surface to associate the token + being created with it. + + Must be triggered before commit. This information is optional. + + + + + + + Requests an activation token based on the different parameters that + have been offered through set_serial, set_surface and set_app_id. + + + + + + The 'done' event contains the unique token of this activation request + and notifies that the provider is done. + + Applications will typically receive the token through the + XDG_ACTIVATION_TOKEN environment variable as set by its launcher, and + should unset the environment variable right after this request, in + order to avoid propagating it to child processes. + + Applications implementing the D-Bus interface org.freedesktop.Application + should get their token under XDG_ACTIVATION_TOKEN on their platform_data. + + Presentation tokens may be transferred across clients through means not + described in this protocol. + + + + + + + Notify the compositor that the xdg_activation_token_v1 object will no + longer be used. + + + + diff --git a/src/3rdparty/wayland/protocols/xdg-decoration/REUSE.toml b/src/3rdparty/wayland/protocols/xdg-decoration/REUSE.toml new file mode 100644 index 00000000000..ecae188cbd7 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-decoration/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "xdg-decoration-unstable-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2018 Simon Ser" +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/xdg-decoration/qt_attribution.json b/src/3rdparty/wayland/protocols/xdg-decoration/qt_attribution.json new file mode 100644 index 00000000000..7e551a5d200 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-decoration/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-xdg-decoration-protocol", + "Name": "Wayland xdg-decoration Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor API, and the Qt Wayland platform plugin.", + "Files": "xdg-decoration-unstable-v1.xml", + + "Description": "The xdg-decoration protocol allows a compositor to announce support for server-side decorations.", + "Homepage": "https://wayland.freedesktop.org", + "Version": "unstable v1, version 1", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/raw/1.18/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2018 Simon Ser" + } +] diff --git a/src/3rdparty/wayland/protocols/xdg-decoration/xdg-decoration-unstable-v1.xml b/src/3rdparty/wayland/protocols/xdg-decoration/xdg-decoration-unstable-v1.xml new file mode 100644 index 00000000000..378e8ff4bbf --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-decoration/xdg-decoration-unstable-v1.xml @@ -0,0 +1,156 @@ + + + + Copyright © 2018 Simon Ser + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This interface allows a compositor to announce support for server-side + decorations. + + A window decoration is a set of window controls as deemed appropriate by + the party managing them, such as user interface components used to move, + resize and change a window's state. + + A client can use this protocol to request being decorated by a supporting + compositor. + + If compositor and client do not negotiate the use of a server-side + decoration using this protocol, clients continue to self-decorate as they + see fit. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Destroy the decoration manager. This doesn't destroy objects created + with the manager. + + + + + + Create a new decoration object associated with the given toplevel. + + Creating an xdg_toplevel_decoration from an xdg_toplevel which has a + buffer attached or committed is a client error, and any attempts by a + client to attach or manipulate a buffer prior to the first + xdg_toplevel_decoration.configure event must also be treated as + errors. + + + + + + + + + The decoration object allows the compositor to toggle server-side window + decorations for a toplevel surface. The client can request to switch to + another mode. + + The xdg_toplevel_decoration object must be destroyed before its + xdg_toplevel. + + + + + + + + + + + Switch back to a mode without any server-side decorations at the next + commit. + + + + + + These values describe window decoration modes. + + + + + + + + Set the toplevel surface decoration mode. This informs the compositor + that the client prefers the provided decoration mode. + + After requesting a decoration mode, the compositor will respond by + emitting a xdg_surface.configure event. The client should then update + its content, drawing it without decorations if the received mode is + server-side decorations. The client must also acknowledge the configure + when committing the new content (see xdg_surface.ack_configure). + + The compositor can decide not to use the client's mode and enforce a + different mode instead. + + Clients whose decoration mode depend on the xdg_toplevel state may send + a set_mode request in response to a xdg_surface.configure event and wait + for the next xdg_surface.configure event to prevent unwanted state. + Such clients are responsible for preventing configure loops and must + make sure not to send multiple successive set_mode requests with the + same decoration mode. + + + + + + + Unset the toplevel surface decoration mode. This informs the compositor + that the client doesn't prefer a particular decoration mode. + + This request has the same semantics as set_mode. + + + + + + The configure event asks the client to change its decoration mode. The + configured state should not be applied immediately. Clients must send an + ack_configure in response to this event. See xdg_surface.configure and + xdg_surface.ack_configure for details. + + A configure event can be sent at any time. The specified mode must be + obeyed by the client. + + + + + diff --git a/src/3rdparty/wayland/protocols/xdg-dialog/REUSE.toml b/src/3rdparty/wayland/protocols/xdg-dialog/REUSE.toml new file mode 100644 index 00000000000..deb7bea6c9d --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-dialog/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "xdg-dialog-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2023 Carlos Garnacho" +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/xdg-dialog/qt_attribution.json b/src/3rdparty/wayland/protocols/xdg-dialog/qt_attribution.json new file mode 100644 index 00000000000..4a6d959e4a1 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-dialog/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "xdg-dialog-v1", + "Name": "Wayland Dialog Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "xdg-dialog-v1.xml", + + "Description": "Register toplevel as dialogs", + "Homepage": "https://wayland.freedesktop.org", + "Version": "1", + "DownloadLocation": "", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2023 Carlos Garnacho" + } +] diff --git a/src/3rdparty/wayland/protocols/xdg-dialog/xdg-dialog-v1.xml b/src/3rdparty/wayland/protocols/xdg-dialog/xdg-dialog-v1.xml new file mode 100644 index 00000000000..ed2141132ba --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-dialog/xdg-dialog-v1.xml @@ -0,0 +1,110 @@ + + + + Copyright © 2023 Carlos Garnacho + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The xdg_wm_dialog_v1 interface is exposed as a global object allowing + to register surfaces with a xdg_toplevel role as "dialogs" relative to + another toplevel. + + The compositor may let this relation influence how the surface is + placed, displayed or interacted with. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + + + + + Destroys the xdg_wm_dialog_v1 object. This does not affect + the xdg_dialog_v1 objects generated through it. + + + + + + Creates a xdg_dialog_v1 object for the given toplevel. See the interface + description for more details. + + Compositors must raise an already_used error if clients attempt to + create multiple xdg_dialog_v1 objects for the same xdg_toplevel. + + + + + + + + + A xdg_dialog_v1 object is an ancillary object tied to a xdg_toplevel. Its + purpose is hinting the compositor that the toplevel is a "dialog" (e.g. a + temporary window) relative to another toplevel (see + xdg_toplevel.set_parent). If the xdg_toplevel is destroyed, the xdg_dialog_v1 + becomes inert. + + Through this object, the client may provide additional hints about + the purpose of the secondary toplevel. This interface has no effect + on toplevels that are not attached to a parent toplevel. + + + + + Destroys the xdg_dialog_v1 object. If this object is destroyed + before the related xdg_toplevel, the compositor should unapply its + effects. + + + + + + Hints that the dialog has "modal" behavior. Modal dialogs typically + require to be fully addressed by the user (i.e. closed) before resuming + interaction with the parent toplevel, and may require a distinct + presentation. + + Clients must implement the logic to filter events in the parent + toplevel on their own. + + Compositors may choose any policy in event delivery to the parent + toplevel, from delivering all events unfiltered to using them for + internal consumption. + + + + + + Drops the hint that this dialog has "modal" behavior. See + xdg_dialog_v1.set_modal for more details. + + + + diff --git a/src/3rdparty/wayland/protocols/xdg-foreign/REUSE.toml b/src/3rdparty/wayland/protocols/xdg-foreign/REUSE.toml new file mode 100644 index 00000000000..d5b24d88137 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-foreign/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "xdg-foreign-unstable-v2.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2015-2016 Red Hat Inc." +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/xdg-foreign/qt_attribution.json b/src/3rdparty/wayland/protocols/xdg-foreign/qt_attribution.json new file mode 100644 index 00000000000..00daedd5b95 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-foreign/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "xdg-foreign-unstable-v2", + "Name": "Wayland XDG Foreign Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "xdg-foreign-unstable-v2.xml", + + "Description": "Allows referencing surfaces of different clients", + "Homepage": "https://wayland.freedesktop.org", + "Version": "1", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/raw/1.25/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2015-2016 Red Hat Inc." + } +] diff --git a/src/3rdparty/wayland/protocols/xdg-foreign/xdg-foreign-unstable-v2.xml b/src/3rdparty/wayland/protocols/xdg-foreign/xdg-foreign-unstable-v2.xml new file mode 100644 index 00000000000..cc3271dca4d --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-foreign/xdg-foreign-unstable-v2.xml @@ -0,0 +1,200 @@ + + + + + Copyright © 2015-2016 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol specifies a way for making it possible to reference a surface + of a different client. With such a reference, a client can, by using the + interfaces provided by this protocol, manipulate the relationship between + its own surfaces and the surface of some other client. For example, stack + some of its own surface above the other clients surface. + + In order for a client A to get a reference of a surface of client B, client + B must first export its surface using xdg_exporter.export_toplevel. Upon + doing this, client B will receive a handle (a unique string) that it may + share with client A in some way (for example D-Bus). After client A has + received the handle from client B, it may use xdg_importer.import_toplevel + to create a reference to the surface client B just exported. See the + corresponding requests for details. + + A possible use case for this is out-of-process dialogs. For example when a + sandboxed client without file system access needs the user to select a file + on the file system, given sandbox environment support, it can export its + surface, passing the exported surface handle to an unsandboxed process that + can show a file browser dialog and stack it above the sandboxed client's + surface. + + Warning! The protocol described in this file is experimental and backward + incompatible changes may be made. Backward compatible changes may be added + together with the corresponding interface version bump. Backward + incompatible changes are done by bumping the version number in the protocol + and interface names and resetting the interface version. Once the protocol + is to be declared stable, the 'z' prefix and the version number in the + protocol and interface names are removed and the interface version number is + reset. + + + + + A global interface used for exporting surfaces that can later be imported + using xdg_importer. + + + + + Notify the compositor that the xdg_exporter object will no longer be + used. + + + + + + These errors can be emitted in response to invalid xdg_exporter + requests. + + + + + + + The export_toplevel request exports the passed surface so that it can later be + imported via xdg_importer. When called, a new xdg_exported object will + be created and xdg_exported.handle will be sent immediately. See the + corresponding interface and event for details. + + A surface may be exported multiple times, and each exported handle may + be used to create an xdg_imported multiple times. Only xdg_toplevel + equivalent surfaces may be exported, otherwise an invalid_surface + protocol error is sent. + + + + + + + + + A global interface used for importing surfaces exported by xdg_exporter. + With this interface, a client can create a reference to a surface of + another client. + + + + + Notify the compositor that the xdg_importer object will no longer be + used. + + + + + + The import_toplevel request imports a surface from any client given a handle + retrieved by exporting said surface using xdg_exporter.export_toplevel. + When called, a new xdg_imported object will be created. This new object + represents the imported surface, and the importing client can + manipulate its relationship using it. See xdg_imported for details. + + + + + + + + + An xdg_exported object represents an exported reference to a surface. The + exported surface may be referenced as long as the xdg_exported object not + destroyed. Destroying the xdg_exported invalidates any relationship the + importer may have established using xdg_imported. + + + + + Revoke the previously exported surface. This invalidates any + relationship the importer may have set up using the xdg_imported created + given the handle sent via xdg_exported.handle. + + + + + + The handle event contains the unique handle of this exported surface + reference. It may be shared with any client, which then can use it to + import the surface by calling xdg_importer.import_toplevel. A handle + may be used to import the surface multiple times. + + + + + + + + An xdg_imported object represents an imported reference to surface exported + by some client. A client can use this interface to manipulate + relationships between its own surfaces and the imported surface. + + + + + These errors can be emitted in response to invalid xdg_imported + requests. + + + + + + + Notify the compositor that it will no longer use the xdg_imported + object. Any relationship that may have been set up will at this point + be invalidated. + + + + + + Set the imported surface as the parent of some surface of the client. + The passed surface must be an xdg_toplevel equivalent, otherwise an + invalid_surface protocol error is sent. Calling this function sets up + a surface to surface relation with the same stacking and positioning + semantics as xdg_toplevel.set_parent. + + + + + + + The imported surface handle has been destroyed and any relationship set + up has been invalidated. This may happen for various reasons, for + example if the exported surface or the exported surface handle has been + destroyed, if the handle used for importing was invalid. + + + + + diff --git a/src/3rdparty/wayland/protocols/xdg-output/REUSE.toml b/src/3rdparty/wayland/protocols/xdg-output/REUSE.toml new file mode 100644 index 00000000000..3c42c4aeb09 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-output/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "xdg-output-unstable-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2017 Red Hat Inc." +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/xdg-output/qt_attribution.json b/src/3rdparty/wayland/protocols/xdg-output/qt_attribution.json new file mode 100644 index 00000000000..04c6bf482f5 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-output/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-xdg-output-protocol", + "Name": "Wayland XDG Output Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor API, and the Qt Wayland platform plugin.", + "Files": "xdg-output-unstable-v1.xml", + + "Description": "The XDG Output protocol is an extended way to describe output regions under Wayland", + "Homepage": "https://wayland.freedesktop.org", + "Version": "unstable v1, version 3", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/raw/1.18/unstable/xdg-output/xdg-output-unstable-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2017 Red Hat Inc." + } +] diff --git a/src/3rdparty/wayland/protocols/xdg-output/xdg-output-unstable-v1.xml b/src/3rdparty/wayland/protocols/xdg-output/xdg-output-unstable-v1.xml new file mode 100644 index 00000000000..a7306e4193e --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-output/xdg-output-unstable-v1.xml @@ -0,0 +1,222 @@ + + + + + Copyright © 2017 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol aims at describing outputs in a way which is more in line + with the concept of an output on desktop oriented systems. + + Some information are more specific to the concept of an output for + a desktop oriented system and may not make sense in other applications, + such as IVI systems for example. + + Typically, the global compositor space on a desktop system is made of + a contiguous or overlapping set of rectangular regions. + + The logical_position and logical_size events defined in this protocol + might provide information identical to their counterparts already + available from wl_output, in which case the information provided by this + protocol should be preferred to their equivalent in wl_output. The goal is + to move the desktop specific concepts (such as output location within the + global compositor space, etc.) out of the core wl_output protocol. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible + changes may be added together with the corresponding interface + version bump. + Backward incompatible changes are done by bumping the version + number in the protocol and interface names and resetting the + interface version. Once the protocol is to be declared stable, + the 'z' prefix and the version number in the protocol and + interface names are removed and the interface version number is + reset. + + + + + A global factory interface for xdg_output objects. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output_manager object anymore. + + Any objects already created through this instance are not affected. + + + + + + This creates a new xdg_output object for the given wl_output. + + + + + + + + + An xdg_output describes part of the compositor geometry. + + This typically corresponds to a monitor that displays part of the + compositor space. + + For objects version 3 onwards, after all xdg_output properties have been + sent (when the object is created and when properties are updated), a + wl_output.done event is sent. This allows changes to the output + properties to be seen as atomic, even if they happen via multiple events. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output object anymore. + + + + + + The position event describes the location of the wl_output within + the global compositor space. + + The logical_position event is sent after creating an xdg_output + (see xdg_output_manager.get_xdg_output) and whenever the location + of the output changes within the global compositor space. + + + + + + + + The logical_size event describes the size of the output in the + global compositor space. + + Most regular Wayland clients should not pay attention to the + logical size and would rather rely on xdg_shell interfaces. + + Some clients such as Xwayland, however, need this to configure + their surfaces in the global compositor space as the compositor + may apply a different scale from what is advertised by the output + scaling property (to achieve fractional scaling, for example). + + For example, for a wl_output mode 3840×2160 and a scale factor 2: + + - A compositor not scaling the monitor viewport in its compositing space + will advertise a logical size of 3840×2160, + + - A compositor scaling the monitor viewport with scale factor 2 will + advertise a logical size of 1920×1080, + + - A compositor scaling the monitor viewport using a fractional scale of + 1.5 will advertise a logical size of 2560×1440. + + For example, for a wl_output mode 1920×1080 and a 90 degree rotation, + the compositor will advertise a logical size of 1080x1920. + + The logical_size event is sent after creating an xdg_output + (see xdg_output_manager.get_xdg_output) and whenever the logical + size of the output changes, either as a result of a change in the + applied scale or because of a change in the corresponding output + mode(see wl_output.mode) or transform (see wl_output.transform). + + + + + + + + This event is sent after all other properties of an xdg_output + have been sent. + + This allows changes to the xdg_output properties to be seen as + atomic, even if they happen via multiple events. + + For objects version 3 onwards, this event is deprecated. Compositors + are not required to send it anymore and must send wl_output.done + instead. + + + + + + + + Many compositors will assign names to their outputs, show them to the + user, allow them to be configured by name, etc. The client may wish to + know this name as well to offer the user similar behaviors. + + The naming convention is compositor defined, but limited to + alphanumeric characters and dashes (-). Each name is unique among all + wl_output globals, but if a wl_output global is destroyed the same name + may be reused later. The names will also remain consistent across + sessions with the same hardware and software configuration. + + Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do + not assume that the name is a reflection of an underlying DRM + connector, X11 connection, etc. + + The name event is sent after creating an xdg_output (see + xdg_output_manager.get_xdg_output). This event is only sent once per + xdg_output, and the name does not change over the lifetime of the + wl_output global. + + This event is deprecated, instead clients should use wl_output.name. + Compositors must still support this event. + + + + + + + Many compositors can produce human-readable descriptions of their + outputs. The client may wish to know this description as well, to + communicate the user for various purposes. + + The description is a UTF-8 string with no convention defined for its + contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 + output via :1'. + + The description event is sent after creating an xdg_output (see + xdg_output_manager.get_xdg_output) and whenever the description + changes. The description is optional, and may not be sent at all. + + For objects of version 2 and lower, this event is only sent once per + xdg_output, and the description does not change over the lifetime of + the wl_output global. + + This event is deprecated, instead clients should use + wl_output.description. Compositors must still support this event. + + + + + + diff --git a/src/3rdparty/wayland/protocols/xdg-shell/REUSE.toml b/src/3rdparty/wayland/protocols/xdg-shell/REUSE.toml new file mode 100644 index 00000000000..f0e5dde4ebe --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-shell/REUSE.toml @@ -0,0 +1,22 @@ +version = 1 + +[[annotations]] +path = "xdg-shell.xml" +precedence = "closest" +SPDX-FileCopyrightText = ["Copyright 2008-2013 Kristian Høgsberg", + "Copyright 2013 Rafael Antognolli", + "Copyright 2013 Jasper St. Pierre", + "Copyright 2010-2013 Intel Corporation", + "Copyright 2015-2017 Samsung Electronics Co., Ltd", + "Copyright 2015-2017 Red Hat Inc."] +SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "xdg-shell-unstable-v6.xml" +precedence = "closest" +comment = "missing from qt_atttribution.json" +SPDX-FileCopyrightText = ["Copyright © 2008-2013 Kristian Høgsberg", + "Copyright © 2013 Rafael Antognolli", + "Copyright © 2013 Jasper St. Pierre", + "Copyright © 2010-2013 Intel Corporation"] +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/xdg-shell/qt_attribution.json b/src/3rdparty/wayland/protocols/xdg-shell/qt_attribution.json new file mode 100644 index 00000000000..b47679c7148 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-shell/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-xdg-shell-protocol", + "Name": "Wayland XDG Shell Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor, and the Qt Wayland platform plugin.", + "Files": "xdg-shell.xml", + + "Description": "The XDG-Shell protocol is an extended way to manage surfaces under Wayland compositors.", + "Homepage": "https://gitlab.freedesktop.org/wayland/wayland-protocols/", + "Version": "1.18", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/tree/1.18/", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2008-2013 Kristian Høgsberg\nCopyright © 2013 Rafael Antognolli\nCopyright © 2013 Jasper St. Pierre\nCopyright © 2010-2013 Intel Corporation\nCopyright © 2015-2017 Samsung Electronics Co., Ltd\nCopyright © 2015-2017 Red Hat Inc.\n" + } +] diff --git a/src/3rdparty/wayland/protocols/xdg-shell/xdg-shell-unstable-v6.xml b/src/3rdparty/wayland/protocols/xdg-shell/xdg-shell-unstable-v6.xml new file mode 100644 index 00000000000..1c0f92452b9 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-shell/xdg-shell-unstable-v6.xml @@ -0,0 +1,1044 @@ + + + + + Copyright © 2008-2013 Kristian Høgsberg + Copyright © 2013 Rafael Antognolli + Copyright © 2013 Jasper St. Pierre + Copyright © 2010-2013 Intel Corporation + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + xdg_shell allows clients to turn a wl_surface into a "real window" + which can be dragged, resized, stacked, and moved around by the + user. Everything about this interface is suited towards traditional + desktop environments. + + + + + + + + + + + + + + Destroy this xdg_shell object. + + Destroying a bound xdg_shell object while there are surfaces + still alive created by this xdg_shell object instance is illegal + and will result in a protocol error. + + + + + + Create a positioner object. A positioner object is used to position + surfaces relative to some parent surface. See the interface description + and xdg_surface.get_popup for details. + + + + + + + This creates an xdg_surface for the given surface. While xdg_surface + itself is not a role, the corresponding surface may only be assigned + a role extending xdg_surface, such as xdg_toplevel or xdg_popup. + + This creates an xdg_surface for the given surface. An xdg_surface is + used as basis to define a role to a given surface, such as xdg_toplevel + or xdg_popup. It also manages functionality shared between xdg_surface + based surface roles. + + See the documentation of xdg_surface for more details about what an + xdg_surface is and how it is used. + + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_shell.ping. + + + + + + + The ping event asks the client if it's still alive. Pass the + serial specified in the event back to the compositor by sending + a "pong" request back with the specified serial. See xdg_shell.ping. + + Compositors can use this to determine if the client is still + alive. It's unspecified what will happen if the client doesn't + respond to the ping request, or in what timeframe. Clients should + try to respond in a reasonable amount of time. + + A compositor is free to ping in any way it wants, but a client must + always respond to any xdg_shell object it created. + + + + + + + + The xdg_positioner provides a collection of rules for the placement of a + child surface relative to a parent surface. Rules can be defined to ensure + the child surface remains within the visible area's borders, and to + specify how the child surface changes its position, such as sliding along + an axis, or flipping around a rectangle. These positioner-created rules are + constrained by the requirement that a child surface must intersect with or + be at least partially adjacent to its parent surface. + + See the various requests for details about possible rules. + + At the time of the request, the compositor makes a copy of the rules + specified by the xdg_positioner. Thus, after the request is complete the + xdg_positioner object can be destroyed or reused; further changes to the + object will have no effect on previous usages. + + For an xdg_positioner object to be considered complete, it must have a + non-zero size set by set_size, and a non-zero anchor rectangle set by + set_anchor_rect. Passing an incomplete xdg_positioner object when + positioning a surface raises an error. + + + + + + + + + Notify the compositor that the xdg_positioner will no longer be used. + + + + + + Set the size of the surface that is to be positioned with the positioner + object. The size is in surface-local coordinates and corresponds to the + window geometry. See xdg_surface.set_window_geometry. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + Specify the anchor rectangle within the parent surface that the child + surface will be placed relative to. The rectangle is relative to the + window geometry as defined by xdg_surface.set_window_geometry of the + parent surface. The rectangle must be at least 1x1 large. + + When the xdg_positioner object is used to position a child surface, the + anchor rectangle may not extend outside the window geometry of the + positioned child's parent surface. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + + + + + + + + + + + Defines a set of edges for the anchor rectangle. These are used to + derive an anchor point that the child surface will be positioned + relative to. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left position of the rectangle); otherwise, the derived + anchor point will be centered on the specified edge, or in the center of + the anchor rectangle if no edge is specified. + + If two parallel anchor edges are specified (e.g. 'left' and 'right'), + the invalid_input error is raised. + + + + + + + + + + + + + + + Defines in what direction a surface should be positioned, relative to + the anchor point of the parent surface. If two orthogonal gravities are + specified (e.g. 'bottom' and 'right'), then the child surface will be + placed in the specified direction; otherwise, the child surface will be + centered over the anchor point on any axis that had no gravity + specified. + + If two parallel gravities are specified (e.g. 'left' and 'right'), the + invalid_input error is raised. + + + + + + + The constraint adjustment value define ways the compositor will adjust + the position of the surface, if the unadjusted position would result + in the surface being partly constrained. + + Whether a surface is considered 'constrained' is left to the compositor + to determine. For example, the surface may be partly outside the + compositor's defined 'work area', thus necessitating the child surface's + position be adjusted until it is entirely inside the work area. + + The adjustments can be combined, according to a defined precedence: 1) + Flip, 2) Slide, 3) Resize. + + + + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of a monitor. + + + + + Slide the surface along the x axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the x axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + x axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Slide the surface along the y axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the y axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + y axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Invert the anchor and gravity on the x axis if the surface is + constrained on the x axis. For example, if the left edge of the + surface is constrained, the gravity is 'left' and the anchor is + 'left', change the gravity to 'right' and the anchor to 'right'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_x adjustment will be the one before the + adjustment. + + + + + Invert the anchor and gravity on the y axis if the surface is + constrained on the y axis. For example, if the bottom edge of the + surface is constrained, the gravity is 'bottom' and the anchor is + 'bottom', change the gravity to 'top' and the anchor to 'top'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_y adjustment will be the one before the + adjustment. + + + + + Resize the surface horizontally so that it is completely + unconstrained. + + + + + Resize the surface vertically so that it is completely unconstrained. + + + + + + + Specify how the window should be positioned if the originally intended + position caused the surface to be constrained, meaning at least + partially outside positioning boundaries set by the compositor. The + adjustment is set by constructing a bitmask describing the adjustment to + be made when the surface is constrained on that axis. + + If no bit for one axis is set, the compositor will assume that the child + surface should not change its position on that axis when constrained. + + If more than one bit for one axis is set, the order of how adjustments + are applied is specified in the corresponding adjustment descriptions. + + The default adjustment is none. + + + + + + + Specify the surface position offset relative to the position of the + anchor on the anchor rectangle and the anchor on the surface. For + example if the anchor of the anchor rectangle is at (x, y), the surface + has the gravity bottom|right, and the offset is (ox, oy), the calculated + surface position will be (x + ox, y + oy). The offset position of the + surface is the one used for constraint testing. See + set_constraint_adjustment. + + An example use case is placing a popup menu on top of a user interface + element, while aligning the user interface element of the parent surface + with some user interface element placed somewhere in the popup surface. + + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides a base set of functionality required to construct user + interface elements requiring management by the compositor, such as + toplevel windows, menus, etc. The types of functionality are split into + xdg_surface roles. + + Creating an xdg_surface does not set the role for a wl_surface. In order + to map an xdg_surface, the client must create a role-specific object + using, e.g., get_toplevel, get_popup. The wl_surface for any given + xdg_surface can have at most one role, and may not be assigned any role + not based on xdg_surface. + + A role must be assigned before any other requests are made to the + xdg_surface object. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. + + Creating an xdg_surface from a wl_surface which has a buffer attached or + committed is a client error, and any attempts by a client to attach or + manipulate a buffer prior to the first xdg_surface.configure call must + also be treated as errors. + + For a surface to be mapped by the compositor, the following conditions + must be met: (1) the client has assigned a xdg_surface based role to the + surface, (2) the client has set and committed the xdg_surface state and + the role dependent state to the surface and (3) the client has committed a + buffer to the surface. + + + + + + + + + + + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed. + + + + + + This creates an xdg_toplevel object for the given xdg_surface and gives + the associated wl_surface the xdg_toplevel role. + + See the documentation of xdg_toplevel for more details about what an + xdg_toplevel is and how it is used. + + + + + + + This creates an xdg_popup object for the given xdg_surface and gives the + associated wl_surface the xdg_popup role. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + + + The window geometry of a surface is its "visible bounds" from the + user's perspective. Client-side decorations often have invisible + portions like drop-shadows which should be ignored for the + purposes of aligning, placing and constraining windows. + + The window geometry is double buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Once the window geometry of the surface is set, it is not possible to + unset it, and it will remain the same until set_window_geometry is + called again, even if a new subsurface or buffer is attached. + + If never set, the value is the full bounds of the surface, + including any subsurfaces. This updates dynamically on every + commit. This unset is meant for extremely simple clients. + + The arguments are given in the surface-local coordinate space of + the wl_surface associated with this xdg_surface. + + The width and height must be greater than zero. Setting an invalid size + will raise an error. When applied, the effective window geometry will be + the set window geometry clamped to the bounding rectangle of the + combined geometry of the surface of the xdg_surface and the associated + subsurfaces. + + + + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + For instance, for toplevel surfaces the compositor might use this + information to move a surface to the top left only when the client has + drawn itself for the maximized or fullscreen state. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + The configure event marks the end of a configure sequence. A configure + sequence is a set of one or more events configuring the state of the + xdg_surface, including the final xdg_surface.configure event. + + Where applicable, xdg_surface surface roles will during a configure + sequence extend this event as a latched state sent as events before the + xdg_surface.configure event. Such events should be considered to make up + a set of atomically applied configuration states, where the + xdg_surface.configure commits the accumulated state. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + If the client receives multiple configure events before it can respond + to one, it is free to discard all but the last event it received. + + + + + + + + This interface defines an xdg_surface role which allows a surface to, + among other things, set window-like properties such as maximize, + fullscreen, and minimize, set application-specific metadata like title and + id, and well as trigger user interactive operations such as interactive + resize and move. + + + + + Unmap and destroy the window. The window will be effectively + hidden from the user's point of view, and all state like + maximization, fullscreen, and so on, will be lost. + + + + + + Set the "parent" of this surface. This window should be stacked + above a parent. The parent surface must be mapped as long as this + surface is mapped. + + Parent windows should be set on dialogs, toolboxes, or other + "auxiliary" surfaces, so that the parent is raised when the dialog + is raised. + + + + + + + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + + + + + + + Set an application identifier for the surface. + + The app ID identifies the general class of applications to which + the surface belongs. The compositor can use this to group multiple + surfaces together, or to determine how to launch a new application. + + For D-Bus activatable applications, the app ID is used as the D-Bus + service name. + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + + See the desktop-entry specification [0] for more details on + application identifiers and how they relate to well-known D-Bus + names and .desktop files. + + [0] http://standards.freedesktop.org/desktop-entry-spec/ + + + + + + + Clients implementing client-side decorations might want to show + a context menu when right-clicking on the decorations, giving the + user a menu that they can use to maximize or minimize the window. + + This request asks the compositor to pop up such a window menu at + the given position, relative to the local surface coordinates of + the parent surface. There are no guarantees as to what menu items + the window menu contains. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + + + + + + + + + + Start an interactive, user-driven move of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive move (touch, + pointer, etc). + + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized), or if the passed serial + is no longer valid. + + If triggered, the surface will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the move. It is up to the + compositor to visually indicate that the move is taking place, such as + updating a pointer cursor, during the move. There is no guarantee + that the device focus will return when the move is completed. + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + + + + + + + + + + + + + + + Start a user-driven, interactive resize of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive resize (touch, + pointer, etc). + + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + If triggered, the client will receive configure events with the + "resize" state enum value and the expected sizes. See the "resize" + enum value for more details about what is required. The client + must also acknowledge configure events using "ack_configure". After + the resize is completed, the client will receive another "configure" + event without the resize state. + + If triggered, the surface also will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the resize. It is up to the + compositor to visually indicate that the resize is taking place, + such as updating a pointer cursor, during the resize. There is no + guarantee that the device focus will return when the resize is + completed. + + The edges parameter specifies how the surface should be resized, + and is one of the values of the resize_edge enum. The compositor + may use this information to update the surface position for + example when dragging the top left corner. The compositor may also + use this information to adapt its behavior, e.g. choose an + appropriate cursor image. + + + + + + + + + The different state values used on the surface. This is designed for + state values like maximized, fullscreen. It is paired with the + configure event to ensure that both the client and the compositor + setting the state can be synchronized. + + States set in this way are double-buffered. They will get applied on + the next commit. + + + + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client. + + + + + The surface is fullscreen. The window geometry specified in the configure + event must be obeyed by the client. + + + + + The surface is being resized. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. + Clients that have aspect ratio or cell sizing configuration can use + a smaller size, however. + + + + + Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. + + + + + + + Set a maximum size for the window. + + The client can specify a maximum size so that the compositor does + not try to configure the window beyond this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the maximum + size. The compositor may decide to ignore the values set by the + client and request a larger size. + + If never set, or a value of zero in the request, means that the + client has no expected maximum size in the given dimension. + As a result, a client wishing to reset the maximum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a maximum size to be smaller than the minimum size of + a surface is illegal and will result in a protocol error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + protocol error. + + + + + + + + Set a minimum size for the window. + + The client can specify a minimum size so that the compositor does + not try to configure the window below this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the minimum + size. The compositor may decide to ignore the values set by the + client and request a smaller size. + + If never set, or a value of zero in the request, means that the + client has no expected minimum size in the given dimension. + As a result, a client wishing to reset the minimum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a minimum size to be larger than the maximum size of + a surface is illegal and will result in a protocol error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + protocol error. + + + + + + + + Maximize the surface. + + After requesting that the surface should be maximized, the compositor + will respond by emitting a configure event with the "maximized" state + and the required window geometry. The client should then update its + content, drawing it in a maximized state, i.e. without shadow or other + decoration outside of the window geometry. The client must also + acknowledge the configure when committing the new content (see + ack_configure). + + It is up to the compositor to decide how and where to maximize the + surface, for example which output and what region of the screen should + be used. + + If the surface was already maximized, the compositor will still emit + a configure event with the "maximized" state. + + + + + + Unmaximize the surface. + + After requesting that the surface should be unmaximized, the compositor + will respond by emitting a configure event without the "maximized" + state. If available, the compositor will include the window geometry + dimensions the window had prior to being maximized in the configure + request. The client must then update its content, drawing it in a + regular state, i.e. potentially with shadow, etc. The client must also + acknowledge the configure when committing the new content (see + ack_configure). + + It is up to the compositor to position the surface after it was + unmaximized; usually the position the surface had before maximizing, if + applicable. + + If the surface was already not maximized, the compositor will still + emit a configure event without the "maximized" state. + + + + + + Make the surface fullscreen. + + You can specify an output that you would prefer to be fullscreen. + If this value is NULL, it's up to the compositor to choose which + display will be used to map this surface. + + If the surface doesn't cover the whole output, the compositor will + position the surface in the center of the output and compensate with + black borders filling the rest of the output. + + + + + + + + Request that the compositor minimize your surface. There is no + way to know if the surface is currently minimized, nor is there + any way to unset minimization on this surface. + + If you are looking to throttle redrawing when minimized, please + instead use the wl_surface.frame event for this, as this will + also work with live previews on windows in Alt-Tab, Expose or + similar compositor features. + + + + + + This configure event asks the client to resize its toplevel surface or + to change its state. The configured state should not be applied + immediately. See xdg_surface.configure for details. + + The width and height arguments specify a hint to the window + about how its surface should be resized in window geometry + coordinates. See set_window_geometry. + + If the width or height arguments are zero, it means the client + should decide its own window dimension. This may happen when the + compositor needs to configure the state of the surface but doesn't + have any information about any previous or expected dimension. + + The states listed in the event specify how the width/height + arguments should be interpreted, and possibly how it should be + drawn. + + Clients must send an ack_configure in response to this event. See + xdg_surface.configure and xdg_surface.ack_configure for details. + + + + + + + + + The close event is sent by the compositor when the user + wants the surface to be closed. This should be equivalent to + the user clicking the close button in client-side decorations, + if your application has any. + + This is only a request that the user intends to close the + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data, etc. + + + + + + + A popup surface is a short-lived, temporary surface. It can be used to + implement for example menus, popovers, tooltips and other similar user + interface concepts. + + A popup can be made to take an explicit grab. See xdg_popup.grab for + details. + + When the popup is dismissed, a popup_done event will be sent out, and at + the same time the surface will be unmapped. See the xdg_popup.popup_done + event for details. + + Explicitly destroying the xdg_popup object will also dismiss the popup and + unmap the surface. Clients that want to dismiss the popup when another + surface of their own is clicked should dismiss the popup using the destroy + request. + + The parent surface must have either the xdg_toplevel or xdg_popup surface + role. + + A newly created xdg_popup will be stacked on top of all previously created + xdg_popup surfaces associated with the same xdg_toplevel. + + The parent of an xdg_popup must be mapped (see the xdg_surface + description) before the xdg_popup itself. + + The x and y arguments passed when creating the popup object specify + where the top left of the popup should be placed, relative to the + local surface coordinates of the parent surface. See + xdg_surface.get_popup. An xdg_popup must intersect with or be at least + partially adjacent to its parent surface. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_popup state to take effect. + + + + + + + + + This destroys the popup. Explicitly destroying the xdg_popup + object will also dismiss the popup, and unmap the surface. + + If this xdg_popup is not the "topmost" popup, a protocol error + will be sent. + + + + + + This request makes the created popup take an explicit grab. An explicit + grab will be dismissed when the user dismisses the popup, or when the + client destroys the xdg_popup. This can be done by the user clicking + outside the surface, using the keyboard, or even locking the screen + through closing the lid or a timeout. + + If the compositor denies the grab, the popup will be immediately + dismissed. + + This request must be used in response to some sort of user action like a + button press, key press, or touch down event. The serial number of the + event should be passed as 'serial'. + + The parent of a grabbing popup must either be an xdg_toplevel surface or + another xdg_popup with an explicit grab. If the parent is another + xdg_popup it means that the popups are nested, with this popup now being + the topmost popup. + + Nested popups must be destroyed in the reverse order they were created + in, e.g. the only popup you are allowed to destroy at all times is the + topmost one. + + When compositors choose to dismiss a popup, they may dismiss every + nested grabbing popup as well. When a compositor dismisses popups, it + will follow the same dismissing order as required from the client. + + The parent of a grabbing popup must either be another xdg_popup with an + active explicit grab, or an xdg_popup or xdg_toplevel, if there are no + explicit grabs already taken. + + If the topmost grabbing popup is destroyed, the grab will be returned to + the parent of the popup, if that parent previously had an explicit grab. + + If the parent is a grabbing popup which has already been dismissed, this + popup will be immediately dismissed. If the parent is a popup that did + not take an explicit grab, an error will be raised. + + During a popup grab, the client owning the grab will receive pointer + and touch events for all their surfaces as normal (similar to an + "owner-events" grab in X11 parlance), while the top most grabbing popup + will always have keyboard focus. + + + + + + + + This event asks the popup surface to configure itself given the + configuration. The configured state should not be applied immediately. + See xdg_surface.configure for details. + + The x and y arguments represent the position the popup was placed at + given the xdg_positioner rule, relative to the upper left corner of the + window geometry of the parent surface. + + + + + + + + + + The popup_done event is sent out when a popup is dismissed by the + compositor. The client should destroy the xdg_popup object at this + point. + + + + + diff --git a/src/3rdparty/wayland/protocols/xdg-shell/xdg-shell.xml b/src/3rdparty/wayland/protocols/xdg-shell/xdg-shell.xml new file mode 100644 index 00000000000..777eaa7499a --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-shell/xdg-shell.xml @@ -0,0 +1,1370 @@ + + + + + Copyright © 2008-2013 Kristian Høgsberg + Copyright © 2013 Rafael Antognolli + Copyright © 2013 Jasper St. Pierre + Copyright © 2010-2013 Intel Corporation + Copyright © 2015-2017 Samsung Electronics Co., Ltd + Copyright © 2015-2017 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The xdg_wm_base interface is exposed as a global object enabling clients + to turn their wl_surfaces into windows in a desktop environment. It + defines the basic functionality needed for clients and the compositor to + create windows that can be dragged, resized, maximized, etc, as well as + creating transient windows such as popup menus. + + + + + + + + + + + + + + + Destroy this xdg_wm_base object. + + Destroying a bound xdg_wm_base object while there are surfaces + still alive created by this xdg_wm_base object instance is illegal + and will result in a defunct_surfaces error. + + + + + + Create a positioner object. A positioner object is used to position + surfaces relative to some parent surface. See the interface description + and xdg_surface.get_popup for details. + + + + + + + This creates an xdg_surface for the given surface. While xdg_surface + itself is not a role, the corresponding surface may only be assigned + a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is + illegal to create an xdg_surface for a wl_surface which already has an + assigned role and this will result in a role error. + + This creates an xdg_surface for the given surface. An xdg_surface is + used as basis to define a role to a given surface, such as xdg_toplevel + or xdg_popup. It also manages functionality shared between xdg_surface + based surface roles. + + See the documentation of xdg_surface for more details about what an + xdg_surface is and how it is used. + + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_wm_base.ping + and xdg_wm_base.error.unresponsive. + + + + + + + The ping event asks the client if it's still alive. Pass the + serial specified in the event back to the compositor by sending + a "pong" request back with the specified serial. See xdg_wm_base.pong. + + Compositors can use this to determine if the client is still + alive. It's unspecified what will happen if the client doesn't + respond to the ping request, or in what timeframe. Clients should + try to respond in a reasonable amount of time. The “unresponsive” + error is provided for compositors that wish to disconnect unresponsive + clients. + + A compositor is free to ping in any way it wants, but a client must + always respond to any xdg_wm_base object it created. + + + + + + + + The xdg_positioner provides a collection of rules for the placement of a + child surface relative to a parent surface. Rules can be defined to ensure + the child surface remains within the visible area's borders, and to + specify how the child surface changes its position, such as sliding along + an axis, or flipping around a rectangle. These positioner-created rules are + constrained by the requirement that a child surface must intersect with or + be at least partially adjacent to its parent surface. + + See the various requests for details about possible rules. + + At the time of the request, the compositor makes a copy of the rules + specified by the xdg_positioner. Thus, after the request is complete the + xdg_positioner object can be destroyed or reused; further changes to the + object will have no effect on previous usages. + + For an xdg_positioner object to be considered complete, it must have a + non-zero size set by set_size, and a non-zero anchor rectangle set by + set_anchor_rect. Passing an incomplete xdg_positioner object when + positioning a surface raises an invalid_positioner error. + + + + + + + + + Notify the compositor that the xdg_positioner will no longer be used. + + + + + + Set the size of the surface that is to be positioned with the positioner + object. The size is in surface-local coordinates and corresponds to the + window geometry. See xdg_surface.set_window_geometry. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + Specify the anchor rectangle within the parent surface that the child + surface will be placed relative to. The rectangle is relative to the + window geometry as defined by xdg_surface.set_window_geometry of the + parent surface. + + When the xdg_positioner object is used to position a child surface, the + anchor rectangle may not extend outside the window geometry of the + positioned child's parent surface. + + If a negative size is set the invalid_input error is raised. + + + + + + + + + + + + + + + + + + + + + + Defines the anchor point for the anchor rectangle. The specified anchor + is used derive an anchor point that the child surface will be + positioned relative to. If a corner anchor is set (e.g. 'top_left' or + 'bottom_right'), the anchor point will be at the specified corner; + otherwise, the derived anchor point will be centered on the specified + edge, or in the center of the anchor rectangle if no edge is specified. + + + + + + + + + + + + + + + + + + + Defines in what direction a surface should be positioned, relative to + the anchor point of the parent surface. If a corner gravity is + specified (e.g. 'bottom_right' or 'top_left'), then the child surface + will be placed towards the specified gravity; otherwise, the child + surface will be centered over the anchor point on any axis that had no + gravity specified. If the gravity is not in the ‘gravity’ enum, an + invalid_input error is raised. + + + + + + + The constraint adjustment value define ways the compositor will adjust + the position of the surface, if the unadjusted position would result + in the surface being partly constrained. + + Whether a surface is considered 'constrained' is left to the compositor + to determine. For example, the surface may be partly outside the + compositor's defined 'work area', thus necessitating the child surface's + position be adjusted until it is entirely inside the work area. + + The adjustments can be combined, according to a defined precedence: 1) + Flip, 2) Slide, 3) Resize. + + + + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of an output. + + + + + Slide the surface along the x axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the x axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + x axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Slide the surface along the y axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the y axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + y axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Invert the anchor and gravity on the x axis if the surface is + constrained on the x axis. For example, if the left edge of the + surface is constrained, the gravity is 'left' and the anchor is + 'left', change the gravity to 'right' and the anchor to 'right'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_x adjustment will be the one before the + adjustment. + + + + + Invert the anchor and gravity on the y axis if the surface is + constrained on the y axis. For example, if the bottom edge of the + surface is constrained, the gravity is 'bottom' and the anchor is + 'bottom', change the gravity to 'top' and the anchor to 'top'. + + The adjusted position is calculated given the original anchor + rectangle and offset, but with the new flipped anchor and gravity + values. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_y adjustment will be the one before the + adjustment. + + + + + Resize the surface horizontally so that it is completely + unconstrained. + + + + + Resize the surface vertically so that it is completely unconstrained. + + + + + + + Specify how the window should be positioned if the originally intended + position caused the surface to be constrained, meaning at least + partially outside positioning boundaries set by the compositor. The + adjustment is set by constructing a bitmask describing the adjustment to + be made when the surface is constrained on that axis. + + If no bit for one axis is set, the compositor will assume that the child + surface should not change its position on that axis when constrained. + + If more than one bit for one axis is set, the order of how adjustments + are applied is specified in the corresponding adjustment descriptions. + + The default adjustment is none. + + + + + + + Specify the surface position offset relative to the position of the + anchor on the anchor rectangle and the anchor on the surface. For + example if the anchor of the anchor rectangle is at (x, y), the surface + has the gravity bottom|right, and the offset is (ox, oy), the calculated + surface position will be (x + ox, y + oy). The offset position of the + surface is the one used for constraint testing. See + set_constraint_adjustment. + + An example use case is placing a popup menu on top of a user interface + element, while aligning the user interface element of the parent surface + with some user interface element placed somewhere in the popup surface. + + + + + + + + + + When set reactive, the surface is reconstrained if the conditions used + for constraining changed, e.g. the parent window moved. + + If the conditions changed and the popup was reconstrained, an + xdg_popup.configure event is sent with updated geometry, followed by an + xdg_surface.configure event. + + + + + + Set the parent window geometry the compositor should use when + positioning the popup. The compositor may use this information to + determine the future state the popup should be constrained using. If + this doesn't match the dimension of the parent the popup is eventually + positioned against, the behavior is undefined. + + The arguments are given in the surface-local coordinate space. + + + + + + + + Set the serial of an xdg_surface.configure event this positioner will be + used in response to. The compositor may use this information together + with set_parent_size to determine what future state the popup should be + constrained using. + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides a base set of functionality required to construct user + interface elements requiring management by the compositor, such as + toplevel windows, menus, etc. The types of functionality are split into + xdg_surface roles. + + Creating an xdg_surface does not set the role for a wl_surface. In order + to map an xdg_surface, the client must create a role-specific object + using, e.g., get_toplevel, get_popup. The wl_surface for any given + xdg_surface can have at most one role, and may not be assigned any role + not based on xdg_surface. + + A role must be assigned before any other requests are made to the + xdg_surface object. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. + + Creating an xdg_surface from a wl_surface which has a buffer attached or + committed is a client error, and any attempts by a client to attach or + manipulate a buffer prior to the first xdg_surface.configure call must + also be treated as errors. + + After creating a role-specific object and setting it up, the client must + perform an initial commit without any buffer attached. The compositor + will reply with initial wl_surface state such as + wl_surface.preferred_buffer_scale followed by an xdg_surface.configure + event. The client must acknowledge it and is then allowed to attach a + buffer to map the surface. + + Mapping an xdg_surface-based role surface is defined as making it + possible for the surface to be shown by the compositor. Note that + a mapped surface is not guaranteed to be visible once it is mapped. + + For an xdg_surface to be mapped by the compositor, the following + conditions must be met: + (1) the client has assigned an xdg_surface-based role to the surface + (2) the client has set and committed the xdg_surface state and the + role-dependent state to the surface + (3) the client has committed a buffer to the surface + + A newly-unmapped surface is considered to have met condition (1) out + of the 3 required conditions for mapping a surface if its role surface + has not been destroyed, i.e. the client must perform the initial commit + again before attaching a buffer. + + + + + + + + + + + + + + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed, otherwise + a defunct_role_object error is raised. + + + + + + This creates an xdg_toplevel object for the given xdg_surface and gives + the associated wl_surface the xdg_toplevel role. + + See the documentation of xdg_toplevel for more details about what an + xdg_toplevel is and how it is used. + + + + + + + This creates an xdg_popup object for the given xdg_surface and gives + the associated wl_surface the xdg_popup role. + + If null is passed as a parent, a parent surface must be specified using + some other protocol, before committing the initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + + + The window geometry of a surface is its "visible bounds" from the + user's perspective. Client-side decorations often have invisible + portions like drop-shadows which should be ignored for the + purposes of aligning, placing and constraining windows. + + The window geometry is double buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + When maintaining a position, the compositor should treat the (x, y) + coordinate of the window geometry as the top left corner of the window. + A client changing the (x, y) window geometry coordinate should in + general not alter the position of the window. + + Once the window geometry of the surface is set, it is not possible to + unset it, and it will remain the same until set_window_geometry is + called again, even if a new subsurface or buffer is attached. + + If never set, the value is the full bounds of the surface, + including any subsurfaces. This updates dynamically on every + commit. This unset is meant for extremely simple clients. + + The arguments are given in the surface-local coordinate space of + the wl_surface associated with this xdg_surface, and may extend outside + of the wl_surface itself to mark parts of the subsurface tree as part of + the window geometry. + + When applied, the effective window geometry will be the set window + geometry clamped to the bounding rectangle of the combined + geometry of the surface of the xdg_surface and the associated + subsurfaces. + + The effective geometry will not be recalculated unless a new call to + set_window_geometry is done and the new pending surface state is + subsequently applied. + + The width and height of the effective window geometry must be + greater than zero. Setting an invalid size will raise an + invalid_size error. + + + + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + For instance, for toplevel surfaces the compositor might use this + information to move a surface to the top left only when the client has + drawn itself for the maximized or fullscreen state. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + Acking a configure event that was never sent raises an invalid_serial + error. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + Sending an ack_configure request consumes the serial number sent with + the request, as well as serial numbers sent by all configure events + sent on this xdg_surface prior to the configure event referenced by + the committed serial. + + It is an error to issue multiple ack_configure requests referencing a + serial from the same configure event, or to issue an ack_configure + request referencing a serial from a configure event issued before the + event identified by the last ack_configure request for the same + xdg_surface. Doing so will raise an invalid_serial error. + + + + + + + The configure event marks the end of a configure sequence. A configure + sequence is a set of one or more events configuring the state of the + xdg_surface, including the final xdg_surface.configure event. + + Where applicable, xdg_surface surface roles will during a configure + sequence extend this event as a latched state sent as events before the + xdg_surface.configure event. Such events should be considered to make up + a set of atomically applied configuration states, where the + xdg_surface.configure commits the accumulated state. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + If the client receives multiple configure events before it can respond + to one, it is free to discard all but the last event it received. + + + + + + + + + This interface defines an xdg_surface role which allows a surface to, + among other things, set window-like properties such as maximize, + fullscreen, and minimize, set application-specific metadata like title and + id, and well as trigger user interactive operations such as interactive + resize and move. + + Unmapping an xdg_toplevel means that the surface cannot be shown + by the compositor until it is explicitly mapped again. + All active operations (e.g., move, resize) are canceled and all + attributes (e.g. title, state, stacking, ...) are discarded for + an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + the state it had right after xdg_surface.get_toplevel. The client + can re-map the toplevel by perfoming a commit without any buffer + attached, waiting for a configure event and handling it as usual (see + xdg_surface description). + + Attaching a null buffer to a toplevel unmaps the surface. + + + + + This request destroys the role surface and unmaps the surface; + see "Unmapping" behavior in interface section for details. + + + + + + + + + + + + Set the "parent" of this surface. This surface should be stacked + above the parent surface and all other ancestor surfaces. + + Parent surfaces should be set on dialogs, toolboxes, or other + "auxiliary" surfaces, so that the parent is raised when the dialog + is raised. + + Setting a null parent for a child surface unsets its parent. Setting + a null parent for a surface which currently has no parent is a no-op. + + Only mapped surfaces can have child surfaces. Setting a parent which + is not mapped is equivalent to setting a null parent. If a surface + becomes unmapped, its children's parent is set to the parent of + the now-unmapped surface. If the now-unmapped surface has no parent, + its children's parent is unset. If the now-unmapped surface becomes + mapped again, its parent-child relationship is not restored. + + The parent toplevel must not be one of the child toplevel's + descendants, and the parent must be different from the child toplevel, + otherwise the invalid_parent protocol error is raised. + + + + + + + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + + + + + + + Set an application identifier for the surface. + + The app ID identifies the general class of applications to which + the surface belongs. The compositor can use this to group multiple + surfaces together, or to determine how to launch a new application. + + For D-Bus activatable applications, the app ID is used as the D-Bus + service name. + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + + Like other properties, a set_app_id request can be sent after the + xdg_toplevel has been mapped to update the property. + + See the desktop-entry specification [0] for more details on + application identifiers and how they relate to well-known D-Bus + names and .desktop files. + + [0] https://standards.freedesktop.org/desktop-entry-spec/ + + + + + + + Clients implementing client-side decorations might want to show + a context menu when right-clicking on the decorations, giving the + user a menu that they can use to maximize or minimize the window. + + This request asks the compositor to pop up such a window menu at + the given position, relative to the local surface coordinates of + the parent surface. There are no guarantees as to what menu items + the window menu contains, or even if a window menu will be drawn + at all. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + + + + + + + + + + Start an interactive, user-driven move of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive move (touch, + pointer, etc). + + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized), or if the passed serial + is no longer valid. + + If triggered, the surface will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the move. It is up to the + compositor to visually indicate that the move is taking place, such as + updating a pointer cursor, during the move. There is no guarantee + that the device focus will return when the move is completed. + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + + + + + + + + + + + + + + + Start a user-driven, interactive resize of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive resize (touch, + pointer, etc). + + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + If triggered, the client will receive configure events with the + "resize" state enum value and the expected sizes. See the "resize" + enum value for more details about what is required. The client + must also acknowledge configure events using "ack_configure". After + the resize is completed, the client will receive another "configure" + event without the resize state. + + If triggered, the surface also will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the resize. It is up to the + compositor to visually indicate that the resize is taking place, + such as updating a pointer cursor, during the resize. There is no + guarantee that the device focus will return when the resize is + completed. + + The edges parameter specifies how the surface should be resized, and + is one of the values of the resize_edge enum. Values not matching + a variant of the enum will cause the invalid_resize_edge protocol error. + The compositor may use this information to update the surface position + for example when dragging the top left corner. The compositor may also + use this information to adapt its behavior, e.g. choose an appropriate + cursor image. + + + + + + + + + The different state values used on the surface. This is designed for + state values like maximized, fullscreen. It is paired with the + configure event to ensure that both the client and the compositor + setting the state can be synchronized. + + States set in this way are double-buffered. They will get applied on + the next commit. + + + + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client, or the xdg_wm_base.invalid_surface_state + error is raised. + + The client should draw without shadow or other + decoration outside of the window geometry. + + + + + The surface is fullscreen. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. For + a surface to cover the whole fullscreened area, the geometry + dimensions must be obeyed by the client. For more details, see + xdg_toplevel.set_fullscreen. + + + + + The surface is being resized. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. + Clients that have aspect ratio or cell sizing configuration can use + a smaller size, however. + + + + + Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. + + + + + The window is currently in a tiled layout and the left edge is + considered to be adjacent to another part of the tiling grid. + + + + + The window is currently in a tiled layout and the right edge is + considered to be adjacent to another part of the tiling grid. + + + + + The window is currently in a tiled layout and the top edge is + considered to be adjacent to another part of the tiling grid. + + + + + The window is currently in a tiled layout and the bottom edge is + considered to be adjacent to another part of the tiling grid. + + + + + The surface is currently not ordinarily being repainted; for + example because its content is occluded by another window, or its + outputs are switched off due to screen locking. + + + + + + + Set a maximum size for the window. + + The client can specify a maximum size so that the compositor does + not try to configure the window beyond this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the maximum + size. The compositor may decide to ignore the values set by the + client and request a larger size. + + If never set, or a value of zero in the request, means that the + client has no expected maximum size in the given dimension. + As a result, a client wishing to reset the maximum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a maximum size to be smaller than the minimum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width or height will result in a + invalid_size error. + + + + + + + + Set a minimum size for the window. + + The client can specify a minimum size so that the compositor does + not try to configure the window below this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the minimum + size. The compositor may decide to ignore the values set by the + client and request a smaller size. + + If never set, or a value of zero in the request, means that the + client has no expected minimum size in the given dimension. + As a result, a client wishing to reset the minimum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a minimum size to be larger than the maximum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + invalid_size error. + + + + + + + + Maximize the surface. + + After requesting that the surface should be maximized, the compositor + will respond by emitting a configure event. Whether this configure + actually sets the window maximized is subject to compositor policies. + The client must then update its content, drawing in the configured + state. The client must also acknowledge the configure when committing + the new content (see ack_configure). + + It is up to the compositor to decide how and where to maximize the + surface, for example which output and what region of the screen should + be used. + + If the surface was already maximized, the compositor will still emit + a configure event with the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + + + + + + Unmaximize the surface. + + After requesting that the surface should be unmaximized, the compositor + will respond by emitting a configure event. Whether this actually + un-maximizes the window is subject to compositor policies. + If available and applicable, the compositor will include the window + geometry dimensions the window had prior to being maximized in the + configure event. The client must then update its content, drawing it in + the configured state. The client must also acknowledge the configure + when committing the new content (see ack_configure). + + It is up to the compositor to position the surface after it was + unmaximized; usually the position the surface had before maximizing, if + applicable. + + If the surface was already not maximized, the compositor will still + emit a configure event without the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + + + + + + Make the surface fullscreen. + + After requesting that the surface should be fullscreened, the + compositor will respond by emitting a configure event. Whether the + client is actually put into a fullscreen state is subject to compositor + policies. The client must also acknowledge the configure when + committing the new content (see ack_configure). + + The output passed by the request indicates the client's preference as + to which display it should be set fullscreen on. If this value is NULL, + it's up to the compositor to choose which display will be used to map + this surface. + + If the surface doesn't cover the whole output, the compositor will + position the surface in the center of the output and compensate with + with border fill covering the rest of the output. The content of the + border fill is undefined, but should be assumed to be in some way that + attempts to blend into the surrounding area (e.g. solid black). + + If the fullscreened surface is not opaque, the compositor must make + sure that other screen content not part of the same surface tree (made + up of subsurfaces, popups or similarly coupled surfaces) are not + visible below the fullscreened surface. + + + + + + + Make the surface no longer fullscreen. + + After requesting that the surface should be unfullscreened, the + compositor will respond by emitting a configure event. + Whether this actually removes the fullscreen state of the client is + subject to compositor policies. + + Making a surface unfullscreen sets states for the surface based on the following: + * the state(s) it may have had before becoming fullscreen + * any state(s) decided by the compositor + * any state(s) requested by the client while the surface was fullscreen + + The compositor may include the previous window geometry dimensions in + the configure event, if applicable. + + The client must also acknowledge the configure when committing the new + content (see ack_configure). + + + + + + Request that the compositor minimize your surface. There is no + way to know if the surface is currently minimized, nor is there + any way to unset minimization on this surface. + + If you are looking to throttle redrawing when minimized, please + instead use the wl_surface.frame event for this, as this will + also work with live previews on windows in Alt-Tab, Expose or + similar compositor features. + + + + + + This configure event asks the client to resize its toplevel surface or + to change its state. The configured state should not be applied + immediately. See xdg_surface.configure for details. + + The width and height arguments specify a hint to the window + about how its surface should be resized in window geometry + coordinates. See set_window_geometry. + + If the width or height arguments are zero, it means the client + should decide its own window dimension. This may happen when the + compositor needs to configure the state of the surface but doesn't + have any information about any previous or expected dimension. + + The states listed in the event specify how the width/height + arguments should be interpreted, and possibly how it should be + drawn. + + Clients must send an ack_configure in response to this event. See + xdg_surface.configure and xdg_surface.ack_configure for details. + + + + + + + + + The close event is sent by the compositor when the user + wants the surface to be closed. This should be equivalent to + the user clicking the close button in client-side decorations, + if your application has any. + + This is only a request that the user intends to close the + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data, etc. + + + + + + + + The configure_bounds event may be sent prior to a xdg_toplevel.configure + event to communicate the bounds a window geometry size is recommended + to constrain to. + + The passed width and height are in surface coordinate space. If width + and height are 0, it means bounds is unknown and equivalent to as if no + configure_bounds event was ever sent for this surface. + + The bounds can for example correspond to the size of a monitor excluding + any panels or other shell components, so that a surface isn't created in + a way that it cannot fit. + + The bounds may change at any point, and in such a case, a new + xdg_toplevel.configure_bounds will be sent, followed by + xdg_toplevel.configure and xdg_surface.configure. + + + + + + + + + + + + + + + + + This event advertises the capabilities supported by the compositor. If + a capability isn't supported, clients should hide or disable the UI + elements that expose this functionality. For instance, if the + compositor doesn't advertise support for minimized toplevels, a button + triggering the set_minimized request should not be displayed. + + The compositor will ignore requests it doesn't support. For instance, + a compositor which doesn't advertise support for minimized will ignore + set_minimized requests. + + Compositors must send this event once before the first + xdg_surface.configure event. When the capabilities change, compositors + must send this event again and then send an xdg_surface.configure + event. + + The configured state should not be applied immediately. See + xdg_surface.configure for details. + + The capabilities are sent as an array of 32-bit unsigned integers in + native endianness. + + + + + + + + A popup surface is a short-lived, temporary surface. It can be used to + implement for example menus, popovers, tooltips and other similar user + interface concepts. + + A popup can be made to take an explicit grab. See xdg_popup.grab for + details. + + When the popup is dismissed, a popup_done event will be sent out, and at + the same time the surface will be unmapped. See the xdg_popup.popup_done + event for details. + + Explicitly destroying the xdg_popup object will also dismiss the popup and + unmap the surface. Clients that want to dismiss the popup when another + surface of their own is clicked should dismiss the popup using the destroy + request. + + A newly created xdg_popup will be stacked on top of all previously created + xdg_popup surfaces associated with the same xdg_toplevel. + + The parent of an xdg_popup must be mapped (see the xdg_surface + description) before the xdg_popup itself. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_popup state to take effect. + + + + + + + + + This destroys the popup. Explicitly destroying the xdg_popup + object will also dismiss the popup, and unmap the surface. + + If this xdg_popup is not the "topmost" popup, the + xdg_wm_base.not_the_topmost_popup protocol error will be sent. + + + + + + This request makes the created popup take an explicit grab. An explicit + grab will be dismissed when the user dismisses the popup, or when the + client destroys the xdg_popup. This can be done by the user clicking + outside the surface, using the keyboard, or even locking the screen + through closing the lid or a timeout. + + If the compositor denies the grab, the popup will be immediately + dismissed. + + This request must be used in response to some sort of user action like a + button press, key press, or touch down event. The serial number of the + event should be passed as 'serial'. + + The parent of a grabbing popup must either be an xdg_toplevel surface or + another xdg_popup with an explicit grab. If the parent is another + xdg_popup it means that the popups are nested, with this popup now being + the topmost popup. + + Nested popups must be destroyed in the reverse order they were created + in, e.g. the only popup you are allowed to destroy at all times is the + topmost one. + + When compositors choose to dismiss a popup, they may dismiss every + nested grabbing popup as well. When a compositor dismisses popups, it + will follow the same dismissing order as required from the client. + + If the topmost grabbing popup is destroyed, the grab will be returned to + the parent of the popup, if that parent previously had an explicit grab. + + If the parent is a grabbing popup which has already been dismissed, this + popup will be immediately dismissed. If the parent is a popup that did + not take an explicit grab, an error will be raised. + + During a popup grab, the client owning the grab will receive pointer + and touch events for all their surfaces as normal (similar to an + "owner-events" grab in X11 parlance), while the top most grabbing popup + will always have keyboard focus. + + + + + + + + This event asks the popup surface to configure itself given the + configuration. The configured state should not be applied immediately. + See xdg_surface.configure for details. + + The x and y arguments represent the position the popup was placed at + given the xdg_positioner rule, relative to the upper left corner of the + window geometry of the parent surface. + + For version 2 or older, the configure event for an xdg_popup is only + ever sent once for the initial configuration. Starting with version 3, + it may be sent again if the popup is setup with an xdg_positioner with + set_reactive requested, or in response to xdg_popup.reposition requests. + + + + + + + + + + The popup_done event is sent out when a popup is dismissed by the + compositor. The client should destroy the xdg_popup object at this + point. + + + + + + + + Reposition an already-mapped popup. The popup will be placed given the + details in the passed xdg_positioner object, and a + xdg_popup.repositioned followed by xdg_popup.configure and + xdg_surface.configure will be emitted in response. Any parameters set + by the previous positioner will be discarded. + + The passed token will be sent in the corresponding + xdg_popup.repositioned event. The new popup position will not take + effect until the corresponding configure event is acknowledged by the + client. See xdg_popup.repositioned for details. The token itself is + opaque, and has no other special meaning. + + If multiple reposition requests are sent, the compositor may skip all + but the last one. + + If the popup is repositioned in response to a configure event for its + parent, the client should send an xdg_positioner.set_parent_configure + and possibly an xdg_positioner.set_parent_size request to allow the + compositor to properly constrain the popup. + + If the popup is repositioned together with a parent that is being + resized, but not in response to a configure event, the client should + send an xdg_positioner.set_parent_size request. + + + + + + + + The repositioned event is sent as part of a popup configuration + sequence, together with xdg_popup.configure and lastly + xdg_surface.configure to notify the completion of a reposition request. + + The repositioned event is to notify about the completion of a + xdg_popup.reposition request. The token argument is the token passed + in the xdg_popup.reposition request. + + Immediately after this event is emitted, xdg_popup.configure and + xdg_surface.configure will be sent with the updated size and position, + as well as a new configure serial. + + The client should optionally update the content of the popup, but must + acknowledge the new popup configuration for the new position to take + effect. See xdg_surface.ack_configure for details. + + + + + + diff --git a/src/3rdparty/wayland/protocols/xdg-system-bell/REUSE.toml b/src/3rdparty/wayland/protocols/xdg-system-bell/REUSE.toml new file mode 100644 index 00000000000..c8b780621ef --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-system-bell/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = ["xdg-system-bell-v1.xml"] +precedence = "closest" +SPDX-FileCopyrightText = "Copyright (C) 2016, 2023 Red Hat" +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/xdg-system-bell/qt_attribution.json b/src/3rdparty/wayland/protocols/xdg-system-bell/qt_attribution.json new file mode 100644 index 00000000000..cf8b0e5bde8 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-system-bell/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-xdg-system-bell-protocol", + "Name": "Wayland XDG System Bell Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor, and the Qt Wayland platform plugin.", + "Files": "xdg-system-bell-v1.xml", + + "Description": "The XDG-System-Bell protocol provides a mechanism for apps to provide visual notification feedback through the compositor.", + "Homepage": "https://gitlab.freedesktop.org/wayland/wayland-protocols/", + "Version": "1.18", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/tree/1.38/", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2016, 2023 Red Hat\n" + } +] diff --git a/src/3rdparty/wayland/protocols/xdg-system-bell/xdg-system-bell-v1.xml b/src/3rdparty/wayland/protocols/xdg-system-bell/xdg-system-bell-v1.xml new file mode 100644 index 00000000000..f00508de850 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-system-bell/xdg-system-bell-v1.xml @@ -0,0 +1,58 @@ + + + + Copyright © 2016, 2023 Red Hat + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This global interface enables clients to ring the system bell. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + Notify that the object will no longer be used. + + + + + + This requests rings the system bell on behalf of a client. How ringing + the bell is implemented is up to the compositor. It may be an audible + sound, a visual feedback of some kind, or any other thing including + nothing. + + The passed surface should correspond to a toplevel like surface role, + or be null, meaning the client doesn't have a particular toplevel it + wants to associate the bell ringing with. See the xdg-shell protocol + extension for a toplevel like surface role. + + + + + diff --git a/src/3rdparty/wayland/protocols/xdg-toplevel-drag/REUSE.toml b/src/3rdparty/wayland/protocols/xdg-toplevel-drag/REUSE.toml new file mode 100644 index 00000000000..9bc6663ae58 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-toplevel-drag/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "xdg-toplevel-drag-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright 2023 David Redondo" +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/xdg-toplevel-drag/xdg-toplevel-drag-v1.xml b/src/3rdparty/wayland/protocols/xdg-toplevel-drag/xdg-toplevel-drag-v1.xml new file mode 100644 index 00000000000..2fe9645839c --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-toplevel-drag/xdg-toplevel-drag-v1.xml @@ -0,0 +1,141 @@ + + + + + Copyright 2023 David Redondo + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This protocol enhances normal drag and drop with the ability to move a + window at the same time. This allows having detachable parts of a window + that when dragged out of it become a new window and can be dragged over + an existing window to be reattached. + + A typical workflow would be when the user starts dragging on top of a + detachable part of a window, the client would create a wl_data_source and + a xdg_toplevel_drag_v1 object and start the drag as normal via + wl_data_device.start_drag. Once the client determines that the detachable + window contents should be detached from the originating window, it creates + a new xdg_toplevel with these contents and issues a + xdg_toplevel_drag_v1.attach request before mapping it. From now on the new + window is moved by the compositor during the drag as if the client called + xdg_toplevel.move. + + Dragging an existing window is similar. The client creates a + xdg_toplevel_drag_v1 object and attaches the existing toplevel before + starting the drag. + + Clients use the existing drag and drop mechanism to detect when a window + can be docked or undocked. If the client wants to snap a window into a + parent window it should delete or unmap the dragged top-level. If the + contents should be detached again it attaches a new toplevel as described + above. If a drag operation is cancelled without being dropped, clients + should revert to the previous state, deleting any newly created windows + as appropriate. When a drag operation ends as indicated by + wl_data_source.dnd_drop_performed the dragged toplevel window's final + position is determined as if a xdg_toplevel_move operation ended. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + + + + + Destroy this xdg_toplevel_drag_manager_v1 object. Other objects, + including xdg_toplevel_drag_v1 objects created by this factory, are not + affected by this request. + + + + + + Create an xdg_toplevel_drag for a drag and drop operation that is going + to be started with data_source. + + This request can only be made on sources used in drag-and-drop, so it + must be performed before wl_data_device.start_drag. Attempting to use + the source other than for drag-and-drop such as in + wl_data_device.set_selection will raise an invalid_source error. + + Destroying data_source while a toplevel is attached to the + xdg_toplevel_drag is undefined. + + + + + + + + + + + + + + + + + + + Destroy this xdg_toplevel_drag_v1 object. This request must only be + called after the underlying wl_data_source drag has ended, as indicated + by the dnd_drop_performed or cancelled events. In any other case an + ongoing_drag error is raised. + + + + + + Request that the window will be moved with the cursor during the drag + operation. The offset is a hint to the compositor how the toplevel + should be positioned relative to the cursor hotspot in surface local + coordinates. For example it might only be used when an unmapped window + is attached. The attached window does not participate in the selection + of the drag target. + + If the toplevel is unmapped while it is attached, it is automatically + detached from the drag. In this case this request has to be called again + if the window should be attached after it is remapped. + + This request can be called multiple times but issuing it while a + toplevel with an active role is attached raises a toplevel_attached + error. + + + + + + + + + + diff --git a/src/3rdparty/wayland/protocols/xdg-toplevel-icon/REUSE.toml b/src/3rdparty/wayland/protocols/xdg-toplevel-icon/REUSE.toml new file mode 100644 index 00000000000..982f28763a7 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-toplevel-icon/REUSE.toml @@ -0,0 +1,7 @@ +version = 1 + +[[annotations]] +path = "xdg-toplevel-icon-v1.xml" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright (C) 2024 Matthias Klumpp 2024 David Edmundson" +SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/xdg-toplevel-icon/qt_attribution.json b/src/3rdparty/wayland/protocols/xdg-toplevel-icon/qt_attribution.json new file mode 100644 index 00000000000..e0a675b93d3 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-toplevel-icon/qt_attribution.json @@ -0,0 +1,18 @@ +[ + { + "Id": "wayland-xdg-toplevel-icon-protocol", + "Name": "Wayland xdg-toplevel-icon Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland Compositor API, and the Qt Wayland platform plugin.", + "Files": "xdg-toplevel-icon-v1.xml", + + "Description": "The xdg-toplevel-icon protocol allows a compositor to announce support for window icons.", + "Homepage": "https://wayland.freedesktop.org", + "Version": "version 1", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/raw/1.37/staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "../MIT_LICENSE.txt", + "Copyright": "Copyright © 2024 Matthias Klumpp 2024 David Edmundson" + } +] diff --git a/src/3rdparty/wayland/protocols/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml b/src/3rdparty/wayland/protocols/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml new file mode 100644 index 00000000000..fc409fef7c6 --- /dev/null +++ b/src/3rdparty/wayland/protocols/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml @@ -0,0 +1,205 @@ + + + + + Copyright © 2023-2024 Matthias Klumpp + Copyright © 2024 David Edmundson + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows clients to set icons for their toplevel surfaces + either via the XDG icon stock (using an icon name), or from pixel data. + + A toplevel icon represents the individual toplevel (unlike the application + or launcher icon, which represents the application as a whole), and may be + shown in window switchers, window overviews and taskbars that list + individual windows. + + This document adheres to RFC 2119 when using words like "must", + "should", "may", etc. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + This interface allows clients to create toplevel window icons and set + them on toplevel windows to be displayed to the user. + + + + + Destroy the toplevel icon manager. + This does not destroy objects created with the manager. + + + + + + Creates a new icon object. This icon can then be attached to a + xdg_toplevel via the 'set_icon' request. + + + + + + + This request assigns the icon 'icon' to 'toplevel', or clears the + toplevel icon if 'icon' was null. + This state is double-buffered and is applied on the next + wl_surface.commit of the toplevel. + + After making this call, the xdg_toplevel_icon_v1 provided as 'icon' + can be destroyed by the client without 'toplevel' losing its icon. + The xdg_toplevel_icon_v1 is immutable from this point, and any + future attempts to change it must raise the + 'xdg_toplevel_icon_v1.immutable' protocol error. + + The compositor must set the toplevel icon from either the pixel data + the icon provides, or by loading a stock icon using the icon name. + See the description of 'xdg_toplevel_icon_v1' for details. + + If 'icon' is set to null, the icon of the respective toplevel is reset + to its default icon (usually the icon of the application, derived from + its desktop-entry file, or a placeholder icon). + If this request is passed an icon with no pixel buffers or icon name + assigned, the icon must be reset just like if 'icon' was null. + + + + + + + + This event indicates an icon size the compositor prefers to be + available if the client has scalable icons and can render to any size. + + When the 'xdg_toplevel_icon_manager_v1' object is created, the + compositor may send one or more 'icon_size' events to describe the list + of preferred icon sizes. If the compositor has no size preference, it + may not send any 'icon_size' event, and it is up to the client to + decide a suitable icon size. + + A sequence of 'icon_size' events must be finished with a 'done' event. + If the compositor has no size preferences, it must still send the + 'done' event, without any preceding 'icon_size' events. + + + + + + + This event is sent after all 'icon_size' events have been sent. + + + + + + + This interface defines a toplevel icon. + An icon can have a name, and multiple buffers. + In order to be applied, the icon must have either a name, or at least + one buffer assigned. Applying an empty icon (with no buffer or name) to + a toplevel should reset its icon to the default icon. + + It is up to compositor policy whether to prefer using a buffer or loading + an icon via its name. See 'set_name' and 'add_buffer' for details. + + + + + + + + + + + Destroys the 'xdg_toplevel_icon_v1' object. + The icon must still remain set on every toplevel it was assigned to, + until the toplevel icon is reset explicitly. + + + + + + This request assigns an icon name to this icon. + Any previously set name is overridden. + + The compositor must resolve 'icon_name' according to the lookup rules + described in the XDG icon theme specification[1] using the + environment's current icon theme. + + If the compositor does not support icon names or cannot resolve + 'icon_name' according to the XDG icon theme specification it must + fall back to using pixel buffer data instead. + + If this request is made after the icon has been assigned to a toplevel + via 'set_icon', a 'immutable' error must be raised. + + [1]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + + + + + + + This request adds pixel data supplied as wl_buffer to the icon. + + The client should add pixel data for all icon sizes and scales that + it can provide, or which are explicitly requested by the compositor + via 'icon_size' events on xdg_toplevel_icon_manager_v1. + + The wl_buffer supplying pixel data as 'buffer' must be backed by wl_shm + and must be a square (width and height being equal). + If any of these buffer requirements are not fulfilled, a 'invalid_buffer' + error must be raised. + + If this icon instance already has a buffer of the same size and scale + from a previous 'add_buffer' request, data from the last request + overrides the preexisting pixel data. + + The wl_buffer must be kept alive for as long as the xdg_toplevel_icon + it is associated with is not destroyed, otherwise a 'no_buffer' error + is raised. The buffer contents must not be modified after it was + assigned to the icon. As a result, the region of the wl_shm_pool's + backing storage used for the wl_buffer must not be modified after this + request is sent. The wl_buffer.release event is unused. + + If this request is made after the icon has been assigned to a toplevel + via 'set_icon', a 'immutable' error must be raised. + + + + + + diff --git a/src/gui/configure.cmake b/src/gui/configure.cmake index 95985bd0fa0..852fc0407e5 100644 --- a/src/gui/configure.cmake +++ b/src/gui/configure.cmake @@ -70,8 +70,19 @@ qt_find_package(Tslib PROVIDED_TARGETS PkgConfig::Tslib MODULE_NAME gui QMAKE_LI qt_find_package(WrapVulkanHeaders PROVIDED_TARGETS WrapVulkanHeaders::WrapVulkanHeaders MODULE_NAME gui QMAKE_LIB vulkan MARK_OPTIONAL) if((LINUX) OR QT_FIND_ALL_PACKAGES_ALWAYS) - qt_find_package(Wayland PROVIDED_TARGETS Wayland::Server MODULE_NAME gui QMAKE_LIB wayland_server) - qt_find_package(Wayland PROVIDED_TARGETS Wayland::Client MODULE_NAME gui QMAKE_LIB wayland_client) + qt_find_package(Wayland PROVIDED_TARGETS Wayland::Server + MODULE_NAME gui QMAKE_LIB wayland_server) + qt_find_package(Wayland PROVIDED_TARGETS Wayland::Client + MODULE_NAME gui QMAKE_LIB wayland_client) + # Gui doesn't use these, but the wayland qpa plugin does, and we + # need to list the rest of the provided targets here, so they are + # promoted to global, and can be accessed by the SBOM at the root + # project level. That's not possible to do in the wayland qpa subdir, + # due to different cmake directory scopes. + qt_find_package(Wayland PROVIDED_TARGETS Wayland::Cursor + MODULE_NAME gui QMAKE_LIB wayland_cursor) + qt_find_package(Wayland PROVIDED_TARGETS Wayland::Egl + MODULE_NAME gui QMAKE_LIB wayland_egl) endif() if((X11_SUPPORTED) OR QT_FIND_ALL_PACKAGES_ALWAYS) qt_find_package(X11 PROVIDED_TARGETS X11::X11 MODULE_NAME gui QMAKE_LIB xlib) @@ -152,6 +163,25 @@ if((X11_SUPPORTED) OR QT_FIND_ALL_PACKAGES_ALWAYS) endif() qt_add_qmake_lib_dependency(xrender xlib) +# qt wayland client +if(LINUX OR QT_FIND_ALL_PACKAGES_ALWAYS) + # EGL + if(NOT TARGET EGL::EGL) + qt_find_package(EGL PROVIDED_TARGETS EGL::EGL MODULE_NAME gui QMAKE_LIB egl MARK_OPTIONAL) + endif() + # and Libdrm + if(NOT TARGET Libdrm::Libdrm) + qt_find_package(Libdrm + PROVIDED_TARGETS Libdrm::Libdrm + MODULE_NAME gui + QMAKE_LIB drm + MARK_OPTIONAL) + endif() +endif() + +qt_find_package(Wayland 1.15) +qt_find_package(WaylandScanner PROVIDED_TARGETS Wayland::Scanner) + qt_find_package(RenderDoc PROVIDED_TARGETS RenderDoc::RenderDoc) #### Tests @@ -697,6 +727,128 @@ int main(int, char **) } ") +# qtwayland client +# drm-egl-server +qt_config_compile_test(drm_egl_server + LABEL "DRM EGL Server" + LIBRARIES + EGL::EGL + CODE + " +#include +#include + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + /* BEGIN TEST: */ +#ifdef EGL_MESA_drm_image +return 0; +#else +#error Requires EGL_MESA_drm_image to be defined +return 1; +#endif + /* END TEST: */ + return 0; +} +") + +# libhybris-egl-server +qt_config_compile_test(libhybris_egl_server + LABEL "libhybris EGL Server" + LIBRARIES + EGL::EGL + CODE + " +#include +#include +#include + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + /* BEGIN TEST: */ +#ifdef EGL_HYBRIS_native_buffer +return 0; +#else +#error Requires EGL_HYBRIS_native_buffer to be defined +return 1; +#endif + /* END TEST: */ + return 0; +} +") + +# dmabuf-server-buffer +qt_config_compile_test(dmabuf_server_buffer + LABEL "Linux dma-buf Buffer Sharing" + LIBRARIES + EGL::EGL + Libdrm::Libdrm + CODE + " +#include +#include +#include + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + /* BEGIN TEST: */ +#ifdef EGL_LINUX_DMA_BUF_EXT +return 0; +#else +#error Requires EGL_LINUX_DMA_BUF_EXT +return 1; +#endif + /* END TEST: */ + return 0; +} +") + +# vulkan-server-buffer +qt_config_compile_test(vulkan_server_buffer + LABEL "Vulkan Buffer Sharing" + LIBRARIES + Wayland::Client + CODE + "#define VK_USE_PLATFORM_WAYLAND_KHR 1 +#include + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + /* BEGIN TEST: */ +VkExportMemoryAllocateInfoKHR exportAllocInfo = {}; +exportAllocInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR; +exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR; +return 0; + /* END TEST: */ + return 0; +} +") + +# egl_1_5-wayland +qt_config_compile_test(egl_1_5_wayland + LABEL "EGL 1.5 with Wayland Platform" + LIBRARIES + EGL::EGL + Wayland::Client + CODE + " +#include +#include +#include + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + /* BEGIN TEST: */ +eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_EXT, (struct wl_display *)(nullptr), nullptr); + /* END TEST: */ + return 0; +} +") #### Features @@ -1344,6 +1496,81 @@ qt_feature("wayland" PUBLIC LABEL "Wayland" CONDITION TARGET Wayland::Client ) +qt_feature("waylandscanner" PUBLIC + SECTION "Wayland Scanner tool" + LABEL "Wayland Scanner" + CONDITION TARGET Wayland::Scanner +) + +# qt wayland client +qt_feature("wayland-client" PRIVATE + LABEL "Client" + CONDITION NOT WIN32 AND QT_FEATURE_wayland AND QT_FEATURE_waylandscanner +) +qt_feature("wayland-server" PRIVATE + LABEL "Qt Wayland Compositor" + CONDITION NOT WIN32 AND QT_FEATURE_wayland AND QT_FEATURE_waylandscanner +) +qt_feature("wayland-egl" PRIVATE + LABEL "EGL" + CONDITION (QT_FEATURE_wayland_client OR QT_FEATURE_wayland_server) + AND QT_FEATURE_opengl AND QT_FEATURE_egl + AND (NOT QNX OR QT_FEATURE_egl_extension_platform_wayland) +) +qt_feature("wayland-brcm" PRIVATE + LABEL "Raspberry Pi" + CONDITION (QT_FEATURE_wayland_client OR QT_FEATURE_wayland_server) AND QT_FEATURE_eglfs_brcm +) +qt_feature("wayland-drm-egl-server-buffer" PRIVATE + LABEL "DRM EGL" + CONDITION (QT_FEATURE_wayland_client OR QT_FEATURE_wayland_server) AND QT_FEATURE_opengl + AND QT_FEATURE_egl AND TEST_drm_egl_server + AND (NOT QNX OR QT_FEATURE_egl_extension_platform_wayland) +) +qt_feature("wayland-libhybris-egl-server-buffer" PRIVATE + LABEL "libhybris EGL" + CONDITION (QT_FEATURE_wayland_client OR QT_FEATURE_wayland_server) AND QT_FEATURE_opengl + AND QT_FEATURE_egl AND TEST_libhybris_egl_server +) +qt_feature("wayland-dmabuf-server-buffer" PRIVATE + LABEL "Linux dma-buf server buffer" + CONDITION (QT_FEATURE_wayland_client OR QT_FEATURE_wayland_server) AND QT_FEATURE_opengl + AND QT_FEATURE_egl AND TEST_dmabuf_server_buffer +) +qt_feature("wayland-shm-emulation-server-buffer" PRIVATE + LABEL "Shm emulation server buffer" + CONDITION (QT_FEATURE_wayland_client OR QT_FEATURE_wayland_server) AND QT_FEATURE_opengl +) +qt_feature("wayland-vulkan-server-buffer" PRIVATE + LABEL "Vulkan-based server buffer" + CONDITION (QT_FEATURE_wayland_client OR QT_FEATURE_wayland_server) AND QT_FEATURE_vulkan + AND QT_FEATURE_opengl AND QT_FEATURE_egl AND TEST_vulkan_server_buffer +) +qt_feature("wayland-datadevice" PRIVATE + CONDITION QT_FEATURE_draganddrop OR QT_FEATURE_clipboard +) +qt_feature("wayland-client-primary-selection" PRIVATE + LABEL "primary-selection clipboard" + CONDITION QT_FEATURE_clipboard +) +qt_feature("wayland-client-fullscreen-shell-v1" PRIVATE + LABEL "fullscreen-shell-v1" + CONDITION QT_FEATURE_wayland_client +) +qt_feature("wayland-client-wl-shell" PRIVATE + LABEL "wl-shell (deprecated)" + CONDITION QT_FEATURE_wayland_client +) +qt_feature("wayland-client-xdg-shell" PRIVATE + LABEL "xdg-shell" + CONDITION QT_FEATURE_wayland_client +) +qt_feature("egl-extension-platform-wayland" PRIVATE + LABEL "EGL wayland platform extension" + CONDITION QT_FEATURE_wayland_client AND QT_FEATURE_opengl AND QT_FEATURE_egl + AND TEST_egl_1_5_wayland +) + qt_configure_add_summary_section(NAME "Qt Gui") qt_configure_add_summary_entry(ARGS "accessibility") @@ -1449,6 +1676,22 @@ qt_configure_add_summary_entry(ARGS "directwrite") qt_configure_add_summary_entry(ARGS "directwrite3") qt_configure_add_summary_entry(ARGS "directwritecolrv1") qt_configure_end_summary_section() # end of "Windows" section +qt_configure_add_summary_section(NAME "Wayland") +qt_configure_add_summary_entry(ARGS "wayland-client") +qt_configure_add_summary_section(NAME "Hardware Integrations") +qt_configure_add_summary_entry(ARGS "wayland-egl") +qt_configure_add_summary_entry(ARGS "wayland-brcm") +qt_configure_add_summary_entry(ARGS "wayland-drm-egl-server-buffer") +qt_configure_add_summary_entry(ARGS "wayland-libhybris-egl-server-buffer") +qt_configure_add_summary_entry(ARGS "wayland-dmabuf-server-buffer") +qt_configure_add_summary_entry(ARGS "wayland-shm-emulation-server-buffer") +qt_configure_add_summary_entry(ARGS "wayland-vulkan-server-buffer") +qt_configure_end_summary_section() # end of "Qt Wayland Drivers" section +qt_configure_add_summary_section(NAME "Shell Integrations") +qt_configure_add_summary_entry(ARGS "wayland-client-xdg-shell") +qt_configure_add_summary_entry(ARGS "wayland-client-wl-shell") +qt_configure_end_summary_section() # end of "Shell Integrations" section +qt_configure_end_summary_section() # end of "Wayland" section qt_configure_end_summary_section() # end of "QPA backends" section qt_configure_add_report_entry( TYPE NOTE @@ -1485,3 +1728,26 @@ qt_configure_add_report_entry( MESSAGE "The desktopservices feature is required on macOS, iOS, and Android and cannot be disabled." CONDITION (APPLE OR ANDROID) AND NOT QT_FEATURE_desktopservices ) +qt_configure_add_report_entry( + TYPE NOTE + MESSAGE "Qt Gui has been built without 'qtwaylandscanner' feature. This feature is required for building Qt Wayland Client." + CONDITION NOT QT_FEATURE_waylandscanner AND QT_FEATURE_wayland_client +) +qt_configure_add_report_entry( + TYPE NOTE + MESSAGE "Qt Gui has been built without 'wayland' feature. This feature is required for building Qt Wayland Client." + CONDITION NOT QT_FEATURE_wayland AND QT_FEATURE_wayland_client +) + +#### Inputs + + + +#### Libraries + + +#### Tests + + +#### Features + diff --git a/src/plugins/platforms/CMakeLists.txt b/src/plugins/platforms/CMakeLists.txt index 69071a22c24..f30c27c24be 100644 --- a/src/plugins/platforms/CMakeLists.txt +++ b/src/plugins/platforms/CMakeLists.txt @@ -56,3 +56,7 @@ endif() if(QT_FEATURE_vkkhrdisplay) add_subdirectory(vkkhrdisplay) endif() +if(QT_FEATURE_wayland) + add_subdirectory(wayland) +endif() + diff --git a/src/plugins/platforms/wayland/CMakeLists.txt b/src/plugins/platforms/wayland/CMakeLists.txt new file mode 100644 index 00000000000..c5d76c12c43 --- /dev/null +++ b/src/plugins/platforms/wayland/CMakeLists.txt @@ -0,0 +1,235 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from src.pro. +# special case begin + +set(QT_SBOM_DEFAULT_QT_LICENSE_ID_LIBRARIES "QT_COMMERCIAL_OR_LGPL3") + +if (NOT QT_FEATURE_waylandscanner OR NOT QT_FEATURE_wayland) + return() +endif() + +# See globalprivate/README for a description of the following module. +qt_internal_add_module(WaylandGlobalPrivate + INTERNAL_MODULE + HEADER_MODULE + NO_GENERATE_CPP_EXPORTS +) + +# Work around 115101. +# If nothing depends on the WaylandGlobalPrivate target it doesn't run custom commands that the +# target depends on. WaylandGlobalPrivate_ensure_sync_headers makes sure that 'all' depends on +# WaylandGlobalPrivate_sync_headers. +# TODO: This needs to be removed once the fix for QTBUG-115101 is merged in qtbase. +add_custom_target(WaylandGlobalPrivate_ensure_sync_headers ALL) +add_dependencies(WaylandGlobalPrivate_ensure_sync_headers WaylandGlobalPrivate_sync_headers) + +# special case begin +# TODO: Ideally these macros would be part of the qtwaylandscanner tool, and not the compositor/client +include(../../../../src/tools/qtwaylandscanner/Qt6WaylandClientMacros.cmake) +# special case end + +# special case end + +# Generated from client.pro. + +##################################################################### +## WaylandClient Module: +##################################################################### + +qt_internal_add_module(WaylandClient + PLUGIN_TYPES wayland-graphics-integration-client wayland-inputdevice-integration wayland-decoration-client wayland-shell-integration + SOURCES + shared/qwaylandinputmethodeventbuilder.cpp shared/qwaylandinputmethodeventbuilder_p.h + shared/qwaylandmimehelper.cpp shared/qwaylandmimehelper_p.h + shared/qwaylandsharedmemoryformathelper_p.h + global/qwaylandclientextension.cpp global/qwaylandclientextension.h global/qwaylandclientextension_p.h + hardwareintegration/qwaylandclientbufferintegration.cpp hardwareintegration/qwaylandclientbufferintegration_p.h + hardwareintegration/qwaylandclientbufferintegrationfactory.cpp hardwareintegration/qwaylandclientbufferintegrationfactory_p.h + hardwareintegration/qwaylandclientbufferintegrationplugin.cpp hardwareintegration/qwaylandclientbufferintegrationplugin_p.h + hardwareintegration/qwaylandhardwareintegration.cpp hardwareintegration/qwaylandhardwareintegration_p.h + hardwareintegration/qwaylandserverbufferintegration.cpp hardwareintegration/qwaylandserverbufferintegration_p.h + hardwareintegration/qwaylandserverbufferintegrationfactory.cpp hardwareintegration/qwaylandserverbufferintegrationfactory_p.h + hardwareintegration/qwaylandserverbufferintegrationplugin.cpp hardwareintegration/qwaylandserverbufferintegrationplugin_p.h + inputdeviceintegration/qwaylandinputdeviceintegration_p.h + inputdeviceintegration/qwaylandinputdeviceintegrationfactory.cpp inputdeviceintegration/qwaylandinputdeviceintegrationfactory_p.h + inputdeviceintegration/qwaylandinputdeviceintegrationplugin.cpp inputdeviceintegration/qwaylandinputdeviceintegrationplugin_p.h + qtwaylandclientglobal.h qtwaylandclientglobal_p.h + qwaylandabstractdecoration.cpp qwaylandabstractdecoration_p.h + qwaylandappmenu.cpp qwaylandappmenu_p.h + qwaylandbuffer.cpp qwaylandbuffer_p.h + qwaylandcolormanagement.cpp qwaylandcolormanagement_p.h + qwaylanddatacontrolv1.cpp qwaylanddatacontrolv1_p.h + qwaylanddecorationfactory.cpp qwaylanddecorationfactory_p.h + qwaylanddecorationplugin.cpp qwaylanddecorationplugin_p.h + qwaylanddisplay.cpp qwaylanddisplay_p.h + qwaylandfractionalscale.cpp qwaylandfractionalscale_p.h + qwaylandinputcontext.cpp qwaylandinputcontext_p.h + qwaylandtextinputv1.cpp qwaylandtextinputv1_p.h + qwaylandtextinputv2.cpp qwaylandtextinputv2_p.h + qwaylandtextinputv3.cpp qwaylandtextinputv3_p.h + qwaylandtextinputinterface.cpp qwaylandtextinputinterface_p.h + qwaylandinputdevice.cpp qwaylandinputdevice_p.h + qwaylandinputmethodcontext.cpp qwaylandinputmethodcontext_p.h + qwaylandintegration.cpp qwaylandintegration_p.h + qwaylandnativeinterface.cpp qwaylandnativeinterface_p.h + qwaylandplatformservices.cpp qwaylandplatformservices_p.h + qwaylandpointergestures.cpp qwaylandpointergestures_p.h + qwaylandscreen.cpp qwaylandscreen_p.h + qwaylandshellsurface.cpp qwaylandshellsurface_p.h + qwaylandshm.cpp qwaylandshm_p.h + qwaylandshmbackingstore.cpp qwaylandshmbackingstore_p.h + qwaylandshmwindow.cpp qwaylandshmwindow_p.h + qwaylandsubsurface.cpp qwaylandsubsurface_p.h + qwaylandsurface.cpp qwaylandsurface_p.h + qwaylandviewport.cpp qwaylandviewport_p.h + qwaylandwindow.cpp qwaylandwindow_p.h + qwaylandwindowmanagerintegration.cpp qwaylandwindowmanagerintegration_p.h + shellintegration/qwaylandclientshellapi_p.h + shellintegration/qwaylandshellintegration_p.h shellintegration/qwaylandshellintegration.cpp + shellintegration/qwaylandshellintegrationfactory.cpp shellintegration/qwaylandshellintegrationfactory_p.h + shellintegration/qwaylandshellintegrationplugin.cpp shellintegration/qwaylandshellintegrationplugin_p.h + INCLUDE_DIRECTORIES + shared + global + hardwareintegration + inputdeviceintegration + shellintegration + LIBRARIES + Qt::CorePrivate + Qt::GuiPrivate + Qt::WaylandGlobalPrivate + PUBLIC_LIBRARIES + Qt::Core + Qt::Gui + Wayland::Client + PRIVATE_MODULE_INTERFACE + Qt::CorePrivate + Qt::GuiPrivate + Qt::WaylandGlobalPrivate + PRIVATE_HEADER_FILTERS + "^qwayland-.*\.h|^wayland-.*-protocol\.h" + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 + ATTRIBUTION_FILE_DIR_PATHS + ../../../3rdparty/wayland/protocols/pointer-gestures + ../../../3rdparty/wayland/protocols/tablet + ../../../3rdparty/wayland/protocols/text-input/v1 + ../../../3rdparty/wayland/protocols/text-input/v2 + ../../../3rdparty/wayland/protocols/text-input/v3 + ../../../3rdparty/wayland/protocols/wayland + ../../../3rdparty/wayland/protocols/wp-primary-selection + ../../../3rdparty/wayland/protocols/xdg-output + ../../../3rdparty/wayland/protocols/fractional-scale + ../../../3rdparty/wayland/protocols/viewporter + ../../../3rdparty/wayland/protocols/xdg-shell + ../../../3rdparty/wayland/protocols/wlr-data-control +) + +qt_internal_add_plugin(QWaylandIntegrationPlugin + OUTPUT_NAME qwayland + PLUGIN_TYPE platforms + DEFAULT_IF "wayland" IN_LIST QT_QPA_PLATFORMS + SOURCES + main.cpp + LIBRARIES + Qt::Core + Qt::Gui + Qt::WaylandClientPrivate + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 +) + +qt6_generate_wayland_protocol_client_sources(WaylandClient + PRIVATE_CODE + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/appmenu/appmenu.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/cursor-shape/cursor-shape-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/pointer-gestures/pointer-gestures-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/tablet/tablet-unstable-v2.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/text-input/v1/text-input-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/text-input/v2/text-input-unstable-v2.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/text-input/v3/text-input-unstable-v3.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/wayland/wayland.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/wp-primary-selection/wp-primary-selection-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/xdg-output/xdg-output-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/fractional-scale/fractional-scale-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/viewporter/viewporter.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/wlr-data-control/wlr-data-control-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/xdg-shell/xdg-shell.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/xdg-system-bell/xdg-system-bell-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/xdg-toplevel-drag/xdg-toplevel-drag-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/extensions/qt-text-input-method-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/extensions/qt-windowmanager.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/extensions/hardware-integration.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/extensions/server-buffer-extension.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/color-management/xx-color-management-v4.xml +) + +#### Keys ignored in scope 1:.:.:client.pro:: +# MODULE = "waylandclient" +# QMAKE_CXXFLAGS_WARN_ON = "--Wcast-qual" + +## Scopes: +##################################################################### + +# special case begin +# +# Do not explicitly add XKB::XKB. This is already done by Qt::GuiPrivate. +# +# qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_xkbcommon +# PUBLIC_LIBRARIES +# XKB::XKB +# ) +# special case end + +qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_tabletevent + SOURCES + qwaylandtabletv2.cpp qwaylandtabletv2_p.h +) + +qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_clipboard + SOURCES + qwaylandclipboard.cpp qwaylandclipboard_p.h +) + +qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_vulkan + SOURCES + qwaylandvulkaninstance.cpp qwaylandvulkaninstance_p.h + qwaylandvulkanwindow.cpp qwaylandvulkanwindow_p.h +) + +qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_cursor + SOURCES + qwaylandcursor.cpp qwaylandcursor_p.h + PUBLIC_LIBRARIES + Wayland::Cursor +) + +qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_wayland_datadevice + SOURCES + qwaylanddatadevice.cpp qwaylanddatadevice_p.h + qwaylanddatadevicemanager.cpp qwaylanddatadevicemanager_p.h + qwaylanddataoffer.cpp qwaylanddataoffer_p.h + qwaylanddatasource.cpp qwaylanddatasource_p.h +) + +qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_wayland_client_primary_selection + SOURCES + qwaylandprimaryselectionv1.cpp qwaylandprimaryselectionv1_p.h +) + +qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_draganddrop + SOURCES + qwaylanddnd.cpp qwaylanddnd_p.h +) + +qt_internal_add_docs(WaylandClient + doc/qtwaylandclient.qdocconf +) + +qt_record_extra_qt_main_tools_package_dependency(WaylandClient WaylandScannerTools "${PROJECT_VERSION}") +qt_internal_register_target_dependencies(WaylandClient PUBLIC Qt6::WaylandGlobalPrivate) + +if (QT_FEATURE_wayland_client) + add_subdirectory(plugins) +endif() diff --git a/src/plugins/platforms/wayland/configure.cmake b/src/plugins/platforms/wayland/configure.cmake new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/plugins/platforms/wayland/doc/qtwaylandclient.qdocconf b/src/plugins/platforms/wayland/doc/qtwaylandclient.qdocconf new file mode 100644 index 00000000000..c06be00479a --- /dev/null +++ b/src/plugins/platforms/wayland/doc/qtwaylandclient.qdocconf @@ -0,0 +1,37 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) +include($QT_INSTALL_DOCS/config/exampleurl-qtwayland.qdocconf) + +project = QtWaylandClient +description = Qt Wayland Client Reference Documentation +version = $QT_VERSION + +qhp.projects = QtWaylandClient + +qhp.QtWaylandClient.file = qtwaylandclient.qhp +qhp.QtWaylandClient.namespace = org.qt-project.QtWaylandClient.$QT_VERSION_TAG +qhp.QtWaylandClient.virtualFolder = QtWaylandClient +qhp.QtWaylandClient.indexTitle = Qt Wayland Client +qhp.QtWaylandClient.indexRoot = + +qhp.QtWaylandClient.subprojects = cmakecommand + +qhp.QtWaylandClient.subprojects.cmakecommand.title = qt_generate_wayland_protocol_client_sources +qhp.QtWaylandClient.subprojects.cmakecommand.indexTitle = qt_generate_wayland_protocol_client_sources +qhp.QtWaylandClient.subprojects.cmakecommand.selectors = group:cmakecommand + +depends += qtcore \ + qtqml \ + qtquick \ + qtdoc \ + qtcmake \ + qtwaylandcompositor + +headerdirs += \ + ../ +sourcedirs += \ + ../ + +navigation.landingpage = "Qt Wayland Client" + +# Enforce zero documentation warnings +warninglimit = 0 diff --git a/src/plugins/platforms/wayland/doc/src/cmake/qt_generate_wayland_protocol_client_sources.qdoc b/src/plugins/platforms/wayland/doc/src/cmake/qt_generate_wayland_protocol_client_sources.qdoc new file mode 100644 index 00000000000..18621b17e0f --- /dev/null +++ b/src/plugins/platforms/wayland/doc/src/cmake/qt_generate_wayland_protocol_client_sources.qdoc @@ -0,0 +1,51 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\page qt-generate-wayland-protocol-client-sources.html +\ingroup cmake-commands-qtwaylandclient + +\title qt_generate_wayland_protocol_client_sources +\keyword qt6_generate_wayland_protocol_client_sources + +\summary {Generates client-side C++ bindings for a Wayland protocol .XML file} + +The command is defined in the \c WaylandClient component of the \c Qt6 package, which +can be loaded like so: + +\badcode +find_package(Qt6 REQUIRED COMPONENTS WaylandClient) +\endcode + +\cmakecommandsince 6.0 + +\section1 Synopsis + +\badcode +qt_generate_wayland_protocol_client_sources(target + [PUBLIC_CODE | PRIVATE_CODE] + FILES file1.xml [file2.xml ...]) +\endcode + +\versionlessCMakeCommandsNote qt6_generate_wayland_protocol_client_sources() + +\section1 Description + +qt_generate_wayland_protocol_client_sources() creates the build steps to run \c{wayland-scanner} and +\c{qtwaylandscanner} on one or more Wayland protocol files. The tools will in turn generate binding +code in C and C++ for implementing the protocols, and the resulting files will be built as part +of the \c target. + +The options \c{PUBLIC_CODE} and \c{PRIVATE_CODE} (added in Qt 6.8) correspond to the \c{public-code} +and \c{private-code} options of \c{wayland-scanner}. \c{PUBLIC_CODE} will cause the symbols in the +code that is generated by \c{wayland-scanner} to be exported. For backwards compatibility \c{PUBLIC_CODE} is the +default but generally \c{PRIVATE_CODE} is strongly recommended. + +qt_generate_wayland_protocol_client_sources() will trigger generation of the files needed to +implement the client side of the protocol. \l{qt_generate_wayland_protocol_server_sources}{qt_generate_wayland_protocol_server_sources()} +is the equivalent function for the compositor. + +See the \l{Custom Shell} or \l{Custom Extension} examples for a demonstration of how to use these +functions. +*/ + diff --git a/src/plugins/platforms/wayland/doc/src/qtwaylandclient-overview.qdoc b/src/plugins/platforms/wayland/doc/src/qtwaylandclient-overview.qdoc new file mode 100644 index 00000000000..a6f5ce56a87 --- /dev/null +++ b/src/plugins/platforms/wayland/doc/src/qtwaylandclient-overview.qdoc @@ -0,0 +1,36 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page qtwaylandclient-index.html + \title Qt Wayland Client + \brief Library to enable connecting to a Wayland compositor as a client + + The Qt Wayland Client library provides the necessary functions for an application to act + as a \l {https://wayland.freedesktop.org/}{Wayland} client and connect to a Wayland compositor. + For most use cases, the library is used automatically through the Wayland QPA plugin, and there is + no need for the application itself to use any functions from the library. + + However, when paired with \l{Qt Wayland Compositor}, the cmake function + \l{qt_generate_wayland_protocol_client_sources}{qt_generate_wayland_protocol_client_sources()} + can be used to create custom protocol extensions. + + \section1 Licenses and Attributions + + Qt Wayland Compositor and the Qt Wayland integration plugin + are available under commercial licenses from \l{The Qt Company}. + + In addition, Qt Wayland Compositor is available under the + \l{GNU General Public License, version 3}, while + the Qt Wayland integration plugin 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. + + Qt Wayland Compositor and the Qt Wayland integration plugin + use protocol definitions under following permissive licenses: + + \generatelist{groupsbymodule attributions-qtwaylandcompositor} + +*/ diff --git a/src/plugins/platforms/wayland/global/qwaylandclientextension.cpp b/src/plugins/platforms/wayland/global/qwaylandclientextension.cpp new file mode 100644 index 00000000000..92c746d3541 --- /dev/null +++ b/src/plugins/platforms/wayland/global/qwaylandclientextension.cpp @@ -0,0 +1,109 @@ +// Copyright (C) 2017 Erik Larsson. +// Copyright (C) 2021 David Redondo +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandclientextension.h" +#include "qwaylandclientextension_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +using RegistryGlobal = QtWaylandClient::QWaylandDisplay::RegistryGlobal; +using namespace Qt::StringLiterals; + +QWaylandClientExtensionPrivate::QWaylandClientExtensionPrivate() +{ + // Keep the possibility to use a custom waylandIntegration as a plugin, + // but also add the possibility to run it as a QML component. + waylandIntegration = QtWaylandClient::QWaylandIntegration::instance(); + if (!waylandIntegration) + waylandIntegration = new QtWaylandClient::QWaylandIntegration("wayland"_L1); +} + +void QWaylandClientExtensionPrivate::globalAdded(const RegistryGlobal &global) +{ + Q_Q(QWaylandClientExtension); + if (!active && global.interface == QLatin1String(q->extensionInterface()->name)) { + q->bind(global.registry, global.id, global.version); + active = true; + emit q->activeChanged(); + } +} + +void QWaylandClientExtensionPrivate::globalRemoved(const RegistryGlobal &global) +{ + Q_Q(QWaylandClientExtension); + if (active && global.interface == QLatin1String(q->extensionInterface()->name)) { + active = false; + emit q->activeChanged(); + } +} + +void QWaylandClientExtension::initialize() +{ + Q_D(QWaylandClientExtension); + if (d->active) { + return; + } + const QtWaylandClient::QWaylandDisplay *display = d->waylandIntegration->display(); + const auto globals = display->globals(); + auto global = + std::find_if(globals.cbegin(), globals.cend(), [this](const RegistryGlobal &global) { + return global.interface == QLatin1String(extensionInterface()->name); + }); + if (global != globals.cend()) { + bind(global->registry, global->id, global->version); + d->active = true; + emit activeChanged(); + } +} + +QWaylandClientExtension::QWaylandClientExtension(const int ver) + : QObject(*new QWaylandClientExtensionPrivate()) +{ + Q_D(QWaylandClientExtension); + d->version = ver; + auto display = d->waylandIntegration->display(); + QObjectPrivate::connect(display, &QtWaylandClient::QWaylandDisplay::globalAdded, d, + &QWaylandClientExtensionPrivate::globalAdded); + QObjectPrivate::connect(display, &QtWaylandClient::QWaylandDisplay::globalRemoved, d, + &QWaylandClientExtensionPrivate::globalRemoved); + // This function uses virtual functions and we don't want it to be called from the constructor. + QMetaObject::invokeMethod(this, "initialize", Qt::QueuedConnection); +} + +QWaylandClientExtension::~QWaylandClientExtension() +{ +} + +QtWaylandClient::QWaylandIntegration *QWaylandClientExtension::integration() const +{ + Q_D(const QWaylandClientExtension); + return d->waylandIntegration; +} + +int QWaylandClientExtension::version() const +{ + Q_D(const QWaylandClientExtension); + return d->version; +} + +void QWaylandClientExtension::setVersion(const int ver) +{ + Q_D(QWaylandClientExtension); + if (d->version != ver) { + d->version = ver; + emit versionChanged(); + } +} + +bool QWaylandClientExtension::isActive() const +{ + Q_D(const QWaylandClientExtension); + return d->active; +} + +QT_END_NAMESPACE + +#include "moc_qwaylandclientextension.cpp" diff --git a/src/plugins/platforms/wayland/global/qwaylandclientextension.h b/src/plugins/platforms/wayland/global/qwaylandclientextension.h new file mode 100644 index 00000000000..6079e990168 --- /dev/null +++ b/src/plugins/platforms/wayland/global/qwaylandclientextension.h @@ -0,0 +1,100 @@ +// Copyright (C) 2017 Erik Larsson. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDCLIENTEXTENSION_H +#define QWAYLANDCLIENTEXTENSION_H + +#include +#include + +struct wl_interface; +struct wl_registry; + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { +class QWaylandIntegration; +} + +class QWaylandClientExtensionPrivate; +class QWaylandClientExtensionTemplatePrivate; + +class Q_WAYLANDCLIENT_EXPORT QWaylandClientExtension : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QWaylandClientExtension) + Q_PROPERTY(int protocolVersion READ version NOTIFY versionChanged) + Q_PROPERTY(bool active READ isActive NOTIFY activeChanged) +public: + QWaylandClientExtension(const int version); + ~QWaylandClientExtension(); + + QtWaylandClient::QWaylandIntegration *integration() const; + int version() const; + bool isActive() const; + + virtual const struct wl_interface *extensionInterface() const = 0; + virtual void bind(struct ::wl_registry *registry, int id, int version) = 0; +protected: + void setVersion(const int version); +Q_SIGNALS: + void versionChanged(); + void activeChanged(); + +protected Q_SLOTS: + void initialize(); +}; + + +template +class Q_WAYLANDCLIENT_EXPORT QWaylandClientExtensionTemplate : public QWaylandClientExtension +{ + Q_DECLARE_PRIVATE(QWaylandClientExtensionTemplate) + +public: + QWaylandClientExtensionTemplate(const int ver) : QWaylandClientExtension(ver) + { + if constexpr (destruct != nullptr) { + connect(this, &QWaylandClientExtensionTemplate::activeChanged, this, [this] { + if (!isActive()) { + std::invoke(destruct, static_cast(this)); + } + }); + } + } + + ~QWaylandClientExtensionTemplate() + { + if constexpr (destruct != nullptr) { + if (isActive()) { + std::invoke(destruct, static_cast(this)); + } + } + } + + const struct wl_interface *extensionInterface() const override + { + return T::interface(); + } + + void bind(struct ::wl_registry *registry, int id, int ver) override + { + T* instance = static_cast(this); + // Make sure lowest version is used of the supplied version from the + // developer and the version specified in the protocol and also the + // compositor version. + if (this->version() > T::interface()->version) { + qWarning("Supplied protocol version to QWaylandClientExtensionTemplate is higher " + "than the version of the protocol, using protocol version instead.\n" + " interface.name: %s", + T::interface()->name); + } + int minVersion = qMin(ver, qMin(T::interface()->version, this->version())); + setVersion(minVersion); + instance->init(registry, id, minVersion); + } +}; + +QT_END_NAMESPACE + +#endif // QWAYLANDCLIENTEXTENSION_H diff --git a/src/plugins/platforms/wayland/global/qwaylandclientextension_p.h b/src/plugins/platforms/wayland/global/qwaylandclientextension_p.h new file mode 100644 index 00000000000..b3ef87a5d8e --- /dev/null +++ b/src/plugins/platforms/wayland/global/qwaylandclientextension_p.h @@ -0,0 +1,48 @@ +// Copyright (C) 2017 Erik Larsson. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDCLIENTEXTENSION_P_H +#define QWAYLANDCLIENTEXTENSION_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 + +QT_BEGIN_NAMESPACE + +class Q_WAYLANDCLIENT_EXPORT QWaylandClientExtensionPrivate : public QObjectPrivate +{ +public: + Q_DECLARE_PUBLIC(QWaylandClientExtension) + QWaylandClientExtensionPrivate(); + + void globalAdded(const QtWaylandClient::QWaylandDisplay::RegistryGlobal &global); + void globalRemoved(const QtWaylandClient::QWaylandDisplay::RegistryGlobal &global); + + QtWaylandClient::QWaylandIntegration *waylandIntegration = nullptr; + int version = -1; + bool active = false; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandClientExtensionTemplatePrivate : public QWaylandClientExtensionPrivate +{ +public: + QWaylandClientExtensionTemplatePrivate() + { } +}; + +QT_END_NAMESPACE + +#endif /*QWAYLANDCLIENTEXTENSION_P_H*/ diff --git a/src/plugins/platforms/wayland/globalprivate/README b/src/plugins/platforms/wayland/globalprivate/README new file mode 100644 index 00000000000..9b045d4d2e0 --- /dev/null +++ b/src/plugins/platforms/wayland/globalprivate/README @@ -0,0 +1,9 @@ +The WaylandGlobalPrivate module is a module that contains configure +features, shared by the modules of the qtwayland repository. + +The actual definition of this module is in src/CMakeLists.txt, +because we need to have the qt_find_package calls in a directory scope +above src/client and src/compositor. + +This file here mostly exists to make the otherwise empty src/global +directory known to git. diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegration.cpp b/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegration.cpp new file mode 100644 index 00000000000..b521521b887 --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegration.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2016 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 + +#include "qwaylandclientbufferintegration_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandClientBufferIntegration::QWaylandClientBufferIntegration() +{ + +} + +QWaylandClientBufferIntegration::~QWaylandClientBufferIntegration() +{ + +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegration_p.h b/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegration_p.h new file mode 100644 index 00000000000..e8b78c52b67 --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegration_p.h @@ -0,0 +1,61 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDCLIENTBUFFERINTEGRATION_H +#define QWAYLANDCLIENTBUFFERINTEGRATION_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 + +QT_BEGIN_NAMESPACE + +class QWindow; +class QPlatformOpenGLContext; +class QSurfaceFormat; + +namespace QtWaylandClient { + +class QWaylandWindow; +class QWaylandDisplay; + +class Q_WAYLANDCLIENT_EXPORT QWaylandClientBufferIntegration +{ +public: + QWaylandClientBufferIntegration(); + virtual ~QWaylandClientBufferIntegration(); + + virtual void initialize(QWaylandDisplay *display) = 0; + + virtual bool isValid() const { return true; } + + virtual bool supportsThreadedOpenGL() const { return false; } + virtual bool supportsWindowDecoration() const { return false; } + + virtual QWaylandWindow *createEglWindow(QWindow *window) = 0; + virtual QPlatformOpenGLContext *createPlatformOpenGLContext(const QSurfaceFormat &glFormat, QPlatformOpenGLContext *share) const = 0; + + enum NativeResource { + EglDisplay, + EglConfig, + EglContext + }; + virtual void *nativeResource(NativeResource /*resource*/) { return nullptr; } + virtual void *nativeResourceForContext(NativeResource /*resource*/, QPlatformOpenGLContext */*context*/) { return nullptr; } +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDCLIENTBUFFERINTEGRATION_H diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegrationfactory.cpp b/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegrationfactory.cpp new file mode 100644 index 00000000000..65c7a450dbf --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegrationfactory.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2016 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 + +#include "qwaylandclientbufferintegrationfactory_p.h" +#include "qwaylandclientbufferintegrationplugin_p.h" +#include "qwaylandclientbufferintegration_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, qwcbifLoader, + (QWaylandClientBufferIntegrationFactoryInterface_iid, QLatin1String("/wayland-graphics-integration-client"), Qt::CaseInsensitive)) + +QStringList QWaylandClientBufferIntegrationFactory::keys() +{ + return qwcbifLoader->keyMap().values(); +} + +QWaylandClientBufferIntegration *QWaylandClientBufferIntegrationFactory::create(const QString &name, const QStringList &args) +{ + return qLoadPlugin(qwcbifLoader(), name, args); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegrationfactory_p.h b/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegrationfactory_p.h new file mode 100644 index 00000000000..2344df59c73 --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegrationfactory_p.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDCLIENTBUFFERINTEGRATIONFACTORY_H +#define QWAYLANDCLIENTBUFFERINTEGRATIONFACTORY_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandClientBufferIntegration; + +class Q_WAYLANDCLIENT_EXPORT QWaylandClientBufferIntegrationFactory +{ +public: + static QStringList keys(); + static QWaylandClientBufferIntegration *create(const QString &name, const QStringList &args); +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDCLIENTBUFFERINTEGRATIONFACTORY_H diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegrationplugin.cpp b/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegrationplugin.cpp new file mode 100644 index 00000000000..0335a9219d7 --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegrationplugin.cpp @@ -0,0 +1,23 @@ +// Copyright (C) 2016 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 + +#include "qwaylandclientbufferintegrationplugin_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandClientBufferIntegrationPlugin::QWaylandClientBufferIntegrationPlugin(QObject *parent) : + QObject(parent) +{ +} + +QWaylandClientBufferIntegrationPlugin::~QWaylandClientBufferIntegrationPlugin() +{ +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylandclientbufferintegrationplugin_p.cpp" diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegrationplugin_p.h b/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegrationplugin_p.h new file mode 100644 index 00000000000..0cedabff3d3 --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegrationplugin_p.h @@ -0,0 +1,47 @@ +// Copyright (C) 2018 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 + +#ifndef QWAYLANDCLIENTBUFFERINTEGRATIONPLUGIN_H +#define QWAYLANDCLIENTBUFFERINTEGRATIONPLUGIN_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandClientBufferIntegration; + +#define QWaylandClientBufferIntegrationFactoryInterface_iid "org.qt-project.Qt.WaylandClient.QWaylandClientBufferIntegrationFactoryInterface.5.3" + +class Q_WAYLANDCLIENT_EXPORT QWaylandClientBufferIntegrationPlugin : public QObject +{ + Q_OBJECT +public: + explicit QWaylandClientBufferIntegrationPlugin(QObject *parent = nullptr); + ~QWaylandClientBufferIntegrationPlugin() override; + + virtual QWaylandClientBufferIntegration *create(const QString &key, const QStringList ¶mList) = 0; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDCLIENTBUFFERINTEGRATIONPLUGIN_H diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandhardwareintegration.cpp b/src/plugins/platforms/wayland/hardwareintegration/qwaylandhardwareintegration.cpp new file mode 100644 index 00000000000..a8f59a7a571 --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandhardwareintegration.cpp @@ -0,0 +1,38 @@ +// Copyright (C) 2016 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 + +#include "qwaylandhardwareintegration_p.h" + +#include "qwaylanddisplay_p.h" +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandHardwareIntegration::QWaylandHardwareIntegration(struct ::wl_registry *registry, int id) + : qt_hardware_integration(registry, id, 1) +{ +} + +QString QWaylandHardwareIntegration::clientBufferIntegration() +{ + return m_client_buffer; +} + +QString QWaylandHardwareIntegration::serverBufferIntegration() +{ + return m_server_buffer; +} + +void QWaylandHardwareIntegration::hardware_integration_client_backend(const QString &name) +{ + m_client_buffer = name; +} + +void QWaylandHardwareIntegration::hardware_integration_server_backend(const QString &name) +{ + m_server_buffer = name; +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandhardwareintegration_p.h b/src/plugins/platforms/wayland/hardwareintegration/qwaylandhardwareintegration_p.h new file mode 100644 index 00000000000..d82253f9283 --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandhardwareintegration_p.h @@ -0,0 +1,49 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDHARDWAREINTEGRATION_H +#define QWAYLANDHARDWAREINTEGRATION_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class Q_WAYLANDCLIENT_EXPORT QWaylandHardwareIntegration : public QtWayland::qt_hardware_integration +{ +public: + QWaylandHardwareIntegration(struct ::wl_registry *registry, int id); + + QString clientBufferIntegration(); + QString serverBufferIntegration(); + +protected: + void hardware_integration_client_backend(const QString &name) override; + void hardware_integration_server_backend(const QString &name) override; + +private: + QString m_client_buffer; + QString m_server_buffer; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegration.cpp b/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegration.cpp new file mode 100644 index 00000000000..64fd7686be2 --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegration.cpp @@ -0,0 +1,47 @@ +// Copyright (C) 2016 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 + +#include "qwaylandserverbufferintegration_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandServerBuffer::QWaylandServerBuffer() +{ +} + +QWaylandServerBuffer::~QWaylandServerBuffer() +{ +} + +QWaylandServerBuffer::Format QWaylandServerBuffer::format() const +{ + return m_format; +} + +QSize QWaylandServerBuffer::size() const +{ + return m_size; +} + +void QWaylandServerBuffer::setUserData(void *userData) +{ + m_user_data = userData; +} + +void *QWaylandServerBuffer::userData() const +{ + return m_user_data; +} + +QWaylandServerBufferIntegration::QWaylandServerBufferIntegration() +{ +} +QWaylandServerBufferIntegration::~QWaylandServerBufferIntegration() +{ +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegration_p.h b/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegration_p.h new file mode 100644 index 00000000000..92236a7ff6e --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegration_p.h @@ -0,0 +1,76 @@ +// 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 + +#ifndef QWAYLANDSERVERBUFFERINTEGRATION_H +#define QWAYLANDSERVERBUFFERINTEGRATION_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 + +QT_BEGIN_NAMESPACE + +class QOpenGLTexture; + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class Q_WAYLANDCLIENT_EXPORT QWaylandServerBuffer +{ +public: + enum Format { + RGBA32, + A8, + Custom + }; + + QWaylandServerBuffer(); + virtual ~QWaylandServerBuffer(); + + virtual QOpenGLTexture *toOpenGlTexture() = 0; + + Format format() const; + QSize size() const; + + void setUserData(void *userData); + void *userData() const; + +protected: + Format m_format = RGBA32; + QSize m_size; + +private: + void *m_user_data = nullptr; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandServerBufferIntegration +{ +public: + QWaylandServerBufferIntegration(); + virtual ~QWaylandServerBufferIntegration(); + + virtual void initialize(QWaylandDisplay *display) = 0; + + virtual QWaylandServerBuffer *serverBuffer(struct qt_server_buffer *buffer) = 0; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegrationfactory.cpp b/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegrationfactory.cpp new file mode 100644 index 00000000000..e30bb4dc0ca --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegrationfactory.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2016 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 + +#include "qwaylandserverbufferintegrationfactory_p.h" +#include "qwaylandserverbufferintegrationplugin_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, qwsbifLoader, + (QWaylandServerBufferIntegrationFactoryInterface_iid, QLatin1String("/wayland-graphics-integration-client"), Qt::CaseInsensitive)) + +QStringList QWaylandServerBufferIntegrationFactory::keys() +{ + return qwsbifLoader->keyMap().values(); +} + +QWaylandServerBufferIntegration *QWaylandServerBufferIntegrationFactory::create(const QString &name, const QStringList &args) +{ + return qLoadPlugin(qwsbifLoader(), name, args); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegrationfactory_p.h b/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegrationfactory_p.h new file mode 100644 index 00000000000..bce4c45d0a3 --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegrationfactory_p.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDSERVERBUFFERINTEGRATIONFACTORY_H +#define QWAYLANDSERVERBUFFERINTEGRATIONFACTORY_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandServerBufferIntegration; + +class Q_WAYLANDCLIENT_EXPORT QWaylandServerBufferIntegrationFactory +{ +public: + static QStringList keys(); + static QWaylandServerBufferIntegration *create(const QString &name, const QStringList &args); +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDSERVERBUFFERINTEGRATIONFACTORY_H diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegrationplugin.cpp b/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegrationplugin.cpp new file mode 100644 index 00000000000..1a01ca04d5b --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegrationplugin.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2016 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 + +#include "qwaylandserverbufferintegrationplugin_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandServerBufferIntegrationPlugin::QWaylandServerBufferIntegrationPlugin(QObject *parent) + : QObject(parent) +{ +} +QWaylandServerBufferIntegrationPlugin::~QWaylandServerBufferIntegrationPlugin() +{ +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylandserverbufferintegrationplugin_p.cpp" diff --git a/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegrationplugin_p.h b/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegrationplugin_p.h new file mode 100644 index 00000000000..6be8ca8bf51 --- /dev/null +++ b/src/plugins/platforms/wayland/hardwareintegration/qwaylandserverbufferintegrationplugin_p.h @@ -0,0 +1,47 @@ +// Copyright (C) 2018 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 + +#ifndef QWAYLANDSERVERBUFFERINTEGRATIONPLUGIN_H +#define QWAYLANDSERVERBUFFERINTEGRATIONPLUGIN_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandServerBufferIntegration; + +#define QWaylandServerBufferIntegrationFactoryInterface_iid "org.qt-project.Qt.WaylandClient.QWaylandServerBufferIntegrationFactoryInterface.5.3" + +class Q_WAYLANDCLIENT_EXPORT QWaylandServerBufferIntegrationPlugin : public QObject +{ + Q_OBJECT +public: + explicit QWaylandServerBufferIntegrationPlugin(QObject *parent = nullptr); + ~QWaylandServerBufferIntegrationPlugin() override; + + virtual QWaylandServerBufferIntegration *create(const QString &key, const QStringList ¶mList) = 0; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDSERVERBUFFERINTEGRATIONPLUGIN_H diff --git a/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegration_p.h b/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegration_p.h new file mode 100644 index 00000000000..9a6c8df0b48 --- /dev/null +++ b/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegration_p.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 LG Electronics Ltd +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDINPUTDEVICEINTEGRATION_H +#define QWAYLANDINPUTDEVICEINTEGRATION_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; +class QWaylandInputDevice; + +class Q_WAYLANDCLIENT_EXPORT QWaylandInputDeviceIntegration +{ +public: + QWaylandInputDeviceIntegration() {} + virtual ~QWaylandInputDeviceIntegration() {} + + virtual QWaylandInputDevice *createInputDevice(QWaylandDisplay *d, int version, uint32_t id) = 0; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDINPUTDEVICEINTEGRATION_H diff --git a/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegrationfactory.cpp b/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegrationfactory.cpp new file mode 100644 index 00000000000..1c8eb213d14 --- /dev/null +++ b/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegrationfactory.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2016 LG Electronics Ltd +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandinputdeviceintegrationfactory_p.h" +#include "qwaylandinputdeviceintegrationplugin_p.h" +#include "qwaylandinputdeviceintegration_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, qwidfiLoader, + (QWaylandInputDeviceIntegrationFactoryInterface_iid, QLatin1String("/wayland-inputdevice-integration"), Qt::CaseInsensitive)) + +QStringList QWaylandInputDeviceIntegrationFactory::keys() +{ + return qwidfiLoader->keyMap().values(); +} + +QWaylandInputDeviceIntegration *QWaylandInputDeviceIntegrationFactory::create(const QString &name, const QStringList &args) +{ + return qLoadPlugin(qwidfiLoader(), name, args); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegrationfactory_p.h b/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegrationfactory_p.h new file mode 100644 index 00000000000..087c963e186 --- /dev/null +++ b/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegrationfactory_p.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 LG Electronics Ltd +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDINPUTDEVICEINTEGRATIONFACTORY_H +#define QWAYLANDINPUTDEVICEINTEGRATIONFACTORY_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandInputDeviceIntegration; + +class Q_WAYLANDCLIENT_EXPORT QWaylandInputDeviceIntegrationFactory +{ +public: + static QStringList keys(); + static QWaylandInputDeviceIntegration *create(const QString &name, const QStringList &args); +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDINPUTDEVICENTEGRATIONFACTORY_H diff --git a/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegrationplugin.cpp b/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegrationplugin.cpp new file mode 100644 index 00000000000..424b44d3fae --- /dev/null +++ b/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegrationplugin.cpp @@ -0,0 +1,23 @@ +// Copyright (C) 2016 LG Electronics Ltd +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandinputdeviceintegrationplugin_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandInputDeviceIntegrationPlugin::QWaylandInputDeviceIntegrationPlugin(QObject *parent) + : QObject(parent) +{ +} + +QWaylandInputDeviceIntegrationPlugin::~QWaylandInputDeviceIntegrationPlugin() +{ +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylandinputdeviceintegrationplugin_p.cpp" diff --git a/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegrationplugin_p.h b/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegrationplugin_p.h new file mode 100644 index 00000000000..e43ce1bd10c --- /dev/null +++ b/src/plugins/platforms/wayland/inputdeviceintegration/qwaylandinputdeviceintegrationplugin_p.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 LG Electronics Ltd +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDINPUTDEVICEINTEGRATIONPLUGIN_H +#define QWAYLANDINPUTDEVICEINTEGRATIONPLUGIN_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandInputDeviceIntegration; + +#define QWaylandInputDeviceIntegrationFactoryInterface_iid "org.qt-project.Qt.WaylandClient.QWaylandInputDeviceIntegrationFactoryInterface.5.3" + +class Q_WAYLANDCLIENT_EXPORT QWaylandInputDeviceIntegrationPlugin : public QObject +{ + Q_OBJECT +public: + explicit QWaylandInputDeviceIntegrationPlugin(QObject *parent = nullptr); + ~QWaylandInputDeviceIntegrationPlugin() override; + + virtual QWaylandInputDeviceIntegration *create(const QString &key, const QStringList ¶mList) = 0; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDINPUTDEVICEINTEGRATIONPLUGIN_H diff --git a/src/plugins/platforms/wayland/main.cpp b/src/plugins/platforms/wayland/main.cpp new file mode 100644 index 00000000000..1d7edf0ca65 --- /dev/null +++ b/src/plugins/platforms/wayland/main.cpp @@ -0,0 +1,45 @@ +// Copyright (C) 2016 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 + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandIntegrationPlugin : public QPlatformIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "qwayland.json") +public: + QPlatformIntegration *create(const QString&, const QStringList&) override; +}; + +QPlatformIntegration *QWaylandIntegrationPlugin::create(const QString& system, const QStringList& paramList) +{ + Q_UNUSED(paramList) +#if !QT_CONFIG(wayland_egl) + if (system == "wayland-egl") + return nullptr; +#endif +#if !QT_CONFIG(wayland_brcm) + if (system == "wayland-brcm") + return nullptr; +#endif + auto *integration = new QWaylandIntegration(system); + + if (!integration->init()) { + delete integration; + integration = nullptr; + } + + return integration; +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wayland/plugins/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/CMakeLists.txt new file mode 100644 index 00000000000..09852e64c99 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from plugins.pro. + +add_subdirectory(hardwareintegration) +if(TARGET Qt::WaylandClient) + add_subdirectory(decorations) + add_subdirectory(shellintegration) +endif() diff --git a/src/plugins/platforms/wayland/plugins/decorations/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/decorations/CMakeLists.txt new file mode 100644 index 00000000000..092a10fa669 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/decorations/CMakeLists.txt @@ -0,0 +1,5 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from decorations.pro. +add_subdirectory(bradient) diff --git a/src/plugins/platforms/wayland/plugins/decorations/bradient/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/decorations/bradient/CMakeLists.txt new file mode 100644 index 00000000000..bc25aeb2d4d --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/decorations/bradient/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from bradient.pro. + +##################################################################### +## QWaylandBradientDecorationPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(QWaylandBradientDecorationPlugin + OUTPUT_NAME bradient + PLUGIN_TYPE wayland-decoration-client + SOURCES + main.cpp + LIBRARIES + Qt::Core + Qt::Gui + Qt::WaylandClientPrivate + Wayland::Client + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 +) + +#### Keys ignored in scope 1:.:.:bradient.pro:: +# OTHER_FILES = "bradient.json" diff --git a/src/plugins/platforms/wayland/plugins/decorations/bradient/bradient.json b/src/plugins/platforms/wayland/plugins/decorations/bradient/bradient.json new file mode 100644 index 00000000000..e1a5ef24fe8 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/decorations/bradient/bradient.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "bradient" ] +} diff --git a/src/plugins/platforms/wayland/plugins/decorations/bradient/main.cpp b/src/plugins/platforms/wayland/plugins/decorations/bradient/main.cpp new file mode 100644 index 00000000000..32f2d8db818 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/decorations/bradient/main.cpp @@ -0,0 +1,432 @@ +// Copyright (C) 2016 Robin Burchell +// Copyright (C) 2016 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 + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +#define BUTTON_SPACING 5 +#define BUTTON_WIDTH 18 +#define BUTTONS_RIGHT_MARGIN 8 + +enum Button +{ + None, + Close, + Maximize, + Minimize +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandBradientDecoration : public QWaylandAbstractDecoration +{ +public: + QWaylandBradientDecoration(); +protected: + QMargins margins(MarginsType marginsType = Full) const override; + void paint(QPaintDevice *device) override; + bool handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global,Qt::MouseButtons b,Qt::KeyboardModifiers mods) override; + bool handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, QEventPoint::State state, Qt::KeyboardModifiers mods) override; +private: + enum class PointerType { + Mouse, + Touch + }; + + void processPointerTop(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods, PointerType type); + void processPointerBottom(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods, PointerType type); + void processPointerLeft(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods, PointerType type); + void processPointerRight(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b,Qt::KeyboardModifiers mods, PointerType type); + bool clickButton(Qt::MouseButtons b, Button btn); + + QRectF closeButtonRect() const; + QRectF maximizeButtonRect() const; + QRectF minimizeButtonRect() const; + + QStaticText m_windowTitle; + Button m_clicking = None; +}; + + + +QWaylandBradientDecoration::QWaylandBradientDecoration() +{ + QTextOption option(Qt::AlignHCenter | Qt::AlignVCenter); + option.setWrapMode(QTextOption::NoWrap); + m_windowTitle.setTextOption(option); + m_windowTitle.setTextFormat(Qt::PlainText); +} + +QRectF QWaylandBradientDecoration::closeButtonRect() const +{ + const int windowRight = waylandWindow()->surfaceSize().width() - margins(ShadowsOnly).right(); + return QRectF(windowRight - BUTTON_WIDTH - BUTTON_SPACING * 0 - BUTTONS_RIGHT_MARGIN, + (margins().top() - BUTTON_WIDTH) / 2, BUTTON_WIDTH, BUTTON_WIDTH); +} + +QRectF QWaylandBradientDecoration::maximizeButtonRect() const +{ + const int windowRight = waylandWindow()->surfaceSize().width() - margins(ShadowsOnly).right(); + return QRectF(windowRight - BUTTON_WIDTH * 2 - BUTTON_SPACING * 1 - BUTTONS_RIGHT_MARGIN, + (margins().top() - BUTTON_WIDTH) / 2, BUTTON_WIDTH, BUTTON_WIDTH); +} + +QRectF QWaylandBradientDecoration::minimizeButtonRect() const +{ + const int windowRight = waylandWindow()->surfaceSize().width() - margins(ShadowsOnly).right(); + return QRectF(windowRight - BUTTON_WIDTH * 3 - BUTTON_SPACING * 2 - BUTTONS_RIGHT_MARGIN, + (margins().top() - BUTTON_WIDTH) / 2, BUTTON_WIDTH, BUTTON_WIDTH); +} + +QMargins QWaylandBradientDecoration::margins(MarginsType marginsType) const +{ + if (marginsType == ShadowsOnly) + return QMargins(); + + return QMargins(3, 30, 3, 3); +} + +void QWaylandBradientDecoration::paint(QPaintDevice *device) +{ + bool active = window()->handle()->isActive(); + QRect wg = QRect(QPoint(), waylandWindow()->surfaceSize()).marginsRemoved(margins(ShadowsOnly)); + QRect cg = wg.marginsRemoved(margins(ShadowsExcluded)); + QRect clips[] = + { + QRect(wg.left(), wg.top(), wg.width(), margins(ShadowsExcluded).top()), + QRect(wg.left(), cg.bottom() + 1, wg.width(), margins(ShadowsExcluded).bottom()), + QRect(wg.left(), cg.top(), margins(ShadowsExcluded).left(), cg.height()), + QRect(cg.right() + 1, cg.top(), margins(ShadowsExcluded).right(), cg.height()) + }; + + QRect top = clips[0]; + + QPalette palette; + const QColor foregroundColor = palette.color(QPalette::Active, QPalette::WindowText); + const QColor backgroundColor = palette.color(QPalette::Active, QPalette::Window); + const QColor foregroundInactiveColor = palette.color(QPalette::Disabled, QPalette::WindowText); + + QPainter p(device); + p.setRenderHint(QPainter::Antialiasing); + + // Title bar + QPainterPath roundedRect; + roundedRect.addRoundedRect(wg, 3, 3); + for (int i = 0; i < 4; ++i) { + p.save(); + p.setClipRect(clips[i]); + p.fillPath(roundedRect, backgroundColor); + p.restore(); + } + + // Window icon + QIcon icon = waylandWindow()->windowIcon(); + if (!icon.isNull()) { + QRectF iconRect(0, 0, 22, 22); + iconRect.adjust(margins().left() + BUTTON_SPACING, 4, + margins().left() + BUTTON_SPACING, 4), + icon.paint(&p, iconRect.toRect()); + } + + // Window title + QString windowTitleText = waylandWindow()->windowTitle(); + if (!windowTitleText.isEmpty()) { + if (m_windowTitle.text() != windowTitleText) { + m_windowTitle.setText(windowTitleText); + m_windowTitle.prepare(); + } + + QRect titleBar = top; + titleBar.setLeft(margins().left() + BUTTON_SPACING + + (icon.isNull() ? 0 : 22 + BUTTON_SPACING)); + titleBar.setRight(minimizeButtonRect().left() - BUTTON_SPACING); + + p.save(); + p.setClipRect(titleBar); + p.setPen(active ? foregroundColor : foregroundInactiveColor); + QSizeF size = m_windowTitle.size(); + int dx = (top.width() - size.width()) /2; + int dy = (top.height()- size.height()) /2; + QFont font = p.font(); + font.setPixelSize(14); + p.setFont(font); + QPoint windowTitlePoint(top.topLeft().x() + dx, + top.topLeft().y() + dy); + p.drawStaticText(windowTitlePoint, m_windowTitle); + p.restore(); + } + + QRectF rect; + + // Default pen + QPen pen(active ? foregroundColor : foregroundInactiveColor); + p.setPen(pen); + + // Close button + p.save(); + rect = closeButtonRect(); + qreal crossSize = rect.height() / 2.3; + QPointF crossCenter(rect.center()); + QRectF crossRect(crossCenter.x() - crossSize / 2, crossCenter.y() - crossSize / 2, crossSize, crossSize); + pen.setWidth(2); + p.setPen(pen); + p.drawLine(crossRect.topLeft(), crossRect.bottomRight()); + p.drawLine(crossRect.bottomLeft(), crossRect.topRight()); + p.restore(); + + // Maximize button + p.save(); + p.setRenderHint(QPainter::Antialiasing, false); + rect = maximizeButtonRect().adjusted(4, 5, -4, -5); + if ((window()->windowStates() & Qt::WindowMaximized)) { + qreal inset = 2; + QRectF rect1 = rect.adjusted(inset, 0, 0, -inset); + QRectF rect2 = rect.adjusted(0, inset, -inset, 0); + p.drawRect(rect1); + p.setBrush(backgroundColor); // need to cover up some lines from the other rect + p.drawRect(rect2); + } else { + p.drawRect(rect); + p.drawLine(rect.left(), rect.top() + 1, rect.right(), rect.top() + 1); + } + p.restore(); + + // Minimize button + p.save(); + p.setRenderHint(QPainter::Antialiasing, false); + rect = minimizeButtonRect().adjusted(5, 5, -5, -5); + pen.setWidth(2); + p.setPen(pen); + p.drawLine(rect.bottomLeft(), rect.bottomRight()); + p.restore(); +} + +bool QWaylandBradientDecoration::clickButton(Qt::MouseButtons b, Button btn) +{ + if (isLeftClicked(b)) { + m_clicking = btn; + return false; + } else if (isLeftReleased(b)) { + if (m_clicking == btn) { + m_clicking = None; + return true; + } else { + m_clicking = None; + } + } + return false; +} + +bool QWaylandBradientDecoration::handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, Qt::MouseButtons b, Qt::KeyboardModifiers mods) + +{ + Q_UNUSED(global); + + // Figure out what area mouse is in + QSize ss = waylandWindow()->surfaceSize(); + if (local.y() <= margins().top()) { + processPointerTop(inputDevice, local, b, mods, PointerType::Mouse); + } else if (local.y() >= ss.height() - margins().bottom()) { + processPointerBottom(inputDevice, local, b, mods, PointerType::Mouse); + } else if (local.x() <= margins().left()) { + processPointerLeft(inputDevice, local, b, mods, PointerType::Mouse); + } else if (local.x() >= ss.width() - margins().right()) { + processPointerRight(inputDevice, local, b, mods, PointerType::Mouse); + } else { +#if QT_CONFIG(cursor) + waylandWindow()->restoreMouseCursor(inputDevice); +#endif + setMouseButtons(b); + return false; + } + + setMouseButtons(b); + return true; +} + +bool QWaylandBradientDecoration::handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, QEventPoint::State state, Qt::KeyboardModifiers mods) +{ + Q_UNUSED(global); + QSize ss = waylandWindow()->surfaceSize(); + + bool handled = state == QEventPoint::Pressed; + if (handled) { + if (local.y() <= margins().top()) { + processPointerTop(inputDevice, local, Qt::LeftButton, mods, PointerType::Touch); + } else if (local.y() >= ss.height() - margins().bottom()) { + processPointerBottom(inputDevice, local, Qt::LeftButton, mods, PointerType::Touch); + } else if (local.x() <= margins().left()) { + processPointerLeft(inputDevice, local, Qt::LeftButton, mods, PointerType::Touch); + } else if (local.x() >= ss.width() - margins().right()) { + processPointerRight(inputDevice, local, Qt::LeftButton, mods, PointerType::Touch); + } else { + handled = false; + } + } + + return handled; +} + +void QWaylandBradientDecoration::processPointerTop(QWaylandInputDevice *inputDevice, + const QPointF &local, + Qt::MouseButtons b, + Qt::KeyboardModifiers mods, + PointerType type) +{ +#if !QT_CONFIG(cursor) + Q_UNUSED(type); +#endif + + QSize ss = waylandWindow()->surfaceSize(); + Q_UNUSED(mods); + if (local.y() <= margins().bottom()) { + if (local.x() <= margins().left()) { + //top left bit +#if QT_CONFIG(cursor) + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeFDiagCursor); +#endif + startResize(inputDevice, Qt::TopEdge | Qt::LeftEdge, b); + } else if (local.x() >= ss.width() - margins().right()) { + //top right bit +#if QT_CONFIG(cursor) + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeBDiagCursor); +#endif + startResize(inputDevice, Qt::TopEdge | Qt::RightEdge, b); + } else { + //top resize bit +#if QT_CONFIG(cursor) + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SplitVCursor); +#endif + startResize(inputDevice, Qt::TopEdge, b); + } + } else if (local.x() <= margins().left()) { + processPointerLeft(inputDevice, local, b, mods, type); + } else if (local.x() >= ss.width() - margins().right()) { + processPointerRight(inputDevice, local, b, mods, type); + } else if (isRightClicked(b)) { + showWindowMenu(inputDevice); + } else if (closeButtonRect().contains(local)) { + if (type == PointerType::Touch || clickButton(b, Close)) + QWindowSystemInterface::handleCloseEvent(window()); + } else if (maximizeButtonRect().contains(local)) { + if (type == PointerType::Touch || clickButton(b, Maximize)) + window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized); + } else if (minimizeButtonRect().contains(local)) { + if (type == PointerType::Touch || clickButton(b, Minimize)) + window()->setWindowState(Qt::WindowMinimized); + } else { +#if QT_CONFIG(cursor) + if (type == PointerType::Mouse) + waylandWindow()->restoreMouseCursor(inputDevice); +#endif + startMove(inputDevice,b); + } +} + +void QWaylandBradientDecoration::processPointerBottom(QWaylandInputDevice *inputDevice, + const QPointF &local, + Qt::MouseButtons b, + Qt::KeyboardModifiers mods, + PointerType type) +{ + Q_UNUSED(mods); +#if !QT_CONFIG(cursor) + Q_UNUSED(type); +#endif + + QSize ss = waylandWindow()->surfaceSize(); + if (local.x() <= margins().left()) { + //bottom left bit +#if QT_CONFIG(cursor) + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeBDiagCursor); +#endif + startResize(inputDevice, Qt::BottomEdge | Qt::LeftEdge, b); + } else if (local.x() >= ss.width() - margins().right()) { + //bottom right bit +#if QT_CONFIG(cursor) + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeFDiagCursor); +#endif + startResize(inputDevice, Qt::BottomEdge | Qt::RightEdge, b); + } else { + //bottom bit +#if QT_CONFIG(cursor) + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeVerCursor); +#endif + startResize(inputDevice, Qt::BottomEdge, b); + } +} + +void QWaylandBradientDecoration::processPointerLeft(QWaylandInputDevice *inputDevice, + const QPointF &local, + Qt::MouseButtons b, + Qt::KeyboardModifiers mods, + PointerType type) +{ + Q_UNUSED(local); + Q_UNUSED(mods); +#if QT_CONFIG(cursor) + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeHorCursor); +#else + Q_UNUSED(type); +#endif + startResize(inputDevice, Qt::LeftEdge, b); +} + +void QWaylandBradientDecoration::processPointerRight(QWaylandInputDevice *inputDevice, + const QPointF &local, + Qt::MouseButtons b, + Qt::KeyboardModifiers mods, + PointerType type) +{ + Q_UNUSED(local); + Q_UNUSED(mods); +#if QT_CONFIG(cursor) + if (type == PointerType::Mouse) + waylandWindow()->setMouseCursor(inputDevice, Qt::SizeHorCursor); +#else + Q_UNUSED(type); +#endif + startResize(inputDevice, Qt::RightEdge, b); +} + +class QWaylandBradientDecorationPlugin : public QWaylandDecorationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandDecorationFactoryInterface_iid FILE "bradient.json") +public: + QWaylandAbstractDecoration *create(const QString&, const QStringList&) override; +}; + +QWaylandAbstractDecoration *QWaylandBradientDecorationPlugin::create(const QString& system, const QStringList& paramList) +{ + Q_UNUSED(paramList); + Q_UNUSED(system); + return new QWaylandBradientDecoration(); +} + +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/hardwareintegration/CMakeLists.txt new file mode 100644 index 00000000000..9ff56d69f9e --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from client.pro. + +if (NOT TARGET Qt::WaylandClient) + return() +endif() + +if(QT_FEATURE_wayland_egl) + add_subdirectory(wayland-egl) +endif() +if(QT_FEATURE_wayland_brcm) + add_subdirectory(brcm-egl) +endif() +if(QT_FEATURE_wayland_drm_egl_server_buffer) + add_subdirectory(drm-egl-server) +endif() +if(QT_FEATURE_wayland_libhybris_egl_server_buffer) + add_subdirectory(libhybris-egl-server) +endif() +if(QT_FEATURE_wayland_shm_emulation_server_buffer) + add_subdirectory(shm-emulation-server) +endif() +if(QT_FEATURE_wayland_dmabuf_server_buffer) + add_subdirectory(dmabuf-server) +endif() +if(QT_FEATURE_wayland_vulkan_server_buffer) + add_subdirectory(vulkan-server) +endif() diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/CMakeLists.txt new file mode 100644 index 00000000000..a292ae202f3 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from brcm-egl.pro. + +##################################################################### +## QWaylandBrcmEglClientBufferPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(QWaylandBrcmEglClientBufferPlugin + OUTPUT_NAME brcm-egl + PLUGIN_TYPE wayland-graphics-integration-client + SOURCES + qwaylandbrcmeglintegration.cpp qwaylandbrcmeglintegration.h + qwaylandbrcmeglwindow.cpp qwaylandbrcmeglwindow.h + qwaylandbrcmglcontext.cpp qwaylandbrcmglcontext.h + main.cpp + PUBLIC_LIBRARIES + ${CMAKE_DL_LIBS} + EGL::EGL + Qt::Core + Qt::Gui + Qt::WaylandClientPrivate + Wayland::Client + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 +) + +qt6_generate_wayland_protocol_client_sources(QWaylandBrcmEglClientBufferPlugin + PRIVATE_CODE + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/extensions/brcm.xml +) + +#### Keys ignored in scope 1:.:.:brcm-egl.pro:: +# OTHER_FILES = "brcm-egl.json" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/brcm-egl.json b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/brcm-egl.json new file mode 100644 index 00000000000..3a659a87b97 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/brcm-egl.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "brcm" ] +} diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/main.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/main.cpp new file mode 100644 index 00000000000..2df6fcc5821 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/main.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2016 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 + +#include +#include "qwaylandbrcmeglintegration.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandBrcmEglClientBufferPlugin : public QWaylandClientBufferIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandClientBufferIntegrationFactoryInterface_iid FILE "brcm-egl.json") +public: + QWaylandClientBufferIntegration *create(const QString&, const QStringList&) override; +}; + +QWaylandClientBufferIntegration *QWaylandBrcmEglClientBufferPlugin::create(const QString& system, const QStringList& paramList) +{ + Q_UNUSED(paramList); + Q_UNUSED(system); + return new QWaylandBrcmEglIntegration(); +} + +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmeglintegration.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmeglintegration.cpp new file mode 100644 index 00000000000..8f9047993ed --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmeglintegration.cpp @@ -0,0 +1,121 @@ +// Copyright (C) 2016 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 + +#include "qwaylandbrcmeglintegration.h" + +#include + +#include "qwaylandbrcmeglwindow.h" +#include "qwaylandbrcmglcontext.h" + +#include + +#include "wayland-brcm-client-protocol.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandBrcmEglIntegration::QWaylandBrcmEglIntegration() +{ + qDebug() << "Using Brcm-EGL"; +} + +void QWaylandBrcmEglIntegration::wlDisplayHandleGlobal(void *data, struct ::wl_registry *registry, uint32_t id, const QString &interface, uint32_t version) +{ + Q_UNUSED(version); + if (interface == "qt_brcm") { + QWaylandBrcmEglIntegration *integration = static_cast(data); + integration->m_waylandBrcm = static_cast(wl_registry_bind(registry, id, &qt_brcm_interface, 1)); + } +} + +qt_brcm *QWaylandBrcmEglIntegration::waylandBrcm() const +{ + return m_waylandBrcm; +} + +QWaylandBrcmEglIntegration::~QWaylandBrcmEglIntegration() +{ + eglTerminate(m_eglDisplay); +} + +void QWaylandBrcmEglIntegration::initialize(QWaylandDisplay *waylandDisplay) +{ + m_display = waylandDisplay; + m_waylandDisplay = waylandDisplay->wl_display(); + waylandDisplay->addRegistryListener(wlDisplayHandleGlobal, this); + EGLint major,minor; + m_eglDisplay = eglGetDisplay((EGLNativeDisplayType)EGL_DEFAULT_DISPLAY); + if (m_eglDisplay == NULL) { + qWarning("EGL not available"); + } else { + if (!eglInitialize(m_eglDisplay, &major, &minor)) { + qWarning("failed to initialize EGL display"); + return; + } + + eglFlushBRCM = (PFNEGLFLUSHBRCMPROC)eglGetProcAddress("eglFlushBRCM"); + if (!eglFlushBRCM) { + qWarning("failed to resolve eglFlushBRCM, performance will suffer"); + } + + eglCreateGlobalImageBRCM = (PFNEGLCREATEGLOBALIMAGEBRCMPROC)eglGetProcAddress("eglCreateGlobalImageBRCM"); + if (!eglCreateGlobalImageBRCM) { + qWarning("failed to resolve eglCreateGlobalImageBRCM"); + return; + } + + eglDestroyGlobalImageBRCM = (PFNEGLDESTROYGLOBALIMAGEBRCMPROC)eglGetProcAddress("eglDestroyGlobalImageBRCM"); + if (!eglDestroyGlobalImageBRCM) { + qWarning("failed to resolve eglDestroyGlobalImageBRCM"); + return; + } + } +} + +QWaylandWindow *QWaylandBrcmEglIntegration::createEglWindow(QWindow *window) +{ + return new QWaylandBrcmEglWindow(window, m_display); +} + +QPlatformOpenGLContext *QWaylandBrcmEglIntegration::createPlatformOpenGLContext(const QSurfaceFormat &glFormat, QPlatformOpenGLContext *share) const +{ + return new QWaylandBrcmGLContext(m_eglDisplay, glFormat, share); +} + +EGLDisplay QWaylandBrcmEglIntegration::eglDisplay() const +{ + return m_eglDisplay; +} + +void *QWaylandBrcmEglIntegration::nativeResource(NativeResource resource) +{ + switch (resource) { + case EglDisplay: + return m_eglDisplay; + default: + break; + } + return nullptr; +} + +void *QWaylandBrcmEglIntegration::nativeResourceForContext(NativeResource resource, QPlatformOpenGLContext *context) +{ + Q_ASSERT(context); + switch (resource) { + case EglConfig: + return static_cast(context)->eglConfig(); + case EglContext: + return static_cast(context)->eglContext(); + case EglDisplay: + return m_eglDisplay; + default: + break; + } + return nullptr; +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmeglintegration.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmeglintegration.h new file mode 100644 index 00000000000..ac164ab9dac --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmeglintegration.h @@ -0,0 +1,68 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDBRCMEGLINTEGRATION_H +#define QWAYLANDBRCMEGLINTEGRATION_H + +#include +#include +#include + +#include +#include + +#include + +#include + +struct qt_brcm; + +QT_BEGIN_NAMESPACE + +class QWindow; + +namespace QtWaylandClient { + +class QWaylandWindow; + +class QWaylandBrcmEglIntegration : public QWaylandClientBufferIntegration +{ +public: + QWaylandBrcmEglIntegration(); + ~QWaylandBrcmEglIntegration(); + + void initialize(QWaylandDisplay *waylandDisplay) override; + + bool supportsThreadedOpenGL() const override { return true; } + bool supportsWindowDecoration() const override { return false; } + + QWaylandWindow *createEglWindow(QWindow *window); + QPlatformOpenGLContext *createPlatformOpenGLContext(const QSurfaceFormat &glFormat, QPlatformOpenGLContext *share) const override; + + EGLDisplay eglDisplay() const; + + struct qt_brcm *waylandBrcm() const; + + PFNEGLFLUSHBRCMPROC eglFlushBRCM; + PFNEGLCREATEGLOBALIMAGEBRCMPROC eglCreateGlobalImageBRCM; + PFNEGLDESTROYGLOBALIMAGEBRCMPROC eglDestroyGlobalImageBRCM; + + void *nativeResource(NativeResource resource) override; + void *nativeResourceForContext(NativeResource resource, QPlatformOpenGLContext *context) override; + +private: + static void wlDisplayHandleGlobal(void *data, struct ::wl_registry *registry, uint32_t id, const QString &interface, uint32_t version); + + struct wl_display *m_waylandDisplay = nullptr; + struct qt_brcm *m_waylandBrcm = nullptr; + + EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; + + QWaylandDisplay *m_display = nullptr; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDBRCMEGLINTEGRATION_H diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmeglwindow.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmeglwindow.cpp new file mode 100644 index 00000000000..117e57807f6 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmeglwindow.cpp @@ -0,0 +1,230 @@ +// Copyright (C) 2016 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 + +#include "qwaylandbrcmeglwindow.h" + +#include +#include +#include "qwaylandbrcmglcontext.h" + +#include + +#include +#include + +#include + +#include "wayland-brcm-client-protocol.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandBrcmBuffer : public QWaylandBuffer +{ +public: + QWaylandBrcmBuffer(QWaylandDisplay *display, + struct qt_brcm *brcm, + const QSize &size, + EGLint *data, + int count, + struct wl_event_queue *eventQueue) + : m_size(size) + , m_display(display) + , m_eventQueue(eventQueue) + { + wl_array_init(&m_array); + m_data = static_cast(wl_array_add(&m_array, count * sizeof(EGLint))); + + for (int i = 0; i < count; ++i) + m_data[i] = data[i]; + + mBuffer = qt_brcm_create_buffer(brcm, size.width(), size.height(), &m_array); + wl_proxy_set_queue(reinterpret_cast(mBuffer), m_eventQueue); + + static const struct wl_buffer_listener buffer_listener = { + QWaylandBrcmBuffer::buffer_release + }; + + wl_buffer_add_listener(mBuffer, &buffer_listener, this); + } + + ~QWaylandBrcmBuffer() + { + wl_array_release(&m_array); + wl_buffer_destroy(mBuffer); + mBuffer = nullptr; + } + + QSize size() const { return m_size; } + + void bind() + { + m_released = false; + } + + void waitForRelease() + { + if (m_released) + return; + while (!m_released) { + wl_display_dispatch_queue(m_display->wl_display(), m_eventQueue); + } + } + + static void buffer_release(void *data, wl_buffer *buffer) + { + Q_UNUSED(buffer); + static_cast(data)->m_released = true; + } + +private: + + QSize m_size; + bool m_released = true; + wl_array m_array; + EGLint *m_data = nullptr; + QWaylandDisplay *m_display = nullptr; + struct wl_event_queue *m_eventQueue = nullptr; +}; + +QWaylandBrcmEglWindow::QWaylandBrcmEglWindow(QWindow *window, QWaylandDisplay *display) + : QWaylandWindow(window, display) + , m_eglIntegration(static_cast(mDisplay->clientBufferIntegration())) + , m_format(window->format()) + , m_eventQueue(wl_display_create_queue(mDisplay->wl_display())) +{ +} + +QWaylandBrcmEglWindow::~QWaylandBrcmEglWindow() +{ + destroyEglSurfaces(); +} + +QWaylandWindow::WindowType QWaylandBrcmEglWindow::windowType() const +{ + return QWaylandWindow::Egl; +} + +void QWaylandBrcmEglWindow::setGeometry(const QRect &rect) +{ + destroyEglSurfaces(); + QWaylandWindow::setGeometry(rect); +} + +QSurfaceFormat QWaylandBrcmEglWindow::format() const +{ + return m_format; +} + +void QWaylandBrcmEglWindow::destroyEglSurfaces() +{ + for (int i = 0; i < m_count; ++i) { + if (m_eglSurfaces[i]) { + eglDestroySurface(m_eglIntegration->eglDisplay(), m_eglSurfaces[i]); + m_eglSurfaces[i] = 0; + // the server does this + //m_eglIntegration->eglDestroyGlobalImageBRCM(&m_globalImages[5*i]); + delete m_buffers[i]; + } + } + + m_count = 0; + m_current = 0; +} + +QSurfaceFormat brcmFixFormat(const QSurfaceFormat &f) +{ + QSurfaceFormat format = f; + format.setRedBufferSize(8); + format.setGreenBufferSize(8); + format.setBlueBufferSize(8); + format.setAlphaBufferSize(8); + return format; +} + +void QWaylandBrcmEglWindow::createEglSurfaces() +{ + QSize size(geometry().size()); + + m_count = window()->format().swapBehavior() == QSurfaceFormat::TripleBuffer ? 3 : 2; + + EGLConfig eglConfig = q_configFromGLFormat(m_eglIntegration->eglDisplay(), brcmFixFormat(window()->format()), true, EGL_PIXMAP_BIT); + + m_format = q_glFormatFromConfig(m_eglIntegration->eglDisplay(), eglConfig); + + EGLint pixel_format = EGL_PIXEL_FORMAT_ARGB_8888_BRCM; + + EGLint rt; + eglGetConfigAttrib(m_eglIntegration->eglDisplay(), eglConfig, EGL_RENDERABLE_TYPE, &rt); + + if (rt & EGL_OPENGL_ES_BIT) { + pixel_format |= EGL_PIXEL_FORMAT_RENDER_GLES_BRCM; + pixel_format |= EGL_PIXEL_FORMAT_GLES_TEXTURE_BRCM; + } + + if (rt & EGL_OPENGL_ES2_BIT) { + pixel_format |= EGL_PIXEL_FORMAT_RENDER_GLES2_BRCM; + pixel_format |= EGL_PIXEL_FORMAT_GLES2_TEXTURE_BRCM; + } + + if (rt & EGL_OPENVG_BIT) { + pixel_format |= EGL_PIXEL_FORMAT_RENDER_VG_BRCM; + pixel_format |= EGL_PIXEL_FORMAT_VG_IMAGE_BRCM; + } + + if (rt & EGL_OPENGL_BIT) { + pixel_format |= EGL_PIXEL_FORMAT_RENDER_GL_BRCM; + } + + memset(m_globalImages, 0, 5 * m_count * sizeof(EGLint)); + for (int i = 0; i < m_count; ++i) { + m_eglIntegration->eglCreateGlobalImageBRCM(size.width(), size.height(), pixel_format, + 0, size.width() * 4, &m_globalImages[5*i]); + + m_globalImages[5*i+2] = size.width(); + m_globalImages[5*i+3] = size.height(); + m_globalImages[5*i+4] = pixel_format; + + EGLint attrs[] = { + EGL_VG_COLORSPACE, EGL_VG_COLORSPACE_sRGB, + EGL_VG_ALPHA_FORMAT, pixel_format & EGL_PIXEL_FORMAT_ARGB_8888_PRE_BRCM ? EGL_VG_ALPHA_FORMAT_PRE : EGL_VG_ALPHA_FORMAT_NONPRE, + EGL_NONE + }; + + m_eglSurfaces[i] = eglCreatePixmapSurface(m_eglIntegration->eglDisplay(), eglConfig, (EGLNativePixmapType)&m_globalImages[5*i], attrs); + if (m_eglSurfaces[i] == EGL_NO_SURFACE) + qFatal("eglCreatePixmapSurface failed: %x, global image id: %d %d\n", eglGetError(), m_globalImages[5*i], m_globalImages[5*i+1]); + m_buffers[i] = new QWaylandBrcmBuffer(mDisplay, m_eglIntegration->waylandBrcm(), size, &m_globalImages[5*i], 5, m_eventQueue); + } +} + +void QWaylandBrcmEglWindow::swapBuffers() +{ + if (m_eglIntegration->eglFlushBRCM) { + m_eglIntegration->eglFlushBRCM(); + } else { + glFlush(); + glFinish(); + } + + if (!m_count) + return; + + m_buffers[m_current]->bind(); + commit(m_buffers[m_current], QRegion(0, 0, geometry().size().width(), geometry().size().height())); + + m_current = (m_current + 1) % m_count; + m_buffers[m_current]->waitForRelease(); +} + +bool QWaylandBrcmEglWindow::makeCurrent(EGLContext context) +{ + if (!m_count) + const_cast(this)->createEglSurfaces(); + return eglMakeCurrent(m_eglIntegration->eglDisplay(), m_eglSurfaces[m_current], m_eglSurfaces[m_current], context); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmeglwindow.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmeglwindow.h new file mode 100644 index 00000000000..3b9026f9c0a --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmeglwindow.h @@ -0,0 +1,59 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDBRCMEGLWINDOW_H +#define QWAYLANDBRCMEGLWINDOW_H + +#include +#include "qwaylandbrcmeglintegration.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandBrcmBuffer; + +class QWaylandBrcmEglWindow : public QWaylandWindow +{ + Q_OBJECT +public: + QWaylandBrcmEglWindow(QWindow *window, QWaylandDisplay *display); + ~QWaylandBrcmEglWindow(); + WindowType windowType() const override; + void setGeometry(const QRect &rect) override; + + QSurfaceFormat format() const override; + + bool makeCurrent(EGLContext context); + void swapBuffers(); + +private: + void createEglSurfaces(); + void destroyEglSurfaces(); + + QWaylandBrcmEglIntegration *m_eglIntegration = nullptr; + struct wl_egl_window *m_waylandEglWindow = nullptr; + + const QWaylandWindow *m_parentWindow = nullptr; + + EGLint m_globalImages[3*5]; + EGLSurface m_eglSurfaces[3]; + + QWaylandBrcmBuffer *m_buffers[3]; + QSurfaceFormat m_format; + + struct wl_event_queue *m_eventQueue = nullptr; + + int m_current = 0; + int m_count = 0; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDBRCMEGLWINDOW_H diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmglcontext.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmglcontext.cpp new file mode 100644 index 00000000000..66135a7cc43 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmglcontext.cpp @@ -0,0 +1,76 @@ +// Copyright (C) 2016 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 + +#include "qwaylandbrcmglcontext.h" + +#include +#include +#include "qwaylandbrcmeglwindow.h" + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +extern QSurfaceFormat brcmFixFormat(const QSurfaceFormat &format); + +QWaylandBrcmGLContext::QWaylandBrcmGLContext(EGLDisplay eglDisplay, const QSurfaceFormat &format, QPlatformOpenGLContext *share) + : QPlatformOpenGLContext() + , m_eglDisplay(eglDisplay) + , m_config(q_configFromGLFormat(m_eglDisplay, brcmFixFormat(format), true)) + , m_format(q_glFormatFromConfig(m_eglDisplay, m_config)) +{ + EGLContext shareEGLContext = share ? static_cast(share)->eglContext() : EGL_NO_CONTEXT; + + eglBindAPI(EGL_OPENGL_ES_API); + + QList eglContextAttrs; + eglContextAttrs.append(EGL_CONTEXT_CLIENT_VERSION); + eglContextAttrs.append(format.majorVersion() == 1 ? 1 : 2); + eglContextAttrs.append(EGL_NONE); + + m_context = eglCreateContext(m_eglDisplay, m_config, shareEGLContext, eglContextAttrs.constData()); +} + +QWaylandBrcmGLContext::~QWaylandBrcmGLContext() +{ + eglDestroyContext(m_eglDisplay, m_context); +} + +bool QWaylandBrcmGLContext::makeCurrent(QPlatformSurface *surface) +{ + return static_cast(surface)->makeCurrent(m_context); +} + +void QWaylandBrcmGLContext::doneCurrent() +{ + eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +void QWaylandBrcmGLContext::swapBuffers(QPlatformSurface *surface) +{ + static_cast(surface)->swapBuffers(); +} + +QFunctionPointer QWaylandBrcmGLContext::getProcAddress(const char *procName) +{ + QFunctionPointer proc = (QFunctionPointer) eglGetProcAddress(procName); + if (!proc) + proc = (QFunctionPointer) dlsym(RTLD_DEFAULT, procName); + return proc; +} + +EGLConfig QWaylandBrcmGLContext::eglConfig() const +{ + return m_config; +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmglcontext.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmglcontext.h new file mode 100644 index 00000000000..e64a20d9b73 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/brcm-egl/qwaylandbrcmglcontext.h @@ -0,0 +1,48 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDBRCMGLCONTEXT_H +#define QWAYLANDBRCMGLCONTEXT_H + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandWindow; + +class QWaylandBrcmGLContext : public QPlatformOpenGLContext { +public: + QWaylandBrcmGLContext(EGLDisplay eglDisplay, const QSurfaceFormat &format, QPlatformOpenGLContext *share); + ~QWaylandBrcmGLContext(); + + void swapBuffers(QPlatformSurface *surface) override; + + bool makeCurrent(QPlatformSurface *surface) override; + void doneCurrent() override; + + QFunctionPointer getProcAddress(const char *procName) override; + + QSurfaceFormat format() const override { return m_format; } + + EGLConfig eglConfig() const; + EGLContext eglContext() const { return m_context; } + +private: + EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; + + EGLContext m_context; + EGLConfig m_config; + QSurfaceFormat m_format; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDBRCMGLCONTEXT_H diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/CMakeLists.txt new file mode 100644 index 00000000000..5153ce2c5c3 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from dmabuf-server.pro. + +##################################################################### +## DmaBufServerBufferPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(DmaBufServerBufferPlugin + OUTPUT_NAME dmabuf-server + PLUGIN_TYPE wayland-graphics-integration-client + SOURCES + dmabufserverbufferintegration.cpp dmabufserverbufferintegration.h + main.cpp + LIBRARIES + EGL::EGL + Qt::Core + Qt::Gui + Qt::OpenGL + Qt::WaylandClientPrivate + Wayland::Client + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 +) + +qt6_generate_wayland_protocol_client_sources(DmaBufServerBufferPlugin + PRIVATE_CODE + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/extensions/qt-dmabuf-server-buffer.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/extensions/server-buffer-extension.xml +) + +#### Keys ignored in scope 1:.:.:dmabuf-server.pro:: +# OTHER_FILES = "dmabuf-server.json" +# QMAKE_CXXFLAGS_WARN_ON = "--Wcast-qual" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabuf-server.json b/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabuf-server.json new file mode 100644 index 00000000000..22272a4abe9 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabuf-server.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "dmabuf-server" ] +} diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabufserverbufferintegration.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabufserverbufferintegration.cpp new file mode 100644 index 00000000000..54df2badd2e --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabufserverbufferintegration.cpp @@ -0,0 +1,152 @@ +// Copyright (C) 2018 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 + +#include "dmabufserverbufferintegration.h" +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +DmaBufServerBuffer::DmaBufServerBuffer(DmaBufServerBufferIntegration *integration + , struct ::qt_server_buffer *id + , int32_t fd + , int32_t width + , int32_t height + , int32_t stride + , int32_t offset + , int32_t fourcc_format) + : m_integration(integration) + , m_server_buffer(id) +{ + m_size = QSize(width, height); + + + EGLint import_attribs[] = { + EGL_WIDTH, width, + EGL_HEIGHT, height, + EGL_LINUX_DRM_FOURCC_EXT, fourcc_format, + EGL_DMA_BUF_PLANE0_FD_EXT, fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, stride, + EGL_NONE + }; + + m_image = integration->eglCreateImageKHR( + EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, + (EGLClientBuffer)nullptr, + import_attribs); + + int err = eglGetError(); + qCDebug(lcQpaWayland) << "imported egl image" << m_image; + if (m_image == EGL_NO_IMAGE_KHR || err != EGL_SUCCESS) + qCWarning(lcQpaWayland) << "DmaBufServerBuffer error importing image. EGL error code" << Qt::hex << err; + + qt_server_buffer_set_user_data(id, this); + +} + +DmaBufServerBuffer::~DmaBufServerBuffer() +{ + int err = m_integration->eglDestroyImageKHR(m_image); + if (err != EGL_SUCCESS) + qCWarning(lcQpaWayland) << "~DmaBufServerBuffer error destroying image" << m_image << "error code " << Qt::hex << err; + qt_server_buffer_release(m_server_buffer); + qt_server_buffer_destroy(m_server_buffer); +} + +QOpenGLTexture *DmaBufServerBuffer::toOpenGlTexture() +{ + if (!QOpenGLContext::currentContext()) + qCWarning(lcQpaWayland) <<"DmaBufServerBuffer: creating texture with no current context"; + + if (!m_texture) { + m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + m_texture->create(); + } + + m_texture->bind(); + m_integration->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + m_texture->release(); + m_texture->setSize(m_size.width(), m_size.height()); + return m_texture; +} + +void DmaBufServerBufferIntegration::initializeEgl() +{ + if (m_egl_initialized) + return; + m_egl_initialized = true; + + m_egl_display = eglGetDisplay((EGLNativeDisplayType) m_display->wl_display()); + if (m_egl_display == EGL_NO_DISPLAY) { + qCWarning(lcQpaWayland) << "Failed to initialize drm egl server buffer integration. Could not get egl display from wl_display."; + return; + } + + const char *extensionString = eglQueryString(m_egl_display, EGL_EXTENSIONS); + + + if (!extensionString || !strstr(extensionString, "EGL_KHR_image")) { + qCWarning(lcQpaWayland) << "Failed to initialize dmabuf server buffer integration. There is no EGL_KHR_image extension.\n"; + return; + } + m_egl_create_image = reinterpret_cast(eglGetProcAddress("eglCreateImageKHR")); + m_egl_destroy_image = reinterpret_cast(eglGetProcAddress("eglDestroyImageKHR")); + if (!m_egl_create_image || !m_egl_destroy_image) { + qCWarning(lcQpaWayland) << "Failed to initialize dmabuf server buffer integration. Could not resolve eglCreateImageKHR or eglDestroyImageKHR"; + return; + } + + m_gl_egl_image_target_texture = reinterpret_cast(eglGetProcAddress("glEGLImageTargetTexture2DOES")); + if (!m_gl_egl_image_target_texture) { + qCWarning(lcQpaWayland) << "Failed to initialize dmabuf server buffer integration. Could not resolve glEGLImageTargetTexture2DOES"; + return; + } +} + +void DmaBufServerBufferIntegration::initialize(QWaylandDisplay *display) +{ + m_display = display; + display->addRegistryListener(&wlDisplayHandleGlobal, this); +} + +QWaylandServerBuffer *DmaBufServerBufferIntegration::serverBuffer(struct qt_server_buffer *buffer) +{ + return static_cast(qt_server_buffer_get_user_data(buffer)); +} + +void DmaBufServerBufferIntegration::wlDisplayHandleGlobal(void *data, ::wl_registry *registry, uint32_t id, const QString &interface, uint32_t version) +{ + Q_UNUSED(version); + if (interface == QStringLiteral("qt_dmabuf_server_buffer")) { + auto *integration = static_cast(data); + integration->QtWayland::qt_dmabuf_server_buffer::init(registry, id, 1); + } +} + +void DmaBufServerBufferIntegration::dmabuf_server_buffer_server_buffer_created(struct ::qt_server_buffer *id + , int32_t name + , int32_t width + , int32_t height + , int32_t stride + , int32_t offset + , int32_t format) +{ + (void) new DmaBufServerBuffer(this, id, name, width, height, stride, offset, format); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabufserverbufferintegration.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabufserverbufferintegration.h new file mode 100644 index 00000000000..13dbdfbfbab --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/dmabufserverbufferintegration.h @@ -0,0 +1,111 @@ +// Copyright (C) 2018 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 + +#ifndef DMABUFSERVERBUFFERINTEGRATION_H +#define DMABUFSERVERBUFFERINTEGRATION_H + +#include +#include +#include "qwayland-qt-dmabuf-server-buffer.h" +#include + +#include "dmabufserverbufferintegration.h" +#include +#include + +#include +#include +#ifndef EGL_KHR_image +typedef void *EGLImageKHR; +typedef EGLImageKHR (EGLAPIENTRYP PFNEGLCREATEIMAGEKHRPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay dpy, EGLImageKHR image); +#endif + +#ifndef GL_OES_EGL_image +typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES image); +#endif + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class DmaBufServerBufferIntegration; + +class DmaBufServerBuffer : public QWaylandServerBuffer +{ +public: + DmaBufServerBuffer(DmaBufServerBufferIntegration *integration, struct ::qt_server_buffer *id, int32_t fd, + int32_t width, int32_t height, int32_t stride, int32_t offset, int32_t fourcc_format); + ~DmaBufServerBuffer() override; + QOpenGLTexture* toOpenGlTexture() override; +private: + DmaBufServerBufferIntegration *m_integration = nullptr; + EGLImageKHR m_image = EGL_NO_IMAGE_KHR; + QOpenGLTexture *m_texture = nullptr; + struct ::qt_server_buffer *m_server_buffer = nullptr; +}; + +class DmaBufServerBufferIntegration + : public QWaylandServerBufferIntegration + , public QtWayland::qt_dmabuf_server_buffer +{ +public: + void initialize(QWaylandDisplay *display) override; + + QWaylandServerBuffer *serverBuffer(struct qt_server_buffer *buffer) override; + + inline EGLImageKHR eglCreateImageKHR(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list); + inline EGLBoolean eglDestroyImageKHR(EGLImageKHR image); + inline void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image); +protected: + void dmabuf_server_buffer_server_buffer_created(struct ::qt_server_buffer *id, int32_t fd, + int32_t width, int32_t height, int32_t stride, + int32_t offset, int32_t fourcc_format) override; + +private: + static void wlDisplayHandleGlobal(void *data, struct ::wl_registry *registry, uint32_t id, + const QString &interface, uint32_t version); + void initializeEgl(); + + PFNEGLCREATEIMAGEKHRPROC m_egl_create_image = nullptr; + PFNEGLDESTROYIMAGEKHRPROC m_egl_destroy_image = nullptr; + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_gl_egl_image_target_texture = nullptr; + QWaylandDisplay *m_display = nullptr; + EGLDisplay m_egl_display = EGL_NO_DISPLAY; + bool m_egl_initialized = false; +}; + +EGLImageKHR DmaBufServerBufferIntegration::eglCreateImageKHR(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list) +{ + if (!m_egl_initialized) + initializeEgl(); + if (!m_egl_create_image) { + qCWarning(lcQpaWayland) << "DmaBufServerBufferIntegration: Trying to use unresolved function eglCreateImageKHR"; + return EGL_NO_IMAGE_KHR; + } + return m_egl_create_image(m_egl_display, ctx, target, buffer, attrib_list); +} + +EGLBoolean DmaBufServerBufferIntegration::eglDestroyImageKHR(EGLImageKHR image) +{ + if (!m_egl_destroy_image) { + qCWarning(lcQpaWayland) << "DmaBufServerBufferIntegration: Trying to use unresolved function eglDestroyImageKHR"; + return false; + } + return m_egl_destroy_image(m_egl_display, image); +} + +void DmaBufServerBufferIntegration::glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) +{ + if (!m_gl_egl_image_target_texture) { + qCWarning(lcQpaWayland) << "DmaBufServerBufferIntegration: Trying to use unresolved function glEGLImageTargetTexture2DOES"; + return; + } + m_gl_egl_image_target_texture(target, image); +} + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/main.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/main.cpp new file mode 100644 index 00000000000..c1c1225264d --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/dmabuf-server/main.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2018 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 + +#include +#include "dmabufserverbufferintegration.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class DmaBufServerBufferPlugin : public QWaylandServerBufferIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandServerBufferIntegrationFactoryInterface_iid FILE "dmabuf-server.json") +public: + QWaylandServerBufferIntegration *create(const QString&, const QStringList&) override; +}; + +QWaylandServerBufferIntegration *DmaBufServerBufferPlugin::create(const QString&, const QStringList&) +{ + return new DmaBufServerBufferIntegration(); +} + +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/CMakeLists.txt new file mode 100644 index 00000000000..a1d673e5262 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from drm-egl-server.pro. + +##################################################################### +## DrmEglServerBufferPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(DrmEglServerBufferPlugin + OUTPUT_NAME drm-egl-server + PLUGIN_TYPE wayland-graphics-integration-client + SOURCES + drmeglserverbufferintegration.cpp drmeglserverbufferintegration.h + main.cpp + LIBRARIES + EGL::EGL + Qt::Core + Qt::Gui + Qt::OpenGL + Qt::WaylandClientPrivate + Wayland::Client + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 +) + +qt6_generate_wayland_protocol_client_sources(DrmEglServerBufferPlugin + PRIVATE_CODE + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/extensions/drm-egl-server-buffer.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/extensions/server-buffer-extension.xml +) + +#### Keys ignored in scope 1:.:.:drm-egl-server.pro:: +# OTHER_FILES = "drm-egl-server.json" +# QMAKE_CXXFLAGS_WARN_ON = "--Wcast-qual" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drm-egl-server.json b/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drm-egl-server.json new file mode 100644 index 00000000000..e2266ec602d --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drm-egl-server.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "drm-egl-server" ] +} diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drmeglserverbufferintegration.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drmeglserverbufferintegration.cpp new file mode 100644 index 00000000000..16a3aa4cf51 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drmeglserverbufferintegration.cpp @@ -0,0 +1,155 @@ +// Copyright (C) 2016 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 + +#include "drmeglserverbufferintegration.h" +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +DrmServerBuffer::DrmServerBuffer(DrmEglServerBufferIntegration *integration + , int32_t name + , int32_t width + , int32_t height + , int32_t stride + , int32_t format) + : m_integration(integration) +{ + m_size = QSize(width, height); + EGLint egl_format; + int32_t format_stride; + switch (format) { + case QtWayland::qt_drm_egl_server_buffer::format_RGBA32: + m_format = QWaylandServerBuffer::RGBA32; + egl_format = EGL_DRM_BUFFER_FORMAT_ARGB32_MESA; + format_stride = stride / 4; + break; +#ifdef EGL_DRM_BUFFER_FORMAT_A8_MESA + case QtWayland::qt_drm_egl_server_buffer::format_A8: + m_format = QWaylandServerBuffer::A8; + egl_format = EGL_DRM_BUFFER_FORMAT_A8_MESA; + format_stride = stride; + break; +#endif + default: + qWarning("DrmServerBuffer: unknown format"); + m_format = QWaylandServerBuffer::RGBA32; + egl_format = EGL_DRM_BUFFER_FORMAT_ARGB32_MESA; + format_stride = stride / 4; + break; + } + EGLint attribs[] = { + EGL_WIDTH, width, + EGL_HEIGHT, height, + EGL_DRM_BUFFER_STRIDE_MESA, format_stride, + EGL_DRM_BUFFER_FORMAT_MESA, egl_format, + EGL_NONE + }; + + qintptr name_pointer = name; + m_image = integration->eglCreateImageKHR(EGL_NO_CONTEXT, EGL_DRM_BUFFER_MESA, (EGLClientBuffer) name_pointer, attribs); + +} + +DrmServerBuffer::~DrmServerBuffer() +{ + m_integration->eglDestroyImageKHR(m_image); +} + +QOpenGLTexture *DrmServerBuffer::toOpenGlTexture() +{ + if (!QOpenGLContext::currentContext()) + qWarning("DrmServerBuffer: creating texture with no current context"); + + if (!m_texture) { + m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + m_texture->create(); + } + + m_texture->bind(); + m_integration->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + m_texture->release(); + m_texture->setSize(m_size.width(), m_size.height()); + return m_texture; +} + +void DrmEglServerBufferIntegration::initializeEgl() +{ + if (m_egl_initialized) + return; + m_egl_initialized = true; + +#if QT_CONFIG(egl_extension_platform_wayland) + m_egl_display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_EXT, m_display->wl_display(), nullptr); +#else + m_egl_display = eglGetDisplay((EGLNativeDisplayType) m_display->wl_display()); +#endif + if (m_egl_display == EGL_NO_DISPLAY) { + qWarning("Failed to initialize drm egl server buffer integration. Could not get egl display from wl_display."); + return; + } + + const char *extensionString = eglQueryString(m_egl_display, EGL_EXTENSIONS); + if (!extensionString || !strstr(extensionString, "EGL_KHR_image")) { + qWarning("Failed to initialize drm egl server buffer integration. There is no EGL_KHR_image extension.\n"); + return; + } + m_egl_create_image = reinterpret_cast(eglGetProcAddress("eglCreateImageKHR")); + m_egl_destroy_image = reinterpret_cast(eglGetProcAddress("eglDestroyImageKHR")); + if (!m_egl_create_image || !m_egl_destroy_image) { + qWarning("Failed to initialize drm egl server buffer integration. Could not resolve eglCreateImageKHR or eglDestroyImageKHR"); + return; + } + + m_gl_egl_image_target_texture = reinterpret_cast(eglGetProcAddress("glEGLImageTargetTexture2DOES")); + if (!m_gl_egl_image_target_texture) { + qWarning("Failed to initialize drm egl server buffer integration. Could not resolve glEGLImageTargetTexture2DOES"); + return; + } + m_egl_initialized = true; +} + +void DrmEglServerBufferIntegration::initialize(QWaylandDisplay *display) +{ + m_display = display; + display->addRegistryListener(&wlDisplayHandleGlobal, this); +} + +QWaylandServerBuffer *DrmEglServerBufferIntegration::serverBuffer(struct qt_server_buffer *buffer) +{ + return static_cast(qt_server_buffer_get_user_data(buffer)); +} + +void DrmEglServerBufferIntegration::wlDisplayHandleGlobal(void *data, ::wl_registry *registry, uint32_t id, const QString &interface, uint32_t version) +{ + Q_UNUSED(version); + if (interface == QStringLiteral("qt_drm_egl_server_buffer")) { + auto *integration = static_cast(data); + integration->QtWayland::qt_drm_egl_server_buffer::init(registry, id, 1); + } +} + +void DrmEglServerBufferIntegration::drm_egl_server_buffer_server_buffer_created(struct ::qt_server_buffer *id + , int32_t name + , int32_t width + , int32_t height + , int32_t stride + , int32_t format) +{ + DrmServerBuffer *server_buffer = new DrmServerBuffer(this, name, width, height, stride, format); + qt_server_buffer_set_user_data(id, server_buffer); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drmeglserverbufferintegration.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drmeglserverbufferintegration.h new file mode 100644 index 00000000000..a06bc96b87d --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/drmeglserverbufferintegration.h @@ -0,0 +1,106 @@ +// Copyright (C) 2016 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 + +#ifndef DRMEGLSERVERBUFFERINTEGRATION_H +#define DRMEGLSERVERBUFFERINTEGRATION_H + +#include +#include +#include "qwayland-drm-egl-server-buffer.h" +#include + +#include "drmeglserverbufferintegration.h" +#include +#include + +#include +#include +#ifndef EGL_KHR_image +typedef void *EGLImageKHR; +typedef EGLImageKHR (EGLAPIENTRYP PFNEGLCREATEIMAGEKHRPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay dpy, EGLImageKHR image); +#endif + +#ifndef GL_OES_EGL_image +typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES image); +#endif + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class DrmEglServerBufferIntegration; + +class DrmServerBuffer : public QWaylandServerBuffer +{ +public: + DrmServerBuffer(DrmEglServerBufferIntegration *integration, int32_t name, int32_t width, int32_t height, int32_t stride, int32_t format); + ~DrmServerBuffer() override; + QOpenGLTexture* toOpenGlTexture() override; +private: + DrmEglServerBufferIntegration *m_integration = nullptr; + EGLImageKHR m_image; + QOpenGLTexture *m_texture = nullptr; +}; + +class DrmEglServerBufferIntegration + : public QWaylandServerBufferIntegration + , public QtWayland::qt_drm_egl_server_buffer +{ +public: + void initialize(QWaylandDisplay *display) override; + + QWaylandServerBuffer *serverBuffer(struct qt_server_buffer *buffer) override; + + inline EGLImageKHR eglCreateImageKHR(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list); + inline EGLBoolean eglDestroyImageKHR (EGLImageKHR image); + inline void glEGLImageTargetTexture2DOES (GLenum target, GLeglImageOES image); +protected: + void drm_egl_server_buffer_server_buffer_created(struct ::qt_server_buffer *id, int32_t name, int32_t width, int32_t height, int32_t stride, int32_t format) override; +private: + static void wlDisplayHandleGlobal(void *data, struct ::wl_registry *registry, uint32_t id, + const QString &interface, uint32_t version); + void initializeEgl(); + + PFNEGLCREATEIMAGEKHRPROC m_egl_create_image; + PFNEGLDESTROYIMAGEKHRPROC m_egl_destroy_image; + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_gl_egl_image_target_texture; + QWaylandDisplay *m_display = nullptr; + EGLDisplay m_egl_display = EGL_NO_DISPLAY; + bool m_egl_initialized = false; +}; + +EGLImageKHR DrmEglServerBufferIntegration::eglCreateImageKHR(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list) +{ + if (!m_egl_initialized) + initializeEgl(); + if (!m_egl_create_image) { + qWarning("DrmEglServerBufferIntegration: Trying to used unresolved function eglCreateImageKHR"); + return EGL_NO_IMAGE_KHR; + } + return m_egl_create_image(m_egl_display, ctx, target, buffer,attrib_list); +} + +EGLBoolean DrmEglServerBufferIntegration::eglDestroyImageKHR (EGLImageKHR image) +{ + if (!m_egl_destroy_image) { + qWarning("DrmEglServerBufferIntegration: Trying to use unresolved function eglDestroyImageKHR"); + return false; + } + return m_egl_destroy_image(m_egl_display, image); +} + +void DrmEglServerBufferIntegration::glEGLImageTargetTexture2DOES (GLenum target, GLeglImageOES image) +{ + if (!m_gl_egl_image_target_texture) { + qWarning("DrmEglServerBufferIntegration: Trying to use unresolved function glEGLImageTargetRenderbufferStorageOES"); + return; + } + m_gl_egl_image_target_texture(target,image); +} + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/main.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/main.cpp new file mode 100644 index 00000000000..008d2ebd147 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/drm-egl-server/main.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2016 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 + +#include +#include "drmeglserverbufferintegration.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class DrmEglServerBufferPlugin : public QWaylandServerBufferIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandServerBufferIntegrationFactoryInterface_iid FILE "drm-egl-server.json") +public: + QWaylandServerBufferIntegration *create(const QString&, const QStringList&) override; +}; + +QWaylandServerBufferIntegration *DrmEglServerBufferPlugin::create(const QString& system, const QStringList& paramList) +{ + Q_UNUSED(paramList); + Q_UNUSED(system); + return new DrmEglServerBufferIntegration(); +} + +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/CMakeLists.txt new file mode 100644 index 00000000000..66989154826 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from libhybris-egl-server.pro. + +##################################################################### +## LibHybrisEglServerBufferPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(LibHybrisEglServerBufferPlugin + OUTPUT_NAME libhybris-egl-server + PLUGIN_TYPE wayland-graphics-integration-client + SOURCES + libhybriseglserverbufferintegration.cpp libhybriseglserverbufferintegration.h + main.cpp + PUBLIC_LIBRARIES + EGL::EGL + Qt::Core + Qt::Gui + Qt::WaylandClientPrivate + Wayland::Client + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 +) + +qt6_generate_wayland_protocol_client_sources(LibHybrisEglServerBufferPlugin + PRIVATE_CODE + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/extensions/libhybris-egl-server-buffer.xml +) + +#### Keys ignored in scope 1:.:.:libhybris-egl-server.pro:: +# OTHER_FILES = "libhybris-egl-server.json" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/libhybris-egl-server.json b/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/libhybris-egl-server.json new file mode 100644 index 00000000000..3a1f4c08a6a --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/libhybris-egl-server.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "libhybris-egl-server" ] +} diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/libhybriseglserverbufferintegration.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/libhybriseglserverbufferintegration.cpp new file mode 100644 index 00000000000..54081de97af --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/libhybriseglserverbufferintegration.cpp @@ -0,0 +1,167 @@ +// Copyright (C) 2016 Jolla Ltd, author: +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "libhybriseglserverbufferintegration.h" +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +LibHybrisServerBuffer::LibHybrisServerBuffer(LibHybrisEglServerBufferIntegration *integration + , int32_t numFds + , wl_array *ints + , int32_t name + , int32_t width + , int32_t height + , int32_t stride + , int32_t format) + : QWaylandServerBuffer() + , m_integration(integration) + , m_stride(stride) + , m_hybrisFormat(format) +{ + m_numFds = numFds; + m_fds.reserve(numFds); + m_ints.resize(ints->size / sizeof(int32_t)); + memcpy(m_ints.data(), ints->data, ints->size); + m_image = 0; + + m_size = QSize(width, height); +} + +LibHybrisServerBuffer::~LibHybrisServerBuffer() +{ + m_integration->eglDestroyImageKHR(m_image); +} + +QOpenGLTexture * LibHybrisServerBuffer::toOpenGlTexture() +{ + if (!QOpenGLContext::currentContext()) { + qWarning("LibHybrisServerBuffer: creating texture with no current context"); + } + + if (!m_texture) { + m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + m_texture->create(); + } + + m_texture->bind(); + m_integration->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + m_texture->release(); + m_texture->setSize(m_size.width(), m_size.height()); + + return m_texture; +} + +void LibHybrisServerBuffer::libhybris_buffer_add_fd(int32_t fd) +{ + m_fds << fd; + + if (m_fds.size() == m_numFds) { + EGLint egl_format; + switch (m_hybrisFormat) { + case QtWayland::qt_libhybris_egl_server_buffer::format_RGBA32: + m_format = QWaylandServerBuffer::RGBA32; + egl_format = HYBRIS_PIXEL_FORMAT_RGBA_8888; + break; + default: + qWarning("LibHybrisServerBuffer: unknown format"); + m_format = QWaylandServerBuffer::RGBA32; + egl_format = HYBRIS_PIXEL_FORMAT_RGBA_8888; + break; + } + + EGLClientBuffer buf; + m_integration->eglHybrisCreateRemoteBuffer(m_size.width(), m_size.height(), HYBRIS_USAGE_HW_TEXTURE, egl_format, m_stride, m_ints.size(), m_ints.data(), m_fds.size(), m_fds.data(), &buf); + m_image = m_integration->eglCreateImageKHR(EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, buf, 0); + } +} + +void LibHybrisEglServerBufferIntegration::initializeEgl() +{ + if (m_egl_initialized) + return; + m_egl_initialized = true; + + m_egl_display = eglGetDisplay((EGLNativeDisplayType) m_display->wl_display()); + if (m_egl_display == EGL_NO_DISPLAY) { + qWarning("Failed to initialize libhybris egl server buffer integration. Could not get egl display from wl_display."); + return; + } + + const char *extensionString = eglQueryString(m_egl_display, EGL_EXTENSIONS); + if (!extensionString || !strstr(extensionString, "EGL_KHR_image")) { + qWarning("Failed to initialize libhybris egl server buffer integration. There is no EGL_KHR_image extension.\n"); + return; + } + m_egl_create_image = reinterpret_cast(eglGetProcAddress("eglCreateImageKHR")); + m_egl_destroy_image = reinterpret_cast(eglGetProcAddress("eglDestroyImageKHR")); + if (!m_egl_create_image || !m_egl_destroy_image) { + qWarning("Failed to initialize libhybris egl server buffer integration. Could not resolve eglCreateImageKHR or eglDestroyImageKHR"); + return; + } + + m_gl_egl_image_target_texture = reinterpret_cast(eglGetProcAddress("glEGLImageTargetTexture2DOES")); + if (!m_gl_egl_image_target_texture) { + qWarning("Failed to initialize libhybris egl server buffer integration. Could not resolve glEGLImageTargetTexture2DOES"); + return; + } + + m_egl_create_buffer = reinterpret_cast(eglGetProcAddress("eglHybrisCreateRemoteBuffer")); + if (!m_egl_create_buffer) { + qWarning("Failed to initialize libhybris egl server buffer integration. Could not resolve eglHybrisCreateRemoteBuffer"); + return; + } + m_egl_initialized = true; +} + +void LibHybrisEglServerBufferIntegration::initialize(QWaylandDisplay *display) +{ + m_display = display; + display->addRegistryListener(&wlDisplayHandleGlobal, this); +} + +QWaylandServerBuffer *LibHybrisEglServerBufferIntegration::serverBuffer(struct qt_server_buffer *buffer) +{ + return static_cast(qt_server_buffer_get_user_data(buffer)); +} + +void LibHybrisEglServerBufferIntegration::wlDisplayHandleGlobal(void *data, ::wl_registry *registry, uint32_t id, const QString &interface, uint32_t version) +{ + Q_UNUSED(version); + if (interface == QStringLiteral("qt_libhybris_egl_server_buffer")) { + auto *integration = static_cast(data); + integration->QtWayland::qt_libhybris_egl_server_buffer::init(registry, id, 1); + } +} + +void LibHybrisEglServerBufferIntegration::libhybris_egl_server_buffer_server_buffer_created(struct ::qt_libhybris_buffer *id + , struct ::qt_server_buffer *qid + , int32_t numFds + , wl_array *ints + , int32_t name + , int32_t width + , int32_t height + , int32_t stride + , int32_t format) +{ + LibHybrisServerBuffer *server_buffer = new LibHybrisServerBuffer(this, numFds, ints, name, width, height, stride, format); + server_buffer->QtWayland::qt_libhybris_buffer::init(id); + qt_server_buffer_set_user_data(qid, server_buffer); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/libhybriseglserverbufferintegration.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/libhybriseglserverbufferintegration.h new file mode 100644 index 00000000000..7fd4a2abbf8 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/libhybriseglserverbufferintegration.h @@ -0,0 +1,133 @@ +// Copyright (C) 2016 Jolla Ltd, author: +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef LIBHYBRISEGLSERVERBUFFERINTEGRATION_H +#define LIBHYBRISEGLSERVERBUFFERINTEGRATION_H + +#include +#include "qwayland-libhybris-egl-server-buffer.h" +#include +#include +#include +#include + +#include +#include +#ifndef EGL_KHR_image +typedef void *EGLImageKHR; +typedef EGLImageKHR (EGLAPIENTRYP PFNEGLCREATEIMAGEKHRPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay dpy, EGLImageKHR image); +#endif + +#ifndef GL_OES_EGL_image +typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) (GLenum target, GLeglImageOES image); +#endif + +#ifndef EGL_HYBRIS_native_buffer +typedef EGLBoolean (EGLAPIENTRYP PFNEGLHYBRISCREATEREMOTEBUFFERPROC)(EGLint width, EGLint height, EGLint usage, EGLint format, EGLint stride, + int num_ints, int *ints, int num_fds, int *fds, EGLClientBuffer *buffer); +#endif + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class LibHybrisEglServerBufferIntegration; + +class LibHybrisServerBuffer : public QWaylandServerBuffer, public QtWayland::qt_libhybris_buffer +{ +public: + LibHybrisServerBuffer(LibHybrisEglServerBufferIntegration *integration, int32_t numFds, wl_array *ints, int32_t name, int32_t width, int32_t height, int32_t stride, int32_t format); + ~LibHybrisServerBuffer(); + QOpenGLTexture* toOpenGlTexture() override; + +protected: + void libhybris_buffer_add_fd(int32_t fd) override; + +private: + LibHybrisEglServerBufferIntegration *m_integration = nullptr; + EGLImageKHR m_image; + QOpenGLTexture *m_texture = nullptr; + int m_numFds; + QList m_ints; + QList m_fds; + int32_t m_stride; + int32_t m_hybrisFormat; +}; + +class LibHybrisEglServerBufferIntegration + : public QWaylandServerBufferIntegration + , public QtWayland::qt_libhybris_egl_server_buffer +{ +public: + void initialize(QWaylandDisplay *display) override; + + virtual QWaylandServerBuffer *serverBuffer(struct qt_server_buffer *buffer) override; + + inline EGLImageKHR eglCreateImageKHR(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list); + inline EGLBoolean eglDestroyImageKHR (EGLImageKHR image); + inline void glEGLImageTargetTexture2DOES (GLenum target, GLeglImageOES image); + inline EGLBoolean eglHybrisCreateRemoteBuffer(EGLint width, EGLint height, EGLint usage, EGLint format, EGLint stride, int num_ints, int *ints, int num_fds, int *fds, EGLClientBuffer *buffer); + +protected: + void libhybris_egl_server_buffer_server_buffer_created(struct ::qt_libhybris_buffer *id, struct ::qt_server_buffer *qid, + int32_t numFds, wl_array *ints, int32_t name, int32_t width, int32_t height, int32_t stride, int32_t format) override; +private: + static void wlDisplayHandleGlobal(void *data, struct ::wl_registry *registry, uint32_t id, + const QString &interface, uint32_t version); + void initializeEgl(); + + PFNEGLCREATEIMAGEKHRPROC m_egl_create_image; + PFNEGLDESTROYIMAGEKHRPROC m_egl_destroy_image; + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_gl_egl_image_target_texture; + PFNEGLHYBRISCREATEREMOTEBUFFERPROC m_egl_create_buffer; + QWaylandDisplay *m_display = nullptr; + EGLDisplay m_egl_display = EGL_NO_DISPLAY; + bool m_egl_initialized = false; +}; + +EGLImageKHR LibHybrisEglServerBufferIntegration::eglCreateImageKHR(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list) +{ + if (!m_egl_initialized) + initializeEgl(); + + if (!m_egl_create_image) { + qWarning("LibHybrisEglServerBufferIntegration: Trying to used unresolved function eglCreateImageKHR"); + return EGL_NO_IMAGE_KHR; + } + return m_egl_create_image(m_egl_display, ctx, target, buffer,attrib_list); +} + +EGLBoolean LibHybrisEglServerBufferIntegration::eglDestroyImageKHR (EGLImageKHR image) +{ + if (!m_egl_destroy_image) { + qWarning("LibHybrisEglServerBufferIntegration: Trying to use unresolved function eglDestroyImageKHR"); + return false; + } + return m_egl_destroy_image(m_egl_display, image); +} + +void LibHybrisEglServerBufferIntegration::glEGLImageTargetTexture2DOES (GLenum target, GLeglImageOES image) +{ + if (!m_gl_egl_image_target_texture) { + qWarning("LibHybrisEglServerBufferIntegration: Trying to use unresolved function glEGLImageTargetRenderbufferStorageOES"); + return; + } + m_gl_egl_image_target_texture(target,image); +} + +EGLBoolean LibHybrisEglServerBufferIntegration::eglHybrisCreateRemoteBuffer(EGLint width, EGLint height, EGLint usage, EGLint format, EGLint stride, + int num_ints, int *ints, int num_fds, int *fds, EGLClientBuffer *buffer) +{ + if (!m_egl_create_buffer) { + qWarning("LibHybrisEglServerBufferIntegration: Trying to use unresolved function eglHybrisCreateRemoteBuffer"); + return false; + } + return m_egl_create_buffer(width, height, usage, format, stride, num_ints, ints, num_fds, fds, buffer); +} + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/main.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/main.cpp new file mode 100644 index 00000000000..fa36e1a1da9 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/libhybris-egl-server/main.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2016 Jolla Ltd, author: +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include +#include "libhybriseglserverbufferintegration.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class LibHybrisEglServerBufferPlugin : public QWaylandServerBufferIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandServerBufferIntegrationFactoryInterface_iid FILE "libhybris-egl-server.json") +public: + QWaylandServerBufferIntegration *create(const QString&, const QStringList&) override; +}; + +QWaylandServerBufferIntegration *LibHybrisEglServerBufferPlugin::create(const QString& system, const QStringList& paramList) +{ + Q_UNUSED(paramList); + Q_UNUSED(system); + return new LibHybrisEglServerBufferIntegration(); +} + +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/CMakeLists.txt new file mode 100644 index 00000000000..481d1de5877 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/CMakeLists.txt @@ -0,0 +1,34 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from shm-emulation-server.pro. + +##################################################################### +## ShmServerBufferPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(ShmServerBufferPlugin + OUTPUT_NAME shm-emulation-server + PLUGIN_TYPE wayland-graphics-integration-client + SOURCES + shmserverbufferintegration.cpp shmserverbufferintegration.h + main.cpp + LIBRARIES + Qt::Core + Qt::Gui + Qt::OpenGL + Qt::WaylandClientPrivate + Wayland::Client + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 +) + +qt6_generate_wayland_protocol_client_sources(ShmServerBufferPlugin + PRIVATE_CODE + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/extensions/server-buffer-extension.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/extensions/shm-emulation-server-buffer.xml +) + +#### Keys ignored in scope 1:.:.:shm-emulation-server.pro:: +# OTHER_FILES = "shm-emulation-server.json" +# QMAKE_CXXFLAGS_WARN_ON = "--Wcast-qual" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/main.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/main.cpp new file mode 100644 index 00000000000..89673e8463a --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/main.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2017 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 + +#include +#include "shmserverbufferintegration.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class ShmServerBufferPlugin : public QWaylandServerBufferIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandServerBufferIntegrationFactoryInterface_iid FILE "shm-emulation-server.json") +public: + QWaylandServerBufferIntegration *create(const QString&, const QStringList&) override; +}; + +QWaylandServerBufferIntegration *ShmServerBufferPlugin::create(const QString& system, const QStringList& paramList) +{ + Q_UNUSED(paramList); + Q_UNUSED(system); + return new ShmServerBufferIntegration(); +} + +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shm-emulation-server.json b/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shm-emulation-server.json new file mode 100644 index 00000000000..1bf8ded8754 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shm-emulation-server.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "shm-emulation-server" ] +} diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shmserverbufferintegration.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shmserverbufferintegration.cpp new file mode 100644 index 00000000000..38065dbde16 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shmserverbufferintegration.cpp @@ -0,0 +1,107 @@ +// Copyright (C) 2017 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 + +#include "shmserverbufferintegration.h" +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static QOpenGLTexture *createTextureFromShm(const QString &key, int w, int h, int bpl, int format) +{ + QT_IGNORE_DEPRECATIONS(QSharedMemory shm(key);) + bool ok; + ok = shm.attach(QSharedMemory::ReadOnly); + if (!ok) { + qWarning() << "Could not attach to" << key; + return nullptr; + } + ok = shm.lock(); + if (!ok) { + qWarning() << "Could not lock" << key << "for reading"; + return nullptr; + } + + QImage::Format imageFormat; + switch (format) { + case QtWayland::qt_shm_emulation_server_buffer::format_RGBA32: + imageFormat = QImage::Format_RGBA8888; + break; + case QtWayland::qt_shm_emulation_server_buffer::format_A8: + imageFormat = QImage::Format_Alpha8; + break; + default: + qWarning() << "ShmServerBuffer: unknown format" << format; + imageFormat = QImage::Format_RGBA8888; + break; + } + + QImage image(static_cast(shm.constData()), w, h, bpl, imageFormat); + + if (!QOpenGLContext::currentContext()) + qWarning("ShmServerBuffer: creating texture with no current context"); + + auto *tex = new QOpenGLTexture(image, QOpenGLTexture::DontGenerateMipMaps); + shm.unlock(); + return tex; +} + + +namespace QtWaylandClient { + +ShmServerBuffer::ShmServerBuffer(const QString &key, const QSize& size, int bytesPerLine, QWaylandServerBuffer::Format format) + : m_key(key) + , m_bpl(bytesPerLine) +{ + m_format = format; + m_size = size; +} + +ShmServerBuffer::~ShmServerBuffer() +{ +} + +QOpenGLTexture *ShmServerBuffer::toOpenGlTexture() +{ + if (!m_texture) + m_texture = createTextureFromShm(m_key, m_size.width(), m_size.height(), m_bpl, m_format); + + return m_texture; +} + +void ShmServerBufferIntegration::initialize(QWaylandDisplay *display) +{ + m_display = display; + display->addRegistryListener(&wlDisplayHandleGlobal, this); +} + +QWaylandServerBuffer *ShmServerBufferIntegration::serverBuffer(struct qt_server_buffer *buffer) +{ + return static_cast(qt_server_buffer_get_user_data(buffer)); +} + +void ShmServerBufferIntegration::wlDisplayHandleGlobal(void *data, ::wl_registry *registry, uint32_t id, const QString &interface, uint32_t version) +{ + Q_UNUSED(version); + if (interface == "qt_shm_emulation_server_buffer") { + auto *integration = static_cast(data); + integration->QtWayland::qt_shm_emulation_server_buffer::init(registry, id, 1); + } +} + + +void QtWaylandClient::ShmServerBufferIntegration::shm_emulation_server_buffer_server_buffer_created(qt_server_buffer *id, const QString &key, int32_t width, int32_t height, int32_t bytes_per_line, int32_t format) +{ + QSize size(width, height); + auto fmt = QWaylandServerBuffer::Format(format); + auto *server_buffer = new ShmServerBuffer(key, size, bytes_per_line, fmt); + qt_server_buffer_set_user_data(id, server_buffer); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shmserverbufferintegration.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shmserverbufferintegration.h new file mode 100644 index 00000000000..344046ae182 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/shm-emulation-server/shmserverbufferintegration.h @@ -0,0 +1,55 @@ +// Copyright (C) 2017 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 + +#ifndef SHMSERVERBUFFERINTEGRATION_H +#define SHMSERVERBUFFERINTEGRATION_H + +#include +#include "qwayland-shm-emulation-server-buffer.h" +#include + +#include "shmserverbufferintegration.h" +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class ShmServerBufferIntegration; + +class ShmServerBuffer : public QWaylandServerBuffer +{ +public: + ShmServerBuffer(const QString &key, const QSize &size, int bytesPerLine, QWaylandServerBuffer::Format format); + ~ShmServerBuffer() override; + QOpenGLTexture* toOpenGlTexture() override; +private: + QOpenGLTexture *m_texture = nullptr; + QString m_key; + int m_bpl; +}; + +class ShmServerBufferIntegration + : public QWaylandServerBufferIntegration + , public QtWayland::qt_shm_emulation_server_buffer +{ +public: + void initialize(QWaylandDisplay *display) override; + + QWaylandServerBuffer *serverBuffer(struct qt_server_buffer *buffer) override; + +protected: + void shm_emulation_server_buffer_server_buffer_created(qt_server_buffer *id, const QString &key, int32_t width, int32_t height, int32_t bytes_per_line, int32_t format) override; + +private: + static void wlDisplayHandleGlobal(void *data, struct ::wl_registry *registry, uint32_t id, + const QString &interface, uint32_t version); + QWaylandDisplay *m_display = nullptr; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/CMakeLists.txt new file mode 100644 index 00000000000..a6207d73ba5 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from vulkan-server.pro. + +##################################################################### +## VulkanServerBufferPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(VulkanServerBufferPlugin + OUTPUT_NAME vulkan-server + PLUGIN_TYPE wayland-graphics-integration-client + SOURCES + vulkanserverbufferintegration.cpp vulkanserverbufferintegration.h + main.cpp + LIBRARIES + Qt::Core + Qt::Gui + Qt::OpenGL + Qt::WaylandClientPrivate + Wayland::Client + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 +) + +qt6_generate_wayland_protocol_client_sources(VulkanServerBufferPlugin + PRIVATE_CODE + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/extensions/qt-vulkan-server-buffer-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/extensions/server-buffer-extension.xml + +) + +#### Keys ignored in scope 1:.:.:vulkan-server.pro:: +# OTHER_FILES = "vulkan-server.json" +# QMAKE_CXXFLAGS_WARN_ON = "--Wcast-qual" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/main.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/main.cpp new file mode 100644 index 00000000000..0490707e08e --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/main.cpp @@ -0,0 +1,30 @@ +// 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 + +#include +#include "vulkanserverbufferintegration.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class VulkanServerBufferPlugin : public QWaylandServerBufferIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandServerBufferIntegrationFactoryInterface_iid FILE "vulkan-server.json") +public: + QWaylandServerBufferIntegration *create(const QString&, const QStringList&) override; +}; + +QWaylandServerBufferIntegration *VulkanServerBufferPlugin::create(const QString& key, const QStringList& paramList) +{ + Q_UNUSED(paramList); + Q_UNUSED(key); + return new VulkanServerBufferIntegration(); +} + +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkan-server.json b/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkan-server.json new file mode 100644 index 00000000000..baadd152928 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkan-server.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "vulkan-server" ] +} diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkanserverbufferintegration.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkanserverbufferintegration.cpp new file mode 100644 index 00000000000..8f1ff9a46d3 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkanserverbufferintegration.cpp @@ -0,0 +1,177 @@ +// 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 + +#include "vulkanserverbufferintegration.h" +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +static constexpr bool sbiExtraDebug = +#ifdef VULKAN_SERVER_BUFFER_EXTRA_DEBUG + true; +#else + false; +#endif + +#define DECL_GL_FUNCTION(name, type) \ + type name + +#define FIND_GL_FUNCTION(name, type) \ + do { \ + name = reinterpret_cast(glContext->getProcAddress(#name)); \ + if (!name) { \ + qWarning() << "ERROR in GL proc lookup. Could not find " #name; \ + return false; \ + } \ + } while (0) + +struct VulkanServerBufferGlFunctions +{ + DECL_GL_FUNCTION(glCreateMemoryObjectsEXT, PFNGLCREATEMEMORYOBJECTSEXTPROC); + DECL_GL_FUNCTION(glImportMemoryFdEXT, PFNGLIMPORTMEMORYFDEXTPROC); + DECL_GL_FUNCTION(glTextureStorageMem2DEXT, PFNGLTEXTURESTORAGEMEM2DEXTPROC); + DECL_GL_FUNCTION(glTexStorageMem2DEXT, PFNGLTEXSTORAGEMEM2DEXTPROC); + DECL_GL_FUNCTION(glDeleteMemoryObjectsEXT, PFNGLDELETEMEMORYOBJECTSEXTPROC); + + bool init(QOpenGLContext *glContext) + { + FIND_GL_FUNCTION(glCreateMemoryObjectsEXT, PFNGLCREATEMEMORYOBJECTSEXTPROC); + FIND_GL_FUNCTION(glImportMemoryFdEXT, PFNGLIMPORTMEMORYFDEXTPROC); + FIND_GL_FUNCTION(glTextureStorageMem2DEXT, PFNGLTEXTURESTORAGEMEM2DEXTPROC); + FIND_GL_FUNCTION(glTexStorageMem2DEXT, PFNGLTEXSTORAGEMEM2DEXTPROC); + FIND_GL_FUNCTION(glDeleteMemoryObjectsEXT, PFNGLDELETEMEMORYOBJECTSEXTPROC); + + return true; + } + static bool create(QOpenGLContext *glContext); +}; + +static VulkanServerBufferGlFunctions *funcs = nullptr; + +bool VulkanServerBufferGlFunctions::create(QOpenGLContext *glContext) +{ + if (funcs) + return true; + funcs = new VulkanServerBufferGlFunctions; + if (!funcs->init(glContext)) { + delete funcs; + funcs = nullptr; + return false; + } + return true; +} + +VulkanServerBuffer::VulkanServerBuffer(VulkanServerBufferIntegration *integration, struct ::qt_server_buffer *id, + int32_t fd, uint32_t width, uint32_t height, uint32_t memory_size, uint32_t format) + : m_integration(integration) + , m_server_buffer(id) + , m_fd(fd) + , m_memorySize(memory_size) + , m_internalFormat(format) +{ + m_size = QSize(width, height); +} + +VulkanServerBuffer::~VulkanServerBuffer() +{ + if (QCoreApplication::closingDown()) + return; // can't trust anything at this point + + if (m_texture) { //only do gl cleanup if import has been called + m_integration->deleteGLTextureWhenPossible(m_texture); + + if (sbiExtraDebug) qDebug() << "glDeleteMemoryObjectsEXT" << m_memoryObject; + funcs->glDeleteMemoryObjectsEXT(1, &m_memoryObject); + } + qt_server_buffer_release(m_server_buffer); + qt_server_buffer_destroy(m_server_buffer); +} + +void VulkanServerBuffer::import() +{ + if (m_texture) + return; + + if (sbiExtraDebug) qDebug() << "importing" << m_fd << Qt::hex << glGetError(); + + auto *glContext = QOpenGLContext::currentContext(); + if (!glContext) + return; + + if (!funcs && !VulkanServerBufferGlFunctions::create(glContext)) + return; + + funcs->glCreateMemoryObjectsEXT(1, &m_memoryObject); + if (sbiExtraDebug) qDebug() << "glCreateMemoryObjectsEXT" << Qt::hex << glGetError(); + funcs->glImportMemoryFdEXT(m_memoryObject, m_memorySize, GL_HANDLE_TYPE_OPAQUE_FD_EXT, m_fd); + if (sbiExtraDebug) qDebug() << "glImportMemoryFdEXT" << Qt::hex << glGetError(); + + + m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + m_texture->create(); + + if (sbiExtraDebug) qDebug() << "created texture" << m_texture->textureId() << Qt::hex << glGetError(); + + m_texture->bind(); + if (sbiExtraDebug) qDebug() << "bound texture" << Qt::hex << glGetError(); + funcs->glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, m_internalFormat, m_size.width(), m_size.height(), m_memoryObject, 0 ); + if (sbiExtraDebug) qDebug() << "glTexStorageMem2DEXT" << Qt::hex << glGetError(); + if (sbiExtraDebug) qDebug() << "format" << Qt::hex << m_internalFormat << GL_RGBA8; +} + +QOpenGLTexture *VulkanServerBuffer::toOpenGlTexture() +{ + m_integration->deleteOrphanedTextures(); + if (!m_texture) + import(); + return m_texture; +} + +void VulkanServerBufferIntegration::initialize(QWaylandDisplay *display) +{ + m_display = display; + display->addRegistryListener(&wlDisplayHandleGlobal, this); +} + +QWaylandServerBuffer *VulkanServerBufferIntegration::serverBuffer(struct qt_server_buffer *buffer) +{ + return static_cast(qt_server_buffer_get_user_data(buffer)); +} + +void VulkanServerBufferIntegration::wlDisplayHandleGlobal(void *data, ::wl_registry *registry, uint32_t id, const QString &interface, uint32_t version) +{ + Q_UNUSED(version); + if (interface == "zqt_vulkan_server_buffer_v1") { + auto *integration = static_cast(data); + integration->QtWayland::zqt_vulkan_server_buffer_v1::init(registry, id, 1); + } +} + +void VulkanServerBufferIntegration::zqt_vulkan_server_buffer_v1_server_buffer_created(qt_server_buffer *id, int32_t fd, uint32_t width, uint32_t height, uint32_t memory_size, uint32_t format) +{ + if (sbiExtraDebug) qDebug() << "vulkan_server_buffer_server_buffer_created" << fd; + auto *server_buffer = new VulkanServerBuffer(this, id, fd, width, height, memory_size, format); + qt_server_buffer_set_user_data(id, server_buffer); +} + +void VulkanServerBufferIntegration::deleteOrphanedTextures() +{ + if (!QOpenGLContext::currentContext()) { + qWarning("VulkanServerBufferIntegration::deleteOrphanedTextures with no current context!"); + return; + } + qDeleteAll(orphanedTextures); + orphanedTextures.clear(); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkanserverbufferintegration.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkanserverbufferintegration.h new file mode 100644 index 00000000000..2f0867a8197 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/vulkan-server/vulkanserverbufferintegration.h @@ -0,0 +1,66 @@ +// 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 + +#ifndef VULKANSERVERBUFFERINTEGRATION_H +#define VULKANSERVERBUFFERINTEGRATION_H + +#include +#include "qwayland-qt-vulkan-server-buffer-unstable-v1.h" +#include + +#include "vulkanserverbufferintegration.h" +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class VulkanServerBufferIntegration; + +class VulkanServerBuffer : public QWaylandServerBuffer +{ +public: + VulkanServerBuffer(VulkanServerBufferIntegration *integration, struct ::qt_server_buffer *id, int32_t fd, uint32_t width, uint32_t height, uint32_t memory_size, uint32_t format); + ~VulkanServerBuffer() override; + QOpenGLTexture* toOpenGlTexture() override; + +private: + void import(); + + VulkanServerBufferIntegration *m_integration = nullptr; + struct ::qt_server_buffer *m_server_buffer = nullptr; + QOpenGLTexture *m_texture = nullptr; + int m_fd = -1; + uint m_memorySize = 0; + uint m_internalFormat = 0; + GLuint m_memoryObject = 0; +}; + +class VulkanServerBufferIntegration + : public QWaylandServerBufferIntegration + , public QtWayland::zqt_vulkan_server_buffer_v1 +{ +public: + void initialize(QWaylandDisplay *display) override; + + QWaylandServerBuffer *serverBuffer(struct qt_server_buffer *buffer) override; + + void deleteGLTextureWhenPossible(QOpenGLTexture *texture) { orphanedTextures << texture; } + void deleteOrphanedTextures(); + +protected: + void zqt_vulkan_server_buffer_v1_server_buffer_created(qt_server_buffer *id, int32_t fd, uint32_t width, uint32_t height, uint32_t memory_size, uint32_t format) override; + +private: + static void wlDisplayHandleGlobal(void *data, struct ::wl_registry *registry, uint32_t id, + const QString &interface, uint32_t version); + QWaylandDisplay *m_display = nullptr; + QList orphanedTextures; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/CMakeLists.txt new file mode 100644 index 00000000000..ef3df07a434 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from wayland-egl.pro. + +##################################################################### +## QWaylandEglClientBufferPlugin Plugin: +##################################################################### +qt_find_package(EGL) # special case + +qt_internal_add_plugin(QWaylandEglClientBufferPlugin + OUTPUT_NAME qt-plugin-wayland-egl + PLUGIN_TYPE wayland-graphics-integration-client + SOURCES + main.cpp + qwaylandeglclientbufferintegration.cpp qwaylandeglclientbufferintegration_p.h + qwaylandeglinclude_p.h + qwaylandeglwindow.cpp qwaylandeglwindow_p.h + qwaylandglcontext.cpp qwaylandglcontext_p.h + LIBRARIES + ${CMAKE_DL_LIBS} + EGL::EGL + Qt::Core + Qt::Gui + Qt::OpenGLPrivate + Qt::WaylandClientPrivate + Wayland::Client + Wayland::Egl + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 +) + +#### Keys ignored in scope 1:.:.:wayland-egl.pro:: +# OTHER_FILES = "wayland-egl.json" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/main.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/main.cpp new file mode 100644 index 00000000000..4cb5930bc3b --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/main.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2016 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 + +#include +#include "qwaylandeglclientbufferintegration_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandEglClientBufferPlugin : public QWaylandClientBufferIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandClientBufferIntegrationFactoryInterface_iid FILE "wayland-egl.json") +public: + QWaylandClientBufferIntegration *create(const QString&, const QStringList&) override; +}; + +QWaylandClientBufferIntegration *QWaylandEglClientBufferPlugin::create(const QString&, const QStringList&) +{ + return new QWaylandEglClientBufferIntegration(); +} + +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglclientbufferintegration.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglclientbufferintegration.cpp new file mode 100644 index 00000000000..3b97aef208b --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglclientbufferintegration.cpp @@ -0,0 +1,166 @@ +// Copyright (C) 2016 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 + +#include "qwaylandeglclientbufferintegration_p.h" + +#include "qwaylandeglwindow_p.h" +#include "qwaylandglcontext_p.h" + +#include + +#include +#include + +#ifndef EGL_EXT_platform_base +typedef EGLDisplay (*PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list); +#endif + +#ifndef EGL_PLATFORM_WAYLAND_KHR +#define EGL_PLATFORM_WAYLAND_KHR 0x31D8 +#endif + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +static const char *qwaylandegl_threadedgl_blacklist_vendor[] = { + 0 +}; + +QWaylandEglClientBufferIntegration::QWaylandEglClientBufferIntegration() +{ + qCDebug(lcQpaWayland) << "Using Wayland-EGL"; +} + + +QWaylandEglClientBufferIntegration::~QWaylandEglClientBufferIntegration() +{ + eglTerminate(m_eglDisplay); +} + +void QWaylandEglClientBufferIntegration::initialize(QWaylandDisplay *display) +{ +#if QT_CONFIG(egl_extension_platform_wayland) + m_eglDisplay = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_EXT, display->wl_display(), nullptr); +#else + if (q_hasEglExtension(EGL_NO_DISPLAY, "EGL_EXT_platform_base")) { + if (q_hasEglExtension(EGL_NO_DISPLAY, "EGL_KHR_platform_wayland") || + q_hasEglExtension(EGL_NO_DISPLAY, "EGL_EXT_platform_wayland") || + q_hasEglExtension(EGL_NO_DISPLAY, "EGL_MESA_platform_wayland")) { + + static PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplay = nullptr; + if (!eglGetPlatformDisplay) + eglGetPlatformDisplay = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); + + m_eglDisplay = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, display->wl_display(), nullptr); + } else { + qCWarning(lcQpaWayland) << "The EGL implementation does not support the Wayland platform"; + return; + } + } else { + QByteArray eglPlatform = qgetenv("EGL_PLATFORM"); + if (eglPlatform.isEmpty()) { + setenv("EGL_PLATFORM","wayland",true); + } + + m_eglDisplay = eglGetDisplay((EGLNativeDisplayType) display->wl_display()); + } +#endif + + m_display = display; + + if (m_eglDisplay == EGL_NO_DISPLAY) { + qCWarning(lcQpaWayland) << "EGL not available"; + return; + } + + EGLint major,minor; + if (!eglInitialize(m_eglDisplay, &major, &minor)) { + qCWarning(lcQpaWayland) << "Failed to initialize EGL display" << Qt::hex << eglGetError(); + m_eglDisplay = EGL_NO_DISPLAY; + return; + } + + m_supportsThreading = true; + if (qEnvironmentVariableIsSet("QT_OPENGL_NO_SANITY_CHECK")) + return; + + const char *vendor = eglQueryString(m_eglDisplay, EGL_VENDOR); + for (int i = 0; qwaylandegl_threadedgl_blacklist_vendor[i]; ++i) { + if (strstr(vendor, qwaylandegl_threadedgl_blacklist_vendor[i]) != 0) { + m_supportsThreading = false; + break; + } + } + + // On desktop NVIDIA resizing QtQuick freezes them when using threaded rendering QTBUG-95817 + // In order to support threaded rendering on embedded platforms where resizing is not needed + // we check if XDG_CURRENT_DESKTOP is set which desktop environments should set + if (qstrcmp(vendor, "NVIDIA") == 0 && qEnvironmentVariableIsSet("XDG_CURRENT_DESKTOP")) { + m_supportsThreading = false; + } +} + +bool QWaylandEglClientBufferIntegration::isValid() const +{ + return m_eglDisplay != EGL_NO_DISPLAY; +} + +bool QWaylandEglClientBufferIntegration::supportsThreadedOpenGL() const +{ + return m_supportsThreading; +} + +bool QWaylandEglClientBufferIntegration::supportsWindowDecoration() const +{ + return true; +} + +QWaylandWindow *QWaylandEglClientBufferIntegration::createEglWindow(QWindow *window) +{ + return new QWaylandEglWindow(window, m_display); +} + +QPlatformOpenGLContext *QWaylandEglClientBufferIntegration::createPlatformOpenGLContext(const QSurfaceFormat &glFormat, QPlatformOpenGLContext *share) const +{ + QSurfaceFormat fmt = glFormat; + if (m_display->supportsWindowDecoration()) + fmt.setAlphaBufferSize(8); + return new QWaylandGLContext(m_eglDisplay, m_display, fmt, share); +} + +void *QWaylandEglClientBufferIntegration::nativeResource(NativeResource resource) +{ + switch (resource) { + case EglDisplay: + return m_eglDisplay; + default: + break; + } + return nullptr; +} + +void *QWaylandEglClientBufferIntegration::nativeResourceForContext(NativeResource resource, QPlatformOpenGLContext *context) +{ + Q_ASSERT(context); + switch (resource) { + case EglConfig: + return static_cast(context)->eglConfig(); + case EglContext: + return static_cast(context)->eglContext(); + case EglDisplay: + return m_eglDisplay; + default: + break; + } + return nullptr; +} + +EGLDisplay QWaylandEglClientBufferIntegration::eglDisplay() const +{ + return m_eglDisplay; +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglclientbufferintegration_p.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglclientbufferintegration_p.h new file mode 100644 index 00000000000..ed8fdec2d94 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglclientbufferintegration_p.h @@ -0,0 +1,60 @@ +// Copyright (C) 2016 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 + +// +// 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. +// + +#ifndef QWAYLANDEGLINTEGRATION_H +#define QWAYLANDEGLINTEGRATION_H + +#include + +#include "qwaylandeglinclude_p.h" + +QT_BEGIN_NAMESPACE + +class QWindow; + +namespace QtWaylandClient { + +class QWaylandWindow; + +class Q_WAYLANDCLIENT_EXPORT QWaylandEglClientBufferIntegration : public QWaylandClientBufferIntegration +{ +public: + QWaylandEglClientBufferIntegration(); + ~QWaylandEglClientBufferIntegration() override; + + void initialize(QWaylandDisplay *display) override; + bool isValid() const override; + bool supportsThreadedOpenGL() const override; + bool supportsWindowDecoration() const override; + + QWaylandWindow *createEglWindow(QWindow *window) override; + QPlatformOpenGLContext *createPlatformOpenGLContext(const QSurfaceFormat &glFormat, QPlatformOpenGLContext *share) const override; + + void *nativeResource(NativeResource resource) override; + void *nativeResourceForContext(NativeResource resource, QPlatformOpenGLContext *context) override; + + EGLDisplay eglDisplay() const; + +private: + QWaylandDisplay *m_display = nullptr; + + EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; + bool m_supportsThreading = false; +}; + +QT_END_NAMESPACE + +} + +#endif // QWAYLANDEGLINTEGRATION_H diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglinclude_p.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglinclude_p.h new file mode 100644 index 00000000000..eb52ce8eecd --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglinclude_p.h @@ -0,0 +1,27 @@ +// Copyright (C) 2016 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 + + +// +// 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. +// + +#ifndef QWAYLANDEGLINCLUDE_H +#define QWAYLANDEGLINCLUDE_H + +#include +#include + +#include + +#define EGL_EGLEXT_PROTOTYPES +#include + +#endif // QWAYLANDEGLINCLUDE_H diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglwindow.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglwindow.cpp new file mode 100644 index 00000000000..8375d0e196d --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglwindow.cpp @@ -0,0 +1,201 @@ +// Copyright (C) 2016 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 + +#include "qwaylandeglwindow_p.h" + +#include +#include +#include "qwaylandglcontext_p.h" + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandEglWindow::QWaylandEglWindow(QWindow *window, QWaylandDisplay *display) + : QWaylandWindow(window, display) + , m_clientBufferIntegration(static_cast(mDisplay->clientBufferIntegration())) +{ + connect(display, &QWaylandDisplay::connected, this, [this] { + m_clientBufferIntegration = static_cast( + mDisplay->clientBufferIntegration()); + }); + ensureSize(); +} + +QWaylandEglWindow::~QWaylandEglWindow() +{ + if (m_eglSurface) { + eglDestroySurface(m_clientBufferIntegration->eglDisplay(), m_eglSurface); + m_eglSurface = 0; + } + + if (m_waylandEglWindow) + wl_egl_window_destroy(m_waylandEglWindow); + + delete m_contentFBO; +} + +QWaylandWindow::WindowType QWaylandEglWindow::windowType() const +{ + return QWaylandWindow::Egl; +} + +void QWaylandEglWindow::ensureSize() +{ + // this is always called on the main thread + QRect rect = geometry(); + QMargins margins = clientSideMargins(); + QSize sizeWithMargins = (rect.size() + QSize(margins.left() + margins.right(), margins.top() + margins.bottom())) * scale(); + { + QWriteLocker lock(&m_bufferSizeLock); + m_bufferSize = sizeWithMargins; + } + + QMutexLocker lock (&m_eglSurfaceLock); + updateSurface(false); +} + +void QWaylandEglWindow::updateSurface(bool create) +{ + // eglSurfaceLock should be locked before calling this method + + QSize sizeWithMargins; + { + QReadLocker lock(&m_bufferSizeLock); + sizeWithMargins = m_bufferSize; + } + + // wl_egl_windows must have both width and height > 0 + // mesa's egl returns NULL if we try to create a, invalid wl_egl_window, however not all EGL + // implementations may do that, so check the size ourself. Besides, we must deal with resizing + // a valid window to 0x0, which would make it invalid. Hence, destroy it. + if (sizeWithMargins.isEmpty()) { + if (m_eglSurface) { + eglDestroySurface(m_clientBufferIntegration->eglDisplay(), m_eglSurface); + m_eglSurface = 0; + } + if (m_waylandEglWindow) { + wl_egl_window_destroy(m_waylandEglWindow); + m_waylandEglWindow = 0; + } + mOffset = QPoint(); + } else { + QReadLocker locker(&mSurfaceLock); + if (m_waylandEglWindow) { + int current_width = 0; + int current_height = 0; + static bool disableResizeCheck = qgetenv("QT_WAYLAND_DISABLE_RESIZECHECK").toInt(); + + if (!disableResizeCheck) { + wl_egl_window_get_attached_size(m_waylandEglWindow, ¤t_width, ¤t_height); + } + if (disableResizeCheck || (current_width != sizeWithMargins.width() || current_height != sizeWithMargins.height()) || m_requestedSize != sizeWithMargins) { + wl_egl_window_resize(m_waylandEglWindow, sizeWithMargins.width(), sizeWithMargins.height(), mOffset.x(), mOffset.y()); + m_requestedSize = sizeWithMargins; + mOffset = QPoint(); + + m_resize = true; + } + } else if (create && mSurface) { + wl_egl_window *eglWindow = wl_egl_window_create(mSurface->object(), sizeWithMargins.width(), sizeWithMargins.height()); + if (Q_UNLIKELY(!eglWindow)) { + qCWarning(lcQpaWayland, "Could not create wl_egl_window with size %dx%d\n", sizeWithMargins.width(), sizeWithMargins.height()); + return; + } + + QSurfaceFormat fmt = window()->requestedFormat(); + if (mDisplay->supportsWindowDecoration()) + fmt.setAlphaBufferSize(8); + EGLConfig eglConfig = q_configFromGLFormat(m_clientBufferIntegration->eglDisplay(), fmt); + setFormat(q_glFormatFromConfig(m_clientBufferIntegration->eglDisplay(), eglConfig, fmt)); + + EGLSurface eglSurface = eglCreateWindowSurface(m_clientBufferIntegration->eglDisplay(), eglConfig, (EGLNativeWindowType) eglWindow, 0); + if (Q_UNLIKELY(eglSurface == EGL_NO_SURFACE)) { + qCWarning(lcQpaWayland, "Could not create EGL surface (EGL error 0x%x)\n", eglGetError()); + wl_egl_window_destroy(eglWindow); + return; + } + + m_waylandEglWindow = eglWindow; + m_eglSurface = eglSurface; + m_requestedSize = sizeWithMargins; + } + } +} + +QRect QWaylandEglWindow::contentsRect() const +{ + QRect r = geometry(); + QMargins m = clientSideMargins(); + return QRect(m.left(), m.bottom(), r.width(), r.height()); +} + +void QWaylandEglWindow::invalidateSurface() +{ + QMutexLocker lock (&m_eglSurfaceLock); + + if (m_eglSurface) { + eglDestroySurface(m_clientBufferIntegration->eglDisplay(), m_eglSurface); + m_eglSurface = 0; + } + if (m_waylandEglWindow) { + wl_egl_window_destroy(m_waylandEglWindow); + m_waylandEglWindow = nullptr; + } + delete m_contentFBO; + m_contentFBO = nullptr; +} + +EGLSurface QWaylandEglWindow::eglSurface() const +{ + return m_eglSurface; +} + +QMutex* QWaylandEglWindow::eglSurfaceLock() +{ + return &m_eglSurfaceLock; +} + +GLuint QWaylandEglWindow::contentFBO() const +{ + if (!decoration()) + return 0; + + if (m_resize || !m_contentFBO) { + QOpenGLFramebufferObject *old = m_contentFBO; + QSize fboSize = geometry().size() * scale(); + m_contentFBO = new QOpenGLFramebufferObject(fboSize.width(), fboSize.height(), QOpenGLFramebufferObject::CombinedDepthStencil); + + delete old; + m_resize = false; + } + + return m_contentFBO->handle(); +} + +GLuint QWaylandEglWindow::contentTexture() const +{ + return m_contentFBO->texture(); +} + +void QWaylandEglWindow::bindContentFBO() +{ + if (decoration()) { + contentFBO(); + m_contentFBO->bind(); + } +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylandeglwindow_p.cpp" diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglwindow_p.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglwindow_p.h new file mode 100644 index 00000000000..dc2e0319519 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandeglwindow_p.h @@ -0,0 +1,76 @@ +// Copyright (C) 2016 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 + +// +// 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. +// + +#ifndef QWAYLANDEGLWINDOW_H +#define QWAYLANDEGLWINDOW_H + +#include +#include "qwaylandeglinclude_p.h" +#include "qwaylandeglclientbufferintegration_p.h" + +QT_BEGIN_NAMESPACE + +class QOpenGLFramebufferObject; + +namespace QtWaylandClient { + +class QWaylandGLContext; + +class Q_WAYLANDCLIENT_EXPORT QWaylandEglWindow : public QWaylandWindow +{ + Q_OBJECT +public: + QWaylandEglWindow(QWindow *window, QWaylandDisplay *display); + ~QWaylandEglWindow(); + WindowType windowType() const override; + void ensureSize() override; + + void updateSurface(bool create); + QRect contentsRect() const; + + EGLSurface eglSurface() const; + GLuint contentFBO() const; + GLuint contentTexture() const; + bool needToUpdateContentFBO() const { return decoration() && (m_resize || !m_contentFBO); } + + void bindContentFBO(); + + void invalidateSurface() override; + + QMutex* eglSurfaceLock(); + +private: + QWaylandEglClientBufferIntegration *m_clientBufferIntegration = nullptr; + struct wl_egl_window *m_waylandEglWindow = nullptr; + + // Locks any manipulation of the eglSurface size + QMutex m_eglSurfaceLock; + EGLSurface m_eglSurface = EGL_NO_SURFACE; + mutable bool m_resize = false; + mutable QOpenGLFramebufferObject *m_contentFBO = nullptr; + + // Size used in the last call to wl_egl_window_resize + QSize m_requestedSize; + + // Size of the buffer used by QWaylandWindow + // This is always written to from the main thread, potentially read from the rendering thread + QReadWriteLock m_bufferSizeLock; + QSize m_bufferSize; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDEGLWINDOW_H diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandglcontext.cpp b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandglcontext.cpp new file mode 100644 index 00000000000..be5a36fa01a --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandglcontext.cpp @@ -0,0 +1,430 @@ +// Copyright (C) 2016 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 + +#include "qwaylandglcontext_p.h" + +#include +#include +#include +#include +#include +#include "qwaylandeglwindow_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +// Constants from EGL_KHR_create_context +#ifndef EGL_CONTEXT_MINOR_VERSION_KHR +#define EGL_CONTEXT_MINOR_VERSION_KHR 0x30FB +#endif +#ifndef EGL_CONTEXT_FLAGS_KHR +#define EGL_CONTEXT_FLAGS_KHR 0x30FC +#endif +#ifndef EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR +#define EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR 0x30FD +#endif +#ifndef EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR +#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0x00000001 +#endif +#ifndef EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR +#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR 0x00000002 +#endif +#ifndef EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR +#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR 0x00000001 +#endif +#ifndef EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR +#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR 0x00000002 +#endif + +// Constants for OpenGL which are not available in the ES headers. +#ifndef GL_CONTEXT_FLAGS +#define GL_CONTEXT_FLAGS 0x821E +#endif +#ifndef GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT +#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x0001 +#endif +#ifndef GL_CONTEXT_FLAG_DEBUG_BIT +#define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002 +#endif +#ifndef GL_CONTEXT_PROFILE_MASK +#define GL_CONTEXT_PROFILE_MASK 0x9126 +#endif +#ifndef GL_CONTEXT_CORE_PROFILE_BIT +#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 +#endif +#ifndef GL_CONTEXT_COMPATIBILITY_PROFILE_BIT +#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 +#endif + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class DecorationsBlitter : public QOpenGLFunctions +{ +public: + DecorationsBlitter(QWaylandGLContext *context) + : m_context(context) + { + initializeOpenGLFunctions(); + m_blitProgram = new QOpenGLShaderProgram(); + m_blitProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, "attribute vec4 position;\n\ + attribute vec4 texCoords;\n\ + varying vec2 outTexCoords;\n\ + void main()\n\ + {\n\ + gl_Position = position;\n\ + outTexCoords = texCoords.xy;\n\ + }"); + m_blitProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, "varying highp vec2 outTexCoords;\n\ + uniform sampler2D texture;\n\ + void main()\n\ + {\n\ + gl_FragColor = texture2D(texture, outTexCoords);\n\ + }"); + + m_blitProgram->bindAttributeLocation("position", 0); + m_blitProgram->bindAttributeLocation("texCoords", 1); + + if (!m_blitProgram->link()) { + qDebug() << "Shader Program link failed."; + qDebug() << m_blitProgram->log(); + } + + m_blitProgram->bind(); + m_blitProgram->enableAttributeArray(0); + m_blitProgram->enableAttributeArray(1); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glDisable(GL_SCISSOR_TEST); + glDepthMask(GL_FALSE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + m_buffer.create(); + m_buffer.bind(); + + static const GLfloat squareVertices[] = { + -1.f, -1.f, + 1.0f, -1.f, + -1.f, 1.0f, + 1.0f, 1.0f + }; + static const GLfloat inverseSquareVertices[] = { + -1.f, 1.f, + 1.f, 1.f, + -1.f, -1.f, + 1.f, -1.f + }; + static const GLfloat textureVertices[] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + }; + + m_squareVerticesOffset = 0; + m_inverseSquareVerticesOffset = sizeof(squareVertices); + m_textureVerticesOffset = sizeof(squareVertices) + sizeof(textureVertices); + + m_buffer.allocate(sizeof(squareVertices) + sizeof(inverseSquareVertices) + sizeof(textureVertices)); + m_buffer.write(m_squareVerticesOffset, squareVertices, sizeof(squareVertices)); + m_buffer.write(m_inverseSquareVerticesOffset, inverseSquareVertices, sizeof(inverseSquareVertices)); + m_buffer.write(m_textureVerticesOffset, textureVertices, sizeof(textureVertices)); + + m_blitProgram->setAttributeBuffer(1, GL_FLOAT, m_textureVerticesOffset, 2); + + m_textureWrap = m_context->context()->functions()->hasOpenGLFeature(QOpenGLFunctions::NPOTTextureRepeat) ? GL_REPEAT : GL_CLAMP_TO_EDGE; + } + ~DecorationsBlitter() + { + delete m_blitProgram; + } + void blit(QWaylandEglWindow *window) + { + QOpenGLTextureCache *cache = QOpenGLTextureCache::cacheForContext(m_context->context()); + + QSize surfaceSize = window->surfaceSize(); + qreal scale = window->scale() ; + glViewport(0, 0, surfaceSize.width() * scale, surfaceSize.height() * scale); + + //Draw Decoration + if (auto *decoration = window->decoration()) { + m_blitProgram->setAttributeBuffer(0, GL_FLOAT, m_inverseSquareVerticesOffset, 2); + QImage decorationImage = decoration->contentImage(); + cache->bindTexture(m_context->context(), decorationImage); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_textureWrap); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_textureWrap); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + //Draw Content + m_blitProgram->setAttributeBuffer(0, GL_FLOAT, m_squareVerticesOffset, 2); + glBindTexture(GL_TEXTURE_2D, window->contentTexture()); + QRect r = window->contentsRect(); + glViewport(r.x() * scale, r.y() * scale, r.width() * scale, r.height() * scale); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + QOpenGLShaderProgram *m_blitProgram = nullptr; + QWaylandGLContext *m_context = nullptr; + QOpenGLBuffer m_buffer; + int m_squareVerticesOffset; + int m_inverseSquareVerticesOffset; + int m_textureVerticesOffset; + int m_textureWrap; +}; + +QWaylandGLContext::QWaylandGLContext(EGLDisplay eglDisplay, QWaylandDisplay *display, + const QSurfaceFormat &fmt, QPlatformOpenGLContext *share) + : QEGLPlatformContext(fmt, share, eglDisplay) + , m_display(display) + , m_decorationsContext(EGL_NO_CONTEXT) +{ + m_reconnectionWatcher = QObject::connect(m_display, &QWaylandDisplay::connected, + m_display, [this] { invalidateContext(); }); + + switch (format().renderableType()) { + case QSurfaceFormat::OpenVG: + m_api = EGL_OPENVG_API; + break; +#ifdef EGL_VERSION_1_4 + case QSurfaceFormat::OpenGL: + m_api = EGL_OPENGL_API; + break; +#endif // EGL_VERSION_1_4 + default: + m_api = EGL_OPENGL_ES_API; + break; + } + + if (m_display->supportsWindowDecoration()) { + // Create an EGL context for the decorations blitter. By using a dedicated context we are free to + // change its state and we also use OpenGL ES 2 API independently to what the app is using to draw. + QList eglDecorationsContextAttrs = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; + m_decorationsContext = eglCreateContext(eglDisplay, eglConfig(), eglContext(), + eglDecorationsContextAttrs.constData()); + if (m_decorationsContext == EGL_NO_CONTEXT) + qWarning("QWaylandGLContext: Failed to create the decorations EGLContext. Decorations will not be drawn."); + } + + EGLint a = EGL_MIN_SWAP_INTERVAL; + EGLint b = EGL_MAX_SWAP_INTERVAL; + if (!eglGetConfigAttrib(eglDisplay, eglConfig(), a, &a) + || !eglGetConfigAttrib(eglDisplay, eglConfig(), b, &b) || a > 0) { + m_supportNonBlockingSwap = false; + } + { + bool ok; + int supportNonBlockingSwap = qEnvironmentVariableIntValue("QT_WAYLAND_FORCE_NONBLOCKING_SWAP_SUPPORT", &ok); + if (ok) + m_supportNonBlockingSwap = supportNonBlockingSwap != 0; + } + if (!m_supportNonBlockingSwap) { + qCWarning(lcQpaWayland) << "Non-blocking swap buffers not supported." + << "Subsurface rendering can be affected." + << "It may also cause the event loop to freeze in some situations"; + } +} + +EGLSurface QWaylandGLContext::createTemporaryOffscreenSurface() +{ + m_wlSurface = m_display->createSurface(nullptr); + m_eglWindow = wl_egl_window_create(m_wlSurface, 1, 1); +#if QT_CONFIG(egl_extension_platform_wayland) + EGLSurface eglSurface = + eglCreatePlatformWindowSurface(eglDisplay(), eglConfig(), m_eglWindow, nullptr); +#else + EGLSurface eglSurface = eglCreateWindowSurface(eglDisplay(), eglConfig(), m_eglWindow, nullptr); +#endif + return eglSurface; +} + +void QWaylandGLContext::destroyTemporaryOffscreenSurface(EGLSurface eglSurface) +{ + eglDestroySurface(eglDisplay(), eglSurface); + wl_egl_window_destroy(m_eglWindow); + m_eglWindow = nullptr; + wl_surface_destroy(m_wlSurface); + m_wlSurface = nullptr; +} + +void QWaylandGLContext::runGLChecks() +{ + bool ok; + const int doneCurrentWorkAround = qEnvironmentVariableIntValue("QT_WAYLAND_ENABLE_DONECURRENT_WORKAROUND", &ok); + if (ok) { + m_doneCurrentWorkAround = doneCurrentWorkAround != 0; + if (m_doneCurrentWorkAround) + qCDebug(lcQpaWayland) << "Enabling doneCurrent() workaround on request."; + else + qCDebug(lcQpaWayland) << "Disabling doneCurrent() workaround on request."; + + } else { + // Note that even though there is an EGL context current here, + // QOpenGLContext and QOpenGLFunctions are not yet usable at this stage. + const char *renderer = reinterpret_cast(glGetString(GL_RENDERER)); + if (renderer && strstr(renderer, "Mali")) { + qCDebug(lcQpaWayland) << "Enabling doneCurrent() workaround for Mali GPU." + << "Set QT_WAYLAND_ENABLE_DONECURRENT_WORKAROUND=0 to disable."; + m_doneCurrentWorkAround = true; + } + } + + QEGLPlatformContext::runGLChecks(); +} + +QWaylandGLContext::~QWaylandGLContext() +{ + QObject::disconnect(m_reconnectionWatcher); + delete m_blitter; + m_blitter = nullptr; + if (m_decorationsContext != EGL_NO_CONTEXT) + eglDestroyContext(eglDisplay(), m_decorationsContext); +} + +void QWaylandGLContext::beginFrame() +{ + Q_ASSERT(m_currentWindow != nullptr); + if (m_supportNonBlockingSwap) + m_currentWindow->beginFrame(); +} + +void QWaylandGLContext::endFrame() +{ + Q_ASSERT(m_currentWindow != nullptr); + if (m_doneCurrentWorkAround) { + doneCurrent(); + QOpenGLContextPrivate::setCurrentContext(nullptr); + } + + if (m_supportNonBlockingSwap) + m_currentWindow->endFrame(); +} + +bool QWaylandGLContext::makeCurrent(QPlatformSurface *surface) +{ + if (!isValid()) { + return false; + } + + // in QWaylandGLContext() we called eglBindAPI with the correct value. However, + // eglBindAPI's documentation says: + // "eglBindAPI defines the current rendering API for EGL in the thread it is called from" + // Since makeCurrent() can be called from a different thread than the one we created the + // context in make sure to call eglBindAPI in the correct thread. + if (eglQueryAPI() != m_api) { + eglBindAPI(m_api); + } + + m_currentWindow = static_cast(surface); + + QMutexLocker lock(m_currentWindow->eglSurfaceLock()); + EGLSurface eglSurface = m_currentWindow->eglSurface(); + + if (!m_currentWindow->needToUpdateContentFBO() && (eglSurface != EGL_NO_SURFACE)) { + if (!eglMakeCurrent(eglDisplay(), eglSurface, eglSurface, eglContext())) { + qWarning("QWaylandGLContext::makeCurrent: eglError: %#x, this: %p \n", eglGetError(), this); + return false; + } + return true; + } + + if (eglSurface == EGL_NO_SURFACE) { + m_currentWindow->updateSurface(true); + eglSurface = m_currentWindow->eglSurface(); + } + + if (!eglMakeCurrent(eglDisplay(), eglSurface, eglSurface, eglContext())) { + qWarning("QWaylandGLContext::makeCurrent: eglError: %#x, this: %p \n", eglGetError(), this); + return false; + } + + //### setCurrentContext will be called in QOpenGLContext::makeCurrent after this function + // returns, but that's too late, as we need a current context in order to bind the content FBO. + QOpenGLContextPrivate::setCurrentContext(context()); + m_currentWindow->bindContentFBO(); + + return true; +} + +void QWaylandGLContext::doneCurrent() +{ + eglMakeCurrent(eglDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +void QWaylandGLContext::swapBuffers(QPlatformSurface *surface) +{ + QWaylandEglWindow *window = static_cast(surface); + + EGLSurface eglSurface = window->eglSurface(); + + if (window->decoration()) { + if (m_api != EGL_OPENGL_ES_API) + eglBindAPI(EGL_OPENGL_ES_API); + + // save the current EGL content and surface to set it again after the blitter is done + EGLDisplay currentDisplay = eglGetCurrentDisplay(); + EGLContext currentContext = eglGetCurrentContext(); + EGLSurface currentSurfaceDraw = eglGetCurrentSurface(EGL_DRAW); + EGLSurface currentSurfaceRead = eglGetCurrentSurface(EGL_READ); + eglMakeCurrent(eglDisplay(), eglSurface, eglSurface, m_decorationsContext); + + if (!m_blitter) + m_blitter = new DecorationsBlitter(this); + m_blitter->blit(window); + + if (m_api != EGL_OPENGL_ES_API) + eglBindAPI(m_api); + eglMakeCurrent(currentDisplay, currentSurfaceDraw, currentSurfaceRead, currentContext); + } + + int swapInterval = m_supportNonBlockingSwap ? 0 : format().swapInterval(); + eglSwapInterval(eglDisplay(), swapInterval); + if (swapInterval == 0 && format().swapInterval() > 0) { + // Emulating a blocking swap + glFlush(); // Flush before waiting so we can swap more quickly when the frame event arrives + window->waitForFrameSync(100); + } + window->handleUpdate(); + if (!eglSwapBuffers(eglDisplay(), eglSurface)) + qCWarning(lcQpaWayland, "eglSwapBuffers failed with %#x, surface: %p", eglGetError(), eglSurface); +} + +GLuint QWaylandGLContext::defaultFramebufferObject(QPlatformSurface *surface) const +{ + return static_cast(surface)->contentFBO(); +} + +QFunctionPointer QWaylandGLContext::getProcAddress(const char *procName) +{ + QFunctionPointer proc = (QFunctionPointer) eglGetProcAddress(procName); + if (!proc) + proc = (QFunctionPointer) dlsym(RTLD_DEFAULT, procName); + return proc; +} + +EGLSurface QWaylandGLContext::eglSurfaceForPlatformSurface(QPlatformSurface *surface) +{ + return static_cast(surface)->eglSurface(); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandglcontext_p.h b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandglcontext_p.h new file mode 100644 index 00000000000..bd9eb53f85e --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/qwaylandglcontext_p.h @@ -0,0 +1,74 @@ +// Copyright (C) 2016 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 + +// +// 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. +// + +#ifndef QWAYLANDGLCONTEXT_H +#define QWAYLANDGLCONTEXT_H + +#include "qwaylandeglinclude_p.h" //must be first + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QOpenGLShaderProgram; +class QOpenGLTextureCache; + +namespace QtWaylandClient { + +class QWaylandEglWindow; +class DecorationsBlitter; + +class Q_WAYLANDCLIENT_EXPORT QWaylandGLContext : public QEGLPlatformContext +{ +public: + QWaylandGLContext(EGLDisplay eglDisplay, QWaylandDisplay *display, const QSurfaceFormat &format, QPlatformOpenGLContext *share); + ~QWaylandGLContext(); + void swapBuffers(QPlatformSurface *surface) override; + + bool makeCurrent(QPlatformSurface *surface) override; + void doneCurrent() override; + + void beginFrame() override; + void endFrame() override; + + GLuint defaultFramebufferObject(QPlatformSurface *surface) const override; + + QFunctionPointer getProcAddress(const char *procName) override; + +protected: + EGLSurface eglSurfaceForPlatformSurface(QPlatformSurface *surface) override; + EGLSurface createTemporaryOffscreenSurface() override; + void destroyTemporaryOffscreenSurface(EGLSurface surface) override; + void runGLChecks() override; + +private: + QWaylandDisplay *m_display = nullptr; + EGLContext m_decorationsContext; + DecorationsBlitter *m_blitter = nullptr; + bool m_supportNonBlockingSwap = true; + EGLenum m_api; + wl_surface *m_wlSurface = nullptr; + wl_egl_window *m_eglWindow = nullptr; + QWaylandEglWindow *m_currentWindow = nullptr; + QMetaObject::Connection m_reconnectionWatcher; + bool m_doneCurrentWorkAround = false; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDGLCONTEXT_H diff --git a/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/wayland-egl.json b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/wayland-egl.json new file mode 100644 index 00000000000..4ea5bab903f --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/hardwareintegration/wayland-egl/wayland-egl.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "wayland-egl" ] +} diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/shellintegration/CMakeLists.txt new file mode 100644 index 00000000000..32377b4c402 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from shellintegration.pro. + +if(QT_FEATURE_wayland_client_fullscreen_shell_v1) + add_subdirectory(fullscreen-shell-v1) +endif() +if(QT_FEATURE_wayland_client_wl_shell) + add_subdirectory(wl-shell) +endif() +if(QT_FEATURE_wayland_client_xdg_shell) + add_subdirectory(xdg-shell) +endif() diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/CMakeLists.txt new file mode 100644 index 00000000000..365f6db23c2 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from fullscreen-shell-v1.pro. + +##################################################################### +## QWaylandFullScreenShellV1IntegrationPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(QWaylandFullScreenShellV1IntegrationPlugin + OUTPUT_NAME fullscreen-shell-v1 + PLUGIN_TYPE wayland-shell-integration + SOURCES + main.cpp + qwaylandfullscreenshellv1integration.cpp qwaylandfullscreenshellv1integration.h + qwaylandfullscreenshellv1surface.cpp qwaylandfullscreenshellv1surface.h + LIBRARIES + Qt::Core + Qt::Gui + Qt::GuiPrivate + Qt::WaylandClientPrivate + Wayland::Client + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 + ATTRIBUTION_FILE_DIR_PATHS + ../../../../../../3rdparty/wayland/protocols/fullscreen-shell +) + +qt6_generate_wayland_protocol_client_sources(QWaylandFullScreenShellV1IntegrationPlugin + PRIVATE_CODE + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/protocols/fullscreen-shell/fullscreen-shell-unstable-v1.xml +) + +#### Keys ignored in scope 1:.:.:fullscreen-shell-v1.pro:: +# OTHER_FILES = "fullscreen-shell-v1.json" diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/fullscreen-shell-v1.json b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/fullscreen-shell-v1.json new file mode 100644 index 00000000000..f32637bccfc --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/fullscreen-shell-v1.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "fullscreen-shell-v1" ] +} diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/main.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/main.cpp new file mode 100644 index 00000000000..70efb71b21c --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/main.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2018 Pier Luigi Fiorini +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include + +#include "qwaylandfullscreenshellv1integration.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandFullScreenShellV1IntegrationPlugin : public QWaylandShellIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandShellIntegrationFactoryInterface_iid FILE "fullscreen-shell-v1.json") +public: + QWaylandShellIntegration *create(const QString &key, const QStringList ¶mList) override; +}; + +QWaylandShellIntegration *QWaylandFullScreenShellV1IntegrationPlugin::create(const QString &key, const QStringList ¶mList) +{ + Q_UNUSED(paramList); + + if (key == QLatin1String("fullscreen-shell-v1")) + return new QWaylandFullScreenShellV1Integration(); + return nullptr; +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1integration.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1integration.cpp new file mode 100644 index 00000000000..7dcdd6e5997 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1integration.cpp @@ -0,0 +1,29 @@ +// Copyright (C) 2018 Pier Luigi Fiorini +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandfullscreenshellv1integration.h" +#include "qwaylandfullscreenshellv1surface.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandFullScreenShellV1Integration::QWaylandFullScreenShellV1Integration() + : QWaylandShellIntegrationTemplate(1) +{ +} + +QWaylandFullScreenShellV1Integration::~QWaylandFullScreenShellV1Integration() +{ + if (isActive()) + release(); +} + +QWaylandShellSurface *QWaylandFullScreenShellV1Integration::createShellSurface(QWaylandWindow *window) +{ + return new QWaylandFullScreenShellV1Surface(this, window); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1integration.h b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1integration.h new file mode 100644 index 00000000000..c01af37074a --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1integration.h @@ -0,0 +1,31 @@ +// Copyright (C) 2018 Pier Luigi Fiorini +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDFULLSCREENSHELLV1INTEGRATION_H +#define QWAYLANDFULLSCREENSHELLV1INTEGRATION_H + +#include +#include +#include + +#include "qwayland-fullscreen-shell-unstable-v1.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class Q_WAYLANDCLIENT_EXPORT QWaylandFullScreenShellV1Integration + : public QWaylandShellIntegrationTemplate, + public QtWayland::zwp_fullscreen_shell_v1 +{ +public: + QWaylandFullScreenShellV1Integration(); + ~QWaylandFullScreenShellV1Integration() override; + QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDFULLSCREENSHELLV1INTEGRATION_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.cpp new file mode 100644 index 00000000000..8874c0bf216 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2018 Pier Luigi Fiorini +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include + +#include "qwaylandfullscreenshellv1surface.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandFullScreenShellV1Surface::QWaylandFullScreenShellV1Surface(QtWayland::zwp_fullscreen_shell_v1 *shell, QWaylandWindow *window) + : QWaylandShellSurface(window) + , m_shell(shell) + , m_window(window) +{ + auto *screen = m_window->waylandScreen(); + auto *output = screen ? screen->output() : nullptr; + m_shell->present_surface(m_window->wlSurface(), + QtWayland::zwp_fullscreen_shell_v1::present_method_default, + output); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.h b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.h new file mode 100644 index 00000000000..0a82e5eee2a --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/fullscreen-shell-v1/qwaylandfullscreenshellv1surface.h @@ -0,0 +1,32 @@ +// Copyright (C) 2018 Pier Luigi Fiorini +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDFULLSCREENSHELLV1SURFACE_H +#define QWAYLANDFULLSCREENSHELLV1SURFACE_H + +#include +#include +#include + +#include "qwayland-fullscreen-shell-unstable-v1.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class Q_WAYLANDCLIENT_EXPORT QWaylandFullScreenShellV1Surface : public QWaylandShellSurface +{ +public: + QWaylandFullScreenShellV1Surface(QtWayland::zwp_fullscreen_shell_v1 *shell, QWaylandWindow *window); + std::any surfaceRole() const override { return m_shell->object(); } + +private: + QtWayland::zwp_fullscreen_shell_v1 *m_shell = nullptr; + QWaylandWindow *m_window = nullptr; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDFULLSCREENSHELLV1SURFACE_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/CMakeLists.txt new file mode 100644 index 00000000000..4b92e6426f6 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/CMakeLists.txt @@ -0,0 +1,62 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from wl-shell.pro. + +##################################################################### +## WlShellIntegrationPrivate Module: +##################################################################### + +qt_internal_add_module(WlShellIntegrationPrivate + CONFIG_MODULE_NAME wl_shell_integration + INTERNAL_MODULE + SOURCES + qwaylandwlshellintegration.cpp qwaylandwlshellintegration_p.h + qwaylandwlshellsurface.cpp qwaylandwlshellsurface_p.h + LIBRARIES + Qt::GuiPrivate + Qt::WaylandClientPrivate + Wayland::Client + PRIVATE_HEADER_FILTERS + "^qwayland-.*\.h|^wayland-.*-protocol\.h" + NO_GENERATE_CPP_EXPORTS + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 + ATTRIBUTION_FILE_DIR_PATHS + ../../../../../../3rdparty/wayland/protocols/wayland +) + +qt6_generate_wayland_protocol_client_sources(WlShellIntegrationPrivate + # this is just a helper module and still part of the QtWaylandClient build + __QT_INTERNAL_WAYLAND_INCLUDE_DIR QtWaylandClient/private + PRIVATE_CODE + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/protocols/wayland/wayland.xml +) + +#### Keys ignored in scope 2:.:.:wl-shell-integration.pro:: +# MODULE = "wl_shell_integration" + +## Scopes: +##################################################################### + +qt_internal_extend_target(WlShellIntegrationPrivate CONDITION QT_FEATURE_xkbcommon + LIBRARIES + XKB::XKB +) +##################################################################### +## QWaylandWlShellIntegrationPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(QWaylandWlShellIntegrationPlugin + OUTPUT_NAME wl-shell-plugin + PLUGIN_TYPE wayland-shell-integration + SOURCES + main.cpp + LIBRARIES + Qt::GuiPrivate + Qt::WaylandClientPrivate + Qt::WlShellIntegrationPrivate +) + +#### Keys ignored in scope 4:.:.:wl-shell-plugin.pro:: +# OTHER_FILES = "wl-shell.json" diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/main.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/main.cpp new file mode 100644 index 00000000000..9d578c58b81 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/main.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// Copyright (C) 2017 ITAGE Corporation, author: +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandwlshellintegration_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandWlShellIntegrationPlugin : public QWaylandShellIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandShellIntegrationFactoryInterface_iid FILE "wl-shell.json") + +public: + QWaylandShellIntegration *create(const QString &key, const QStringList ¶mList) override; +}; + +QWaylandShellIntegration *QWaylandWlShellIntegrationPlugin::create(const QString &key, const QStringList ¶mList) +{ + Q_UNUSED(key); + Q_UNUSED(paramList); + return new QWaylandWlShellIntegration(); +} + +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwaylandwlshellintegration.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwaylandwlshellintegration.cpp new file mode 100644 index 00000000000..2e2076b0a55 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwaylandwlshellintegration.cpp @@ -0,0 +1,47 @@ +// Copyright (C) 2017 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 + +#include "qwaylandwlshellintegration_p.h" +#include "qwaylandwlshellsurface_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandWlShellIntegration::QWaylandWlShellIntegration() : QWaylandShellIntegrationTemplate(1) +{ + qCWarning(lcQpaWayland) << "\"wl-shell\" is a deprecated shell extension, prefer using" + << "\"xdg-shell\" if supported by the compositor" + << "by setting the environment variable QT_WAYLAND_SHELL_INTEGRATION"; +} + +QWaylandWlShellIntegration::~QWaylandWlShellIntegration() +{ + if (object()) + wl_shell_destroy(object()); +} + +QWaylandShellSurface *QWaylandWlShellIntegration::createShellSurface(QWaylandWindow *window) +{ + return new QWaylandWlShellSurface(get_shell_surface(window->wlSurface()), window); +} + +void *QWaylandWlShellIntegration::nativeResourceForWindow(const QByteArray &resource, QWindow *window) +{ + QByteArray lowerCaseResource = resource.toLower(); + if (lowerCaseResource == "wl_shell_surface") { + if (auto waylandWindow = static_cast(window->handle())) { + if (auto shellSurface = qobject_cast(waylandWindow->shellSurface())) { + return shellSurface->object(); + } + } + } + return nullptr; +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwaylandwlshellintegration_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwaylandwlshellintegration_p.h new file mode 100644 index 00000000000..312a1089a5c --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwaylandwlshellintegration_p.h @@ -0,0 +1,43 @@ +// Copyright (C) 2017 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 + +#ifndef QWAYLANDWLSHELLINTEGRATION_P_H +#define QWAYLANDWLSHELLINTEGRATION_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class Q_WAYLANDCLIENT_EXPORT QWaylandWlShellIntegration + : public QWaylandShellIntegrationTemplate, + public QtWayland::wl_shell +{ +public: + QWaylandWlShellIntegration(); + ~QWaylandWlShellIntegration(); + QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override; + void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) override; + +private: +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDWLSHELLINTEGRATION_P_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwaylandwlshellsurface.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwaylandwlshellsurface.cpp new file mode 100644 index 00000000000..7ffa41c91ad --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwaylandwlshellsurface.cpp @@ -0,0 +1,221 @@ +// Copyright (C) 2016 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 + +#include "qwaylandwlshellsurface_p.h" + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandWlShellSurface::QWaylandWlShellSurface(struct ::wl_shell_surface *shell_surface, QWaylandWindow *window) + : QWaylandShellSurface(window) + , QtWayland::wl_shell_surface(shell_surface) + , m_window(window) +{ + Qt::WindowType type = window->window()->type(); + auto *transientParent = window->transientParent(); + if (type == Qt::Popup && transientParent && transientParent->wlSurface()) + setPopup(transientParent, m_window->display()->lastInputDevice(), m_window->display()->lastInputSerial()); + else if (transientParent && transientParent->wlSurface()) + updateTransientParent(transientParent->window()); + else + setTopLevel(); +} + +QWaylandWlShellSurface::~QWaylandWlShellSurface() +{ + wl_shell_surface_destroy(object()); +} + +bool QWaylandWlShellSurface::resize(QWaylandInputDevice *inputDevice, Qt::Edges edges) +{ + enum resize resizeEdges = convertToResizeEdges(edges); + resize(inputDevice->wl_seat(), inputDevice->serial(), resizeEdges); + return true; +} + +bool QWaylandWlShellSurface::move(QWaylandInputDevice *inputDevice) +{ + move(inputDevice->wl_seat(), + inputDevice->serial()); + return true; +} + +void QWaylandWlShellSurface::setTitle(const QString & title) +{ + return QtWayland::wl_shell_surface::set_title(title); +} + +void QWaylandWlShellSurface::setAppId(const QString & appId) +{ + return QtWayland::wl_shell_surface::set_class(appId); +} + +void QWaylandWlShellSurface::applyConfigure() +{ + if ((m_pending.states & (Qt::WindowMaximized|Qt::WindowFullScreen)) + && !(m_applied.states & (Qt::WindowMaximized|Qt::WindowFullScreen))) { + m_normalSize = m_window->windowFrameGeometry().size(); + } + + if (m_pending.states != m_applied.states) + m_window->handleWindowStatesChanged(m_pending.states); + + if (!m_pending.size.isEmpty()) { + int x = 0; + int y = 0; + if (m_pending.edges & resize_left) + x = m_applied.size.width() - m_pending.size.width(); + if (m_pending.edges & resize_top) + y = m_applied.size.height() - m_pending.size.height(); + QPoint offset(x, y); + m_window->resizeFromApplyConfigure(m_pending.size, offset); + } else if (m_pending.size.isValid() && !m_normalSize.isEmpty()) { + m_window->resizeFromApplyConfigure(m_normalSize); + } + + m_applied = m_pending; +} + +bool QWaylandWlShellSurface::wantsDecorations() const +{ + return !(m_pending.states & Qt::WindowFullScreen); +} + +void QWaylandWlShellSurface::requestWindowStates(Qt::WindowStates states) +{ + // On wl-shell the client is in charge of states, so diff from the pending state + Qt::WindowStates changedStates = m_pending.states ^ states; + Qt::WindowStates addedStates = changedStates & states; + + if (addedStates & Qt::WindowMinimized) + qCWarning(lcQpaWayland) << "Minimizing is not supported on wl-shell. Consider using xdg-shell instead."; + + if (addedStates & Qt::WindowMaximized) { + set_maximized(nullptr); + m_window->applyConfigureWhenPossible(); + } + + if (addedStates & Qt::WindowFullScreen) { + set_fullscreen(WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, 0, nullptr); + m_window->applyConfigureWhenPossible(); + } + + bool isNormal = !(states & Qt::WindowMaximized) && !(states & Qt::WindowFullScreen); + if (isNormal && (changedStates & (Qt::WindowMaximized | Qt::WindowFullScreen))) { + setTopLevel(); // set normal window + // There's usually no configure event after this, so just clear the rest of the pending + // configure here and queue the applyConfigure call + m_pending.size = {0, 0}; + m_pending.edges = resize_none; + m_window->applyConfigureWhenPossible(); + } + + m_pending.states = states & ~Qt::WindowMinimized; +} + +enum QWaylandWlShellSurface::resize QWaylandWlShellSurface::convertToResizeEdges(Qt::Edges edges) +{ + return static_cast( + ((edges & Qt::TopEdge) ? resize_top : 0) + | ((edges & Qt::BottomEdge) ? resize_bottom : 0) + | ((edges & Qt::LeftEdge) ? resize_left : 0) + | ((edges & Qt::RightEdge) ? resize_right : 0)); +} + +void QWaylandWlShellSurface::setTopLevel() +{ + set_toplevel(); +} + +static inline bool testShowWithoutActivating(const QWindow *window) +{ + // QWidget-attribute Qt::WA_ShowWithoutActivating. + const QVariant showWithoutActivating = window->property("_q_showWithoutActivating"); + return showWithoutActivating.isValid() && showWithoutActivating.toBool(); +} + +void QWaylandWlShellSurface::updateTransientParent(QWindow *parent) +{ + QWaylandWindow *parent_wayland_window = static_cast(parent->handle()); + if (!parent_wayland_window) + return; + + // set_transient expects a position relative to the parent + QPoint transientPos = m_window->geometry().topLeft(); // this is absolute + transientPos -= parent->geometry().topLeft(); + if (parent_wayland_window->decoration()) { + transientPos.setX(transientPos.x() + parent_wayland_window->decoration()->margins().left()); + transientPos.setY(transientPos.y() + parent_wayland_window->decoration()->margins().top()); + } + + uint32_t flags = 0; + Qt::WindowFlags wf = m_window->window()->flags(); + if (wf.testFlag(Qt::ToolTip) + || wf.testFlag(Qt::WindowTransparentForInput) + || testShowWithoutActivating(m_window->window())) + flags |= WL_SHELL_SURFACE_TRANSIENT_INACTIVE; + + auto *parentSurface = parent_wayland_window->wlSurface(); + Q_ASSERT(parentSurface); + set_transient(parentSurface, transientPos.x(), transientPos.y(), flags); +} + +void QWaylandWlShellSurface::setPopup(QWaylandWindow *parent, QWaylandInputDevice *device, uint serial) +{ + QWaylandWindow *parent_wayland_window = parent; + if (!parent_wayland_window) { + qCWarning(lcQpaWayland) << "setPopup called without a parent window"; + return; + } + if (!device) { + qCWarning(lcQpaWayland) << "setPopup called without an input device"; + return; + } + + // set_popup expects a position relative to the parent + QPoint transientPos = m_window->geometry().topLeft(); // this is absolute + transientPos -= parent_wayland_window->geometry().topLeft(); + if (parent_wayland_window->decoration()) { + transientPos.setX(transientPos.x() + parent_wayland_window->decoration()->margins().left()); + transientPos.setY(transientPos.y() + parent_wayland_window->decoration()->margins().top()); + } + + auto *parentSurface = parent_wayland_window->wlSurface(); + Q_ASSERT(parentSurface); + uint flags = 0; + set_popup(device->wl_seat(), serial, parentSurface, transientPos.x(), transientPos.y(), flags); +} + +void QWaylandWlShellSurface::shell_surface_ping(uint32_t serial) +{ + pong(serial); +} + +void QWaylandWlShellSurface::shell_surface_configure(uint32_t edges, int32_t width, int32_t height) +{ + m_pending.size = QSize(width, height); + m_pending.edges = static_cast(edges); + if (m_pending.edges && !m_pending.size.isEmpty()) + m_normalSize = m_pending.size; + m_window->applyConfigureWhenPossible(); +} + +void QWaylandWlShellSurface::shell_surface_popup_done() +{ + QCoreApplication::postEvent(m_window->window(), new QCloseEvent()); +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylandwlshellsurface_p.cpp" diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwaylandwlshellsurface_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwaylandwlshellsurface_p.h new file mode 100644 index 00000000000..780f5f3268e --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/qwaylandwlshellsurface_p.h @@ -0,0 +1,89 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDWLSHELLSURFACE_H +#define QWAYLANDWLSHELLSURFACE_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 + +QT_BEGIN_NAMESPACE + +class QWindow; + +namespace QtWaylandClient { + +class QWaylandWindow; +class QWaylandInputDevice; +class QWaylandExtendedSurface; + +class Q_WAYLANDCLIENT_EXPORT QWaylandWlShellSurface : public QWaylandShellSurface + , public QtWayland::wl_shell_surface +{ + Q_OBJECT +public: + QWaylandWlShellSurface(struct ::wl_shell_surface *shell_surface, QWaylandWindow *window); + ~QWaylandWlShellSurface() override; + + using QtWayland::wl_shell_surface::resize; + bool resize(QWaylandInputDevice *inputDevice, Qt::Edges edges) override; + + using QtWayland::wl_shell_surface::move; + bool move(QWaylandInputDevice *inputDevice) override; + + void setTitle(const QString & title) override; + void setAppId(const QString &appId) override; + + void applyConfigure() override; + bool wantsDecorations() const override; + + std::any surfaceRole() const override { return object(); }; + +protected: + void requestWindowStates(Qt::WindowStates states) override; + +private: + static enum resize convertToResizeEdges(Qt::Edges edges); + void setTopLevel(); + void updateTransientParent(QWindow *parent); + void setPopup(QWaylandWindow *parent, QWaylandInputDevice *device, uint serial); + + QWaylandWindow *m_window = nullptr; + struct { + Qt::WindowStates states = Qt::WindowNoState; + QSize size; + enum resize edges = resize_none; + } m_applied, m_pending; + QSize m_normalSize; + // There's really no need to have pending and applied state on wl-shell, but we do it just to + // keep the different shell implementations more similar. + QWaylandExtendedSurface *m_extendedWindow = nullptr; + + void shell_surface_ping(uint32_t serial) override; + void shell_surface_configure(uint32_t edges, + int32_t width, + int32_t height) override; + void shell_surface_popup_done() override; + + friend class QWaylandWindow; +}; + +QT_END_NAMESPACE + +} + +#endif // QWAYLANDSHELLSURFACE_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/wl-shell.json b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/wl-shell.json new file mode 100644 index 00000000000..1859bb58ad0 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/wl-shell/wl-shell.json @@ -0,0 +1,3 @@ +{ + "Keys":[ "wl-shell" ] +} diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/CMakeLists.txt b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/CMakeLists.txt new file mode 100644 index 00000000000..4e1d95df7b2 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/CMakeLists.txt @@ -0,0 +1,57 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from xdg-shell.pro. + +##################################################################### +## QWaylandXdgShellIntegrationPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(QWaylandXdgShellIntegrationPlugin + OUTPUT_NAME xdg-shell + PLUGIN_TYPE wayland-shell-integration + SOURCES + main.cpp + qwaylandxdgdecorationv1.cpp qwaylandxdgdecorationv1_p.h + qwaylandxdgactivationv1.cpp qwaylandxdgactivationv1_p.h + qwaylandxdgshell.cpp qwaylandxdgshell_p.h + qwaylandxdgshellintegration.cpp qwaylandxdgshellintegration_p.h + qwaylandxdgexporterv2.cpp qwaylandxdgexporterv2_p.h + qwaylandxdgdialogv1.cpp qwaylandxdgdialogv1_p.h + qwaylandxdgtopleveliconv1.cpp qwaylandxdgtopleveliconv1_p.h + LIBRARIES + Qt::Core + Qt::Gui + Qt::GuiPrivate + Qt::WaylandClientPrivate + Wayland::Client + QT_LICENSE_ID QT_COMMERCIAL_OR_LGPL3 + ATTRIBUTION_FILE_DIR_PATHS + ../../../../../../3rdparty/wayland/protocols/xdg-decoration + ../../../../../../3rdparty/wayland/protocols/xdg-shell + ../../../../../../3rdparty/wayland/protocols/xdg-activation + ../../../../../../3rdparty/wayland/protocols/xdg-foreign + ../../../../../../3rdparty/wayland/protocols/xdg-dialog +) + +qt6_generate_wayland_protocol_client_sources(QWaylandXdgShellIntegrationPlugin + PRIVATE_CODE + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/protocols/xdg-decoration/xdg-decoration-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/protocols/xdg-shell/xdg-shell.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/protocols/xdg-activation/xdg-activation-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/protocols/xdg-foreign/xdg-foreign-unstable-v2.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/protocols/xdg-dialog/xdg-dialog-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../3rdparty/wayland/protocols/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml +) + +#### Keys ignored in scope 1:.:.:xdg-shell.pro:: +# OTHER_FILES = "xdg-shell.json" + +## Scopes: +##################################################################### + +qt_internal_extend_target(QWaylandXdgShellIntegrationPlugin CONDITION QT_FEATURE_xkbcommon + LIBRARIES + XKB::XKB +) diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/main.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/main.cpp new file mode 100644 index 00000000000..bb40a564e5c --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/main.cpp @@ -0,0 +1,32 @@ +// Copyright (C) 2018 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 + +#include "qwaylandxdgshellintegration_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandXdgShellIntegrationPlugin : public QWaylandShellIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QWaylandShellIntegrationFactoryInterface_iid FILE "xdg-shell.json") + +public: + QWaylandShellIntegration *create(const QString &key, const QStringList ¶mList) override; +}; + +QWaylandShellIntegration *QWaylandXdgShellIntegrationPlugin::create(const QString &key, const QStringList ¶mList) +{ + Q_UNUSED(key); + Q_UNUSED(paramList); + return new QWaylandXdgShellIntegration(); +} + +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgactivationv1.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgactivationv1.cpp new file mode 100644 index 00000000000..8540724d879 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgactivationv1.cpp @@ -0,0 +1,54 @@ +// Copyright (C) 2020 Aleix Pol Gonzalez +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandxdgactivationv1_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandXdgActivationV1::QWaylandXdgActivationV1(wl_registry *registry, uint32_t id, + uint32_t availableVersion) + : QtWayland::xdg_activation_v1(registry, id, qMin(availableVersion, 1u)) +{ +} + +QWaylandXdgActivationV1::~QWaylandXdgActivationV1() +{ + Q_ASSERT(isInitialized()); + destroy(); +} + +QWaylandXdgActivationTokenV1 * +QWaylandXdgActivationV1::requestXdgActivationToken(QWaylandDisplay *display, + struct ::wl_surface *surface, + std::optional serial, + const QString &app_id) +{ + auto wl = get_activation_token(); + auto provider = new QWaylandXdgActivationTokenV1; + provider->init(wl); + + if (surface) + provider->set_surface(surface); + + if (!app_id.isEmpty()) + provider->set_app_id(app_id); + + if (serial && display->lastInputDevice()) + provider->set_serial(*serial, display->lastInputDevice()->wl_seat()); + provider->commit(); + return provider; +} + +QWaylandXdgActivationTokenV1::~QWaylandXdgActivationTokenV1() +{ + destroy(); +} +} + +QT_END_NAMESPACE + +#include "moc_qwaylandxdgactivationv1_p.cpp" diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgactivationv1_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgactivationv1_p.h new file mode 100644 index 00000000000..bddb5c614e0 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgactivationv1_p.h @@ -0,0 +1,59 @@ +// Copyright (C) 2020 Aleix Pol Gonzalez +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDXDGACTIVATIONV1_P_H +#define QWAYLANDXDGACTIVATIONV1_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 "qwayland-xdg-activation-v1.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; +class QWaylandSurface; + +class Q_WAYLANDCLIENT_EXPORT QWaylandXdgActivationTokenV1 + : public QObject, + public QtWayland::xdg_activation_token_v1 +{ + Q_OBJECT +public: + ~QWaylandXdgActivationTokenV1() override; + void xdg_activation_token_v1_done(const QString &token) override { Q_EMIT done(token); } + +Q_SIGNALS: + void done(const QString &token); +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandXdgActivationV1 : public QtWayland::xdg_activation_v1 +{ +public: + QWaylandXdgActivationV1(struct ::wl_registry *registry, uint32_t id, uint32_t availableVersion); + ~QWaylandXdgActivationV1() override; + + QWaylandXdgActivationTokenV1 *requestXdgActivationToken(QWaylandDisplay *display, + struct ::wl_surface *surface, + std::optional serial, + const QString &app_id); +}; + +QT_END_NAMESPACE + +} + +#endif // QWAYLANDXDGACTIVATIONV1_P_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdecorationv1.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdecorationv1.cpp new file mode 100644 index 00000000000..23b846439af --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdecorationv1.cpp @@ -0,0 +1,75 @@ +// Copyright (C) 2018 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 + +#include "qwaylandxdgdecorationv1_p.h" +#include "qwaylandxdgshell_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandXdgDecorationManagerV1::QWaylandXdgDecorationManagerV1(wl_registry *registry, uint32_t id, uint32_t availableVersion) + : QtWayland::zxdg_decoration_manager_v1(registry, id, qMin(availableVersion, 1u)) +{ +} + +QWaylandXdgDecorationManagerV1::~QWaylandXdgDecorationManagerV1() +{ + Q_ASSERT(isInitialized()); + destroy(); +} + +QWaylandXdgToplevelDecorationV1 *QWaylandXdgDecorationManagerV1::createToplevelDecoration(::xdg_toplevel *toplevel) +{ + Q_ASSERT(toplevel); + return new QWaylandXdgToplevelDecorationV1(get_toplevel_decoration(toplevel)); +} + +QWaylandXdgToplevelDecorationV1::QWaylandXdgToplevelDecorationV1(::zxdg_toplevel_decoration_v1 *decoration) + : QtWayland::zxdg_toplevel_decoration_v1(decoration) +{ +} + +QWaylandXdgToplevelDecorationV1::~QWaylandXdgToplevelDecorationV1() +{ + Q_ASSERT(isInitialized()); + destroy(); +} + +void QWaylandXdgToplevelDecorationV1::requestMode(QtWayland::zxdg_toplevel_decoration_v1::mode mode) +{ + // According to the spec the client is responsible for not requesting a mode repeatedly. + if (m_modeSet && m_requested == mode) + return; + + set_mode(mode); + m_requested = mode; + m_modeSet = true; +} + +void QWaylandXdgToplevelDecorationV1::unsetMode() +{ + unset_mode(); + m_modeSet = false; + m_requested = mode_client_side; +} + +QWaylandXdgToplevelDecorationV1::mode QWaylandXdgToplevelDecorationV1::pending() const +{ + return m_pending; +} + +bool QWaylandXdgToplevelDecorationV1::isConfigured() const +{ + return m_configured; +} + +void QtWaylandClient::QWaylandXdgToplevelDecorationV1::zxdg_toplevel_decoration_v1_configure(uint32_t mode) +{ + m_pending = zxdg_toplevel_decoration_v1::mode(mode); + m_configured = true; +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdecorationv1_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdecorationv1_p.h new file mode 100644 index 00000000000..8a0cc9e79a7 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdecorationv1_p.h @@ -0,0 +1,63 @@ +// Copyright (C) 2018 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 + +#ifndef QWAYLANDXDGDECORATIONV1_P_H +#define QWAYLANDXDGDECORATIONV1_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 "qwayland-xdg-decoration-unstable-v1.h" + +#include + +QT_BEGIN_NAMESPACE + +class QWindow; + +namespace QtWaylandClient { + +class QWaylandXdgToplevel; +class QWaylandXdgToplevelDecorationV1; + +class Q_WAYLANDCLIENT_EXPORT QWaylandXdgDecorationManagerV1 : public QtWayland::zxdg_decoration_manager_v1 +{ +public: + QWaylandXdgDecorationManagerV1(struct ::wl_registry *registry, uint32_t id, uint32_t availableVersion); + ~QWaylandXdgDecorationManagerV1() override; + QWaylandXdgToplevelDecorationV1 *createToplevelDecoration(::xdg_toplevel *toplevel); +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandXdgToplevelDecorationV1 : public QtWayland::zxdg_toplevel_decoration_v1 +{ +public: + QWaylandXdgToplevelDecorationV1(::zxdg_toplevel_decoration_v1 *decoration); + ~QWaylandXdgToplevelDecorationV1() override; + void requestMode(mode mode); + void unsetMode(); + mode pending() const; + bool isConfigured() const; + +protected: + void zxdg_toplevel_decoration_v1_configure(uint32_t mode) override; + +private: + mode m_pending = mode_client_side; + mode m_requested = mode_client_side; + bool m_modeSet = false; + bool m_configured = false; +}; + +QT_END_NAMESPACE + +} + +#endif // QWAYLANDXDGDECORATIONV1_P_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdialogv1.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdialogv1.cpp new file mode 100644 index 00000000000..abf6746232a --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdialogv1.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2023 David Reondo +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandxdgdialogv1_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandXdgDialogV1::QWaylandXdgDialogV1(::xdg_dialog_v1 *object) : xdg_dialog_v1(object) { } + +QWaylandXdgDialogV1::~QWaylandXdgDialogV1() +{ + xdg_dialog_v1_destroy(object()); +} + +QWaylandXdgDialogWmV1::QWaylandXdgDialogWmV1(wl_registry *registry, uint32_t id, int version) + : xdg_wm_dialog_v1(registry, id, version) +{ +} + +QWaylandXdgDialogWmV1::~QWaylandXdgDialogWmV1() +{ + destroy(); +} +QWaylandXdgDialogV1 *QWaylandXdgDialogWmV1::getDialog(xdg_toplevel *toplevel) +{ + return new QWaylandXdgDialogV1(get_xdg_dialog(toplevel)); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdialogv1_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdialogv1_p.h new file mode 100644 index 00000000000..f5465a63e6f --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgdialogv1_p.h @@ -0,0 +1,32 @@ +// Copyright (C) 2022 David Reondo +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDDIALOGV1_P_H +#define QWAYLANDDIALOGV1_P_H + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandXdgDialogV1 : public QtWayland::xdg_dialog_v1 +{ +public: + QWaylandXdgDialogV1(::xdg_dialog_v1 *object); + ~QWaylandXdgDialogV1() override; +}; + +class QWaylandXdgDialogWmV1 : public QtWayland::xdg_wm_dialog_v1 +{ +public: + QWaylandXdgDialogWmV1(wl_registry *registry, uint32_t id, int version); + ~QWaylandXdgDialogWmV1() override; + QWaylandXdgDialogV1 *getDialog(xdg_toplevel *toplevel); +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgexporterv2.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgexporterv2.cpp new file mode 100644 index 00000000000..5cbbecc5396 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgexporterv2.cpp @@ -0,0 +1,41 @@ +// Copyright (C) 2022 David Reondo +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandxdgexporterv2_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandXdgExportedV2::QWaylandXdgExportedV2(::zxdg_exported_v2 *object) + : QtWayland::zxdg_exported_v2(object) +{ +} + +QWaylandXdgExportedV2::~QWaylandXdgExportedV2() +{ + destroy(); +} + +void QWaylandXdgExportedV2::zxdg_exported_v2_handle(const QString &handle) +{ + mHandle = handle; +} + +QString QWaylandXdgExportedV2::handle() const +{ + return mHandle; +} + +QWaylandXdgExporterV2::QWaylandXdgExporterV2(wl_registry *registry, uint32_t id, int version) + : QtWayland::zxdg_exporter_v2(registry, id, qMin(version, 1)) +{ +} + +QWaylandXdgExporterV2::~QWaylandXdgExporterV2() +{ + destroy(); +} +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgexporterv2_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgexporterv2_p.h new file mode 100644 index 00000000000..c993e390a24 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgexporterv2_p.h @@ -0,0 +1,49 @@ +// Copyright (C) 2022 David Reondo +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDXDGEXPORTERV2_H +#define QWAYLANDXDGEXPORTERV2_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandXdgExportedV2 : public QtWayland::zxdg_exported_v2 +{ +public: + explicit QWaylandXdgExportedV2(::zxdg_exported_v2 *object); + ~QWaylandXdgExportedV2() override; + QString handle() const; + +private: + void zxdg_exported_v2_handle(const QString &handle) override; + QString mHandle; +}; + +class QWaylandXdgExporterV2 : public QtWayland::zxdg_exporter_v2 +{ +public: + QWaylandXdgExporterV2(wl_registry *registry, uint32_t id, int version); + ~QWaylandXdgExporterV2() override; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDXDGEXPORTERV2_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp new file mode 100644 index 00000000000..338d66ea19f --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp @@ -0,0 +1,856 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2017 Eurogiciel, author: +// Copyright (C) 2023 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandxdgshell_p.h" + +#include "qwaylandxdgexporterv2_p.h" +#include "qwaylandxdgdialogv1_p.h" +#include "qwaylandxdgtopleveliconv1_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandXdgSurface::Toplevel::Toplevel(QWaylandXdgSurface *xdgSurface) + : QtWayland::xdg_toplevel(xdgSurface->get_toplevel()) + , m_xdgSurface(xdgSurface) +{ + QWindow *window = xdgSurface->window()->window(); + if (auto *decorationManager = m_xdgSurface->m_shell->decorationManager()) { + if (!(window->flags() & Qt::FramelessWindowHint)) + m_decoration = decorationManager->createToplevelDecoration(object()); + } + requestWindowStates(window->windowStates()); + requestWindowFlags(window->flags()); + if (auto transientParent = xdgSurface->window()->transientParent()) { + auto parentSurface = qobject_cast(transientParent->shellSurface()); + if (parentSurface && parentSurface->m_toplevel) + set_parent(parentSurface->m_toplevel->object()); + } + + // Always use XDG Dialog, a window could be assigned a parent through XDG Foreign. + if (window->modality() != Qt::NonModal && m_xdgSurface->m_shell->m_xdgDialogWm) { + m_xdgDialog.reset(m_xdgSurface->m_shell->m_xdgDialogWm->getDialog(object())); + m_xdgDialog->set_modal(); + } +} + +QWaylandXdgSurface::Toplevel::~Toplevel() +{ + // The protocol spec requires that the decoration object is deleted before xdg_toplevel. + delete m_decoration; + m_decoration = nullptr; + + if (isInitialized()) + destroy(); +} + +void QWaylandXdgSurface::Toplevel::applyConfigure() +{ + if (!(m_applied.states & (Qt::WindowMaximized|Qt::WindowFullScreen))) + m_normalSize = m_xdgSurface->m_window->windowContentGeometry().size(); + + if ((m_pending.states & Qt::WindowActive) && !(m_applied.states & Qt::WindowActive) + && !m_xdgSurface->m_window->display()->isKeyboardAvailable()) + m_xdgSurface->m_window->display()->handleWindowActivated(m_xdgSurface->m_window); + + if (!(m_pending.states & Qt::WindowActive) && (m_applied.states & Qt::WindowActive) + && !m_xdgSurface->m_window->display()->isKeyboardAvailable()) + m_xdgSurface->m_window->display()->handleWindowDeactivated(m_xdgSurface->m_window); + + m_xdgSurface->m_window->handleToplevelWindowTilingStatesChanged(m_toplevelStates); + m_xdgSurface->m_window->handleWindowStatesChanged(m_pending.states); + + // If the width or height is zero, the client should decide the size on its own. + QSize surfaceSize; + + if (m_pending.size.width() > 0) { + surfaceSize.setWidth(m_pending.size.width()); + } else { + if (Q_UNLIKELY(m_pending.states & (Qt::WindowMaximized | Qt::WindowFullScreen))) { + qCWarning(lcQpaWayland) << "Configure event with maximized or fullscreen state contains invalid width:" << m_pending.size.width(); + } else { + int width = m_normalSize.width(); + if (!m_pending.bounds.isEmpty()) + width = std::min(width, m_pending.bounds.width()); + surfaceSize.setWidth(width); + } + } + + if (m_pending.size.height() > 0) { + surfaceSize.setHeight(m_pending.size.height()); + } else { + if (Q_UNLIKELY(m_pending.states & (Qt::WindowMaximized | Qt::WindowFullScreen))) { + qCWarning(lcQpaWayland) << "Configure event with maximized or fullscreen state contains invalid height:" << m_pending.size.height(); + } else { + int height = m_normalSize.height(); + if (!m_pending.bounds.isEmpty()) + height = std::min(height, m_pending.bounds.height()); + surfaceSize.setHeight(height); + } + } + + m_applied = m_pending; + + if (!surfaceSize.isEmpty()) + m_xdgSurface->m_window->resizeFromApplyConfigure(surfaceSize.grownBy(m_xdgSurface->m_window->windowContentMargins())); + + qCDebug(lcQpaWayland) << "Applied pending xdg_toplevel configure event:" << m_applied.size + << "and" << m_applied.states + << ", suspended " << m_applied.suspended; +} + +bool QWaylandXdgSurface::Toplevel::wantsDecorations() +{ + if (m_decoration && (m_decoration->pending() == QWaylandXdgToplevelDecorationV1::mode_server_side + || !m_decoration->isConfigured())) + return false; + + return !(m_pending.states & Qt::WindowFullScreen); +} + +void QWaylandXdgSurface::Toplevel::xdg_toplevel_configure_bounds(int32_t width, int32_t height) +{ + m_pending.bounds = QSize(width, height); +} + +void QWaylandXdgSurface::Toplevel::xdg_toplevel_configure(int32_t width, int32_t height, wl_array *states) +{ + m_pending.size = QSize(width, height); + + auto *xdgStates = static_cast(states->data); + size_t numStates = states->size / sizeof(uint32_t); + + m_pending.suspended = false; + m_pending.states = Qt::WindowNoState; + m_toplevelStates = QWaylandWindow::WindowNoState; + + for (size_t i = 0; i < numStates; i++) { + switch (xdgStates[i]) { + case XDG_TOPLEVEL_STATE_ACTIVATED: + m_pending.states |= Qt::WindowActive; + break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: + m_pending.states |= Qt::WindowMaximized; + break; + case XDG_TOPLEVEL_STATE_FULLSCREEN: + m_pending.states |= Qt::WindowFullScreen; + break; + case XDG_TOPLEVEL_STATE_TILED_LEFT: + m_toplevelStates |= QWaylandWindow::WindowTiledLeft; + break; + case XDG_TOPLEVEL_STATE_TILED_RIGHT: + m_toplevelStates |= QWaylandWindow::WindowTiledRight; + break; + case XDG_TOPLEVEL_STATE_TILED_TOP: + m_toplevelStates |= QWaylandWindow::WindowTiledTop; + break; + case XDG_TOPLEVEL_STATE_TILED_BOTTOM: + m_toplevelStates |= QWaylandWindow::WindowTiledBottom; + break; + case XDG_TOPLEVEL_STATE_SUSPENDED: + m_pending.suspended = true; + break; + default: + break; + } + } + qCDebug(lcQpaWayland) << "Received xdg_toplevel.configure with" << m_pending.size + << "and" << m_pending.states + << ", suspended " << m_pending.suspended; +} + +void QWaylandXdgSurface::Toplevel::xdg_toplevel_close() +{ + QWindowSystemInterface::handleCloseEvent(m_xdgSurface->m_window->window()); +} + +void QWaylandXdgSurface::Toplevel::requestWindowFlags(Qt::WindowFlags flags) +{ + if (m_decoration) { + if (flags & Qt::FramelessWindowHint) { + delete m_decoration; + m_decoration = nullptr; + } else { + m_decoration->unsetMode(); + } + } +} + +void QWaylandXdgSurface::Toplevel::requestWindowStates(Qt::WindowStates states) +{ + // Re-send what's different from the applied state + Qt::WindowStates changedStates = m_applied.states ^ states; + + // Minimized state is not reported by the protocol, so always send it + if (states & Qt::WindowMinimized) { + set_minimized(); + m_xdgSurface->window()->handleWindowStatesChanged(states & ~Qt::WindowMinimized); + // The internal window state whilst minimized is not maximised or fullscreen, but we don't want to + // update the compositors cached version of this state + return; + } + + if (changedStates & Qt::WindowMaximized) { + if (states & Qt::WindowMaximized) + set_maximized(); + else + unset_maximized(); + } + + if (changedStates & Qt::WindowFullScreen) { + if (states & Qt::WindowFullScreen) { + auto screen = m_xdgSurface->window()->waylandScreen(); + if (screen) { + set_fullscreen(screen->output()); + } + } else + unset_fullscreen(); + } + + +} + +QtWayland::xdg_toplevel::resize_edge QWaylandXdgSurface::Toplevel::convertToResizeEdges(Qt::Edges edges) +{ + return static_cast( + ((edges & Qt::TopEdge) ? resize_edge_top : 0) + | ((edges & Qt::BottomEdge) ? resize_edge_bottom : 0) + | ((edges & Qt::LeftEdge) ? resize_edge_left : 0) + | ((edges & Qt::RightEdge) ? resize_edge_right : 0)); +} + +QWaylandXdgSurface::Popup::Popup(QWaylandXdgSurface *xdgSurface, QWaylandWindow *parent, + Positioner *positioner) + : m_xdgSurface(xdgSurface) + , m_parentXdgSurface(qobject_cast(parent->shellSurface())) + , m_parent(parent) +{ + + init(xdgSurface->get_popup(m_parentXdgSurface ? m_parentXdgSurface->object() : nullptr, + positioner->object())); +} + +QWaylandXdgSurface::Popup::~Popup() +{ + if (isInitialized()) + destroy(); + + if (m_grabbing) { + m_grabbing = false; + + // Synthesize Qt enter/leave events for popup + QWindow *leave = nullptr; + if (m_xdgSurface && m_xdgSurface->window()) + leave = m_xdgSurface->window()->window(); + QWindowSystemInterface::handleLeaveEvent(leave); + + QWindow *enter = nullptr; + if (m_parentXdgSurface && m_parentXdgSurface->window()) { + enter = m_parentXdgSurface->window()->window(); + const auto pos = m_xdgSurface->window()->display()->waylandCursor()->pos(); + QWindowSystemInterface::handleEnterEvent(enter, enter->handle()->mapFromGlobal(pos), pos); + } + } +} + +void QWaylandXdgSurface::Popup::applyConfigure() +{ + if (m_pendingGeometry.isValid()) { + QRect geometryWithMargins = m_pendingGeometry.marginsAdded(m_xdgSurface->m_window->windowContentMargins()); + QMargins parentMargins = m_parent->windowContentMargins() - m_parent->clientSideMargins(); + QRect globalGeometry = geometryWithMargins.translated(m_parent->geometry().topLeft() + QPoint(parentMargins.left(), parentMargins.top())); + m_xdgSurface->setGeometryFromApplyConfigure(globalGeometry.topLeft(), globalGeometry.size()); + } + resetConfiguration(); +} + +void QWaylandXdgSurface::Popup::resetConfiguration() +{ + m_pendingGeometry = QRect(); +} + +void QWaylandXdgSurface::Popup::grab(QWaylandInputDevice *seat, uint serial) +{ + xdg_popup::grab(seat->wl_seat(), serial); + m_grabbing = true; +} + +void QWaylandXdgSurface::Popup::xdg_popup_configure(int32_t x, int32_t y, int32_t width, int32_t height) +{ + m_pendingGeometry = QRect(x, y, width, height); +} + +void QWaylandXdgSurface::Popup::xdg_popup_popup_done() +{ + QWindowSystemInterface::handleCloseEvent(m_xdgSurface->m_window->window()); +} + +void QWaylandXdgSurface::Popup::xdg_popup_repositioned(uint32_t token) +{ + if (token == m_waitingForRepositionSerial) + m_waitingForReposition = false; +} + +QWaylandXdgSurface::QWaylandXdgSurface(QWaylandXdgShell *shell, ::xdg_surface *surface, QWaylandWindow *window) + : QWaylandShellSurface(window) + , xdg_surface(surface) + , m_shell(shell) + , m_window(window) +{ + QWaylandDisplay *display = window->display(); + Qt::WindowType type = window->window()->type(); + auto *transientParent = window->transientParent(); + + if (type == Qt::ToolTip) { + if (transientParent) { + setPopup(transientParent); + } else { + qCWarning(lcQpaWayland) << "Failed to create popup. Ensure popup " << window->window() << "has a transientParent set."; + QWindowSystemInterface::handleCloseEvent(m_window->window()); + } + } else if (type == Qt::Popup ) { + if (transientParent && display->lastInputDevice()) { + setGrabPopup(transientParent, display->lastInputDevice(), display->lastInputSerial()); + } else { + qCWarning(lcQpaWayland) << "Failed to create grabbing popup. Ensure popup " << window->window() << "has a transientParent set and that parent window has received input."; + QWindowSystemInterface::handleCloseEvent(m_window->window()); + } + } else { + setToplevel(); + } + setSizeHints(); +} + +QWaylandXdgSurface::~QWaylandXdgSurface() +{ + if (m_toplevel) { + delete m_toplevel; + m_toplevel = nullptr; + } + if (m_popup) { + delete m_popup; + m_popup = nullptr; + } + destroy(); +} + +bool QWaylandXdgSurface::resize(QWaylandInputDevice *inputDevice, Qt::Edges edges) +{ + if (!m_toplevel || !m_toplevel->isInitialized()) + return false; + + auto resizeEdges = Toplevel::convertToResizeEdges(edges); + m_toplevel->resize(inputDevice->wl_seat(), inputDevice->serial(), resizeEdges); + return true; +} + +bool QWaylandXdgSurface::move(QWaylandInputDevice *inputDevice) +{ + if (m_toplevel && m_toplevel->isInitialized()) { + m_toplevel->move(inputDevice->wl_seat(), inputDevice->serial()); + return true; + } + return false; +} + +bool QWaylandXdgSurface::showWindowMenu(QWaylandInputDevice *seat) +{ + if (m_toplevel && m_toplevel->isInitialized()) { + QPoint position = seat->pointerSurfacePosition().toPoint(); + m_toplevel->show_window_menu(seat->wl_seat(), seat->serial(), position.x(), position.y()); + return true; + } + return false; +} + +void QWaylandXdgSurface::setTitle(const QString &title) +{ + if (m_toplevel) + m_toplevel->set_title(title); +} + +void QWaylandXdgSurface::setAppId(const QString &appId) +{ + if (m_toplevel) + m_toplevel->set_app_id(appId); + + m_appId = appId; +} + +void QWaylandXdgSurface::setWindowFlags(Qt::WindowFlags flags) +{ + if (m_toplevel) + m_toplevel->requestWindowFlags(flags); +} + +bool QWaylandXdgSurface::isExposed() const +{ + if (m_toplevel && m_toplevel->m_applied.suspended) + return false; + + // the popup repositioning specification is async + // we need to defer commits between our resize request + // and our new popup position being set + if (m_popup && m_popup->m_waitingForReposition) + return false; + + return m_configured; +} + +bool QWaylandXdgSurface::handleExpose(const QRegion ®ion) +{ + if (!isExposed() && !region.isEmpty()) { + return true; + } + setContentGeometry(window()->windowContentGeometry()); + return false; +} + +void QWaylandXdgSurface::applyConfigure() +{ + // It is a redundant ack_configure, so skipped. + if (m_pendingConfigureSerial == m_appliedConfigureSerial) + return; + + m_appliedConfigureSerial = m_pendingConfigureSerial; + + m_configured = true; + ack_configure(m_appliedConfigureSerial); + + if (m_toplevel) + m_toplevel->applyConfigure(); + if (m_popup) + m_popup->applyConfigure(); + + window()->updateExposure(); +} + +bool QWaylandXdgSurface::wantsDecorations() const +{ + return m_toplevel && m_toplevel->wantsDecorations(); +} + +void QWaylandXdgSurface::propagateSizeHints() +{ + setSizeHints(); +} + +void QWaylandXdgSurface::setContentGeometry(const QRect &rect) +{ + if (window()->isExposed()) + set_window_geometry(rect.x(), rect.y(), rect.width(), rect.height()); +} + +void QWaylandXdgSurface::setSizeHints() +{ + if (m_toplevel && m_window) { + const QMargins margins = m_window->windowContentMargins() - m_window->clientSideMargins(); + const QSize minSize = m_window->windowMinimumSize().shrunkBy(margins); + const QSize maxSize = m_window->windowMaximumSize().shrunkBy(margins); + const int minWidth = qMax(0, minSize.width()); + const int minHeight = qMax(0, minSize.height()); + int maxWidth = qMax(0, maxSize.width()); + int maxHeight = qMax(0, maxSize.height()); + + // It will not change min/max sizes if invalid. + if (minWidth > maxWidth || minHeight > maxHeight) + return; + + if (maxWidth == QWINDOWSIZE_MAX) + maxWidth = 0; + if (maxHeight == QWINDOWSIZE_MAX) + maxHeight = 0; + + m_toplevel->set_min_size(minWidth, minHeight); + m_toplevel->set_max_size(maxWidth, maxHeight); + } +} + +void *QWaylandXdgSurface::nativeResource(const QByteArray &resource) +{ + QByteArray lowerCaseResource = resource.toLower(); + if (lowerCaseResource == "xdg_surface") + return object(); + else if (lowerCaseResource == "xdg_toplevel" && m_toplevel) + return m_toplevel->object(); + else if (lowerCaseResource == "xdg_popup" && m_popup) + return m_popup->object(); + return nullptr; +} + +std::any QWaylandXdgSurface::surfaceRole() const +{ + if (m_toplevel) + return m_toplevel->object(); + if (m_popup) + return m_popup->object(); + return {}; +} + +void QWaylandXdgSurface::requestWindowStates(Qt::WindowStates states) +{ + if (m_toplevel) + m_toplevel->requestWindowStates(states); + else + qCDebug(lcQpaWayland) << "Ignoring window states requested by non-toplevel zxdg_surface_v6."; +} + +void QWaylandXdgSurface::setToplevel() +{ + Q_ASSERT(!m_toplevel && !m_popup); + m_toplevel = new Toplevel(this); +} + +void QWaylandXdgSurface::setPopup(QWaylandWindow *parent) +{ + Q_ASSERT(!m_toplevel && !m_popup); + + std::unique_ptr positioner = createPositioner(parent); + m_popup = new Popup(this, parent, positioner.get()); +} + +void QWaylandXdgSurface::setGrabPopup(QWaylandWindow *parent, QWaylandInputDevice *device, int serial) +{ + setPopup(parent); + m_popup->grab(device, serial); + + // Synthesize Qt enter/leave events for popup + if (!parent) + return; + QWindow *leave = parent->window(); + QWindowSystemInterface::handleLeaveEvent(leave); + + QWindow *enter = nullptr; + if (m_popup && m_popup->m_xdgSurface && m_popup->m_xdgSurface->window()) + enter = m_popup->m_xdgSurface->window()->window(); + + if (enter) { + const auto pos = m_popup->m_xdgSurface->window()->display()->waylandCursor()->pos(); + QWindowSystemInterface::handleEnterEvent(enter, enter->handle()->mapFromGlobal(pos), pos); + } +} + +void QWaylandXdgSurface::xdg_surface_configure(uint32_t serial) +{ + m_pendingConfigureSerial = serial; + if (!m_configured) { + // We have to do the initial applyConfigure() immediately, since that is the expose. + applyConfigure(); + } else { + // Later configures are probably resizes, so we have to queue them up for a time when we + // are not painting to the window. + m_window->applyConfigureWhenPossible(); + } +} + +bool QWaylandXdgSurface::requestActivate() +{ + if (auto *activation = m_shell->activation()) { + if (!m_activationToken.isEmpty()) { + activation->activate(m_activationToken, window()->wlSurface()); + m_activationToken = {}; + return true; + } else if (const auto token = qEnvironmentVariable("XDG_ACTIVATION_TOKEN"); !token.isEmpty()) { + activation->activate(token, window()->wlSurface()); + qunsetenv("XDG_ACTIVATION_TOKEN"); + return true; + } else { + const auto focusWindow = QGuiApplication::focusWindow(); + // At least GNOME requires to request the token in order to get the + // focus stealing prevention indication, so requestXdgActivationToken call + // is still necessary in that case. + const auto wlWindow = focusWindow ? static_cast(focusWindow->handle()) : m_window; + + QString appId; + if (const auto xdgSurface = qobject_cast(wlWindow->shellSurface())) + appId = xdgSurface->m_appId; + + std::optional serial; + if (const auto seat = wlWindow->display()->lastInputDevice()) + serial = seat->serial(); + + const auto tokenProvider = activation->requestXdgActivationToken( + wlWindow->display(), wlWindow->wlSurface(), serial, appId); + connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, this, + [this](const QString &token) { + m_shell->activation()->activate(token, window()->wlSurface()); + }); + connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, tokenProvider, &QObject::deleteLater); + return true; + } + } + return false; +} + +bool QWaylandXdgSurface::requestActivateOnShow() +{ + const Qt::WindowType type = m_window->window()->type(); + if (type == Qt::ToolTip || type == Qt::Popup || type == Qt::SplashScreen) + return false; + + const Qt::WindowFlags flags = m_window->window()->flags(); + if (flags & Qt::WindowDoesNotAcceptFocus) + return false; + + if (m_window->window()->property("_q_showWithoutActivating").toBool()) + return false; + + return requestActivate(); +} + +void QWaylandXdgSurface::requestXdgActivationToken(quint32 serial) +{ + if (auto *activation = m_shell->activation()) { + auto tokenProvider = activation->requestXdgActivationToken( + m_shell->m_display, m_window->wlSurface(), serial, m_appId); + connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, m_window, &QWaylandWindow::xdgActivationTokenCreated); + connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, tokenProvider, &QObject::deleteLater); + } else { + QWaylandShellSurface::requestXdgActivationToken(serial); + } +} + +void QWaylandXdgSurface::setXdgActivationToken(const QString &token) +{ + if (m_shell->activation()) { + m_activationToken = token; + } else { + qCWarning(lcQpaWayland) << "zxdg_activation_v1 not available"; + } +} + +void QWaylandXdgSurface::setAlertState(bool enabled) +{ + if (m_alertState == enabled) + return; + + m_alertState = enabled; + + if (!m_alertState) + return; + + auto *activation = m_shell->activation(); + if (!activation) + return; + + const auto tokenProvider = activation->requestXdgActivationToken( + m_shell->m_display, m_window->wlSurface(), std::nullopt, m_appId); + connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, this, + [this](const QString &token) { + m_shell->activation()->activate(token, m_window->wlSurface()); + }); + connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, tokenProvider, &QObject::deleteLater); +} + +QString QWaylandXdgSurface::externWindowHandle() +{ + if (!m_toplevel || !m_shell->exporter()) { + return QString(); + } + if (!m_toplevel->m_exported) { + auto *exporterWrapper = static_cast( + wl_proxy_create_wrapper(m_shell->exporter()->object())); + auto exportQueue = wl_display_create_queue(m_shell->display()->wl_display()); + wl_proxy_set_queue(reinterpret_cast(exporterWrapper), exportQueue); + m_toplevel->m_exported.reset(new QWaylandXdgExportedV2( + zxdg_exporter_v2_export_toplevel(exporterWrapper, m_window->wlSurface()))); + // handle events is sent immediately + wl_display_roundtrip_queue(m_shell->display()->wl_display(), exportQueue); + + wl_proxy_set_queue(reinterpret_cast(m_toplevel->m_exported->object()), nullptr); + wl_proxy_wrapper_destroy(exporterWrapper); + wl_event_queue_destroy(exportQueue); + } + return m_toplevel->m_exported->handle(); +} + +void QWaylandXdgSurface::setWindowPosition(const QPoint &position) +{ + Q_UNUSED(position); + + if (!m_popup) + return; + + if (m_popup->version() < XDG_POPUP_REPOSITIONED_SINCE_VERSION) + return; + + std::unique_ptr positioner = createPositioner(m_window->transientParent()); + m_popup->m_waitingForRepositionSerial++; + m_popup->reposition(positioner->object(), m_popup->m_waitingForRepositionSerial); + m_popup->m_waitingForReposition = true; + window()->updateExposure(); +} + +std::unique_ptr QWaylandXdgSurface::createPositioner(QWaylandWindow *parent) +{ + std::unique_ptr positioner(new Positioner(m_shell)); + // set_popup expects a position relative to the parent + QRect windowGeometry = m_window->windowContentGeometry(); + QMargins windowMargins = m_window->windowContentMargins() - m_window->clientSideMargins(); + QMargins parentMargins = parent->windowContentMargins() - parent->clientSideMargins(); + + // These property overrides may be removed when public API becomes available + QRect placementAnchor = m_window->window()->property("_q_waylandPopupAnchorRect").toRect(); + if (!placementAnchor.isValid()) { + placementAnchor = QRect(m_window->geometry().topLeft() - parent->geometry().topLeft(), QSize(1,1)); + } + placementAnchor.translate(windowMargins.left(), windowMargins.top()); + placementAnchor.translate(-parentMargins.left(), -parentMargins.top()); + + uint32_t anchor = QtWayland::xdg_positioner::anchor_top_left; + const QVariant anchorVariant = m_window->window()->property("_q_waylandPopupAnchor"); + if (anchorVariant.isValid()) { + switch (anchorVariant.value()) { + case Qt::Edges(): + anchor = QtWayland::xdg_positioner::anchor_none; + break; + case Qt::TopEdge: + anchor = QtWayland::xdg_positioner::anchor_top; + break; + case Qt::TopEdge | Qt::RightEdge: + anchor = QtWayland::xdg_positioner::anchor_top_right; + break; + case Qt::RightEdge: + anchor = QtWayland::xdg_positioner::anchor_right; + break; + case Qt::BottomEdge | Qt::RightEdge: + anchor = QtWayland::xdg_positioner::anchor_bottom_right; + break; + case Qt::BottomEdge: + anchor = QtWayland::xdg_positioner::anchor_bottom; + break; + case Qt::BottomEdge | Qt::LeftEdge: + anchor = QtWayland::xdg_positioner::anchor_bottom_left; + break; + case Qt::LeftEdge: + anchor = QtWayland::xdg_positioner::anchor_left; + break; + case Qt::TopEdge | Qt::LeftEdge: + anchor = QtWayland::xdg_positioner::anchor_top_left; + break; + } + } + + uint32_t gravity = QtWayland::xdg_positioner::gravity_bottom_right; + const QVariant popupGravityVariant = m_window->window()->property("_q_waylandPopupGravity"); + if (popupGravityVariant.isValid()) { + switch (popupGravityVariant.value()) { + case Qt::Edges(): + gravity = QtWayland::xdg_positioner::gravity_none; + break; + case Qt::TopEdge: + gravity = QtWayland::xdg_positioner::gravity_top; + break; + case Qt::TopEdge | Qt::RightEdge: + gravity = QtWayland::xdg_positioner::gravity_top_right; + break; + case Qt::RightEdge: + gravity = QtWayland::xdg_positioner::gravity_right; + break; + case Qt::BottomEdge | Qt::RightEdge: + gravity = QtWayland::xdg_positioner::gravity_bottom_right; + break; + case Qt::BottomEdge: + gravity = QtWayland::xdg_positioner::gravity_bottom; + break; + case Qt::BottomEdge | Qt::LeftEdge: + gravity = QtWayland::xdg_positioner::gravity_bottom_left; + break; + case Qt::LeftEdge: + gravity = QtWayland::xdg_positioner::gravity_left; + break; + case Qt::TopEdge | Qt::LeftEdge: + gravity = QtWayland::xdg_positioner::gravity_top_left; + break; + } + } + + uint32_t constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_slide_x | QtWayland::xdg_positioner::constraint_adjustment_slide_y; + const QVariant constraintAdjustmentVariant = m_window->window()->property("_q_waylandPopupConstraintAdjustment"); + if (constraintAdjustmentVariant.isValid()) { + constraintAdjustment = constraintAdjustmentVariant.toUInt(); + } + + positioner->set_anchor_rect(placementAnchor.x(), + placementAnchor.y(), + placementAnchor.width(), + placementAnchor.height()); + positioner->set_anchor(anchor); + positioner->set_gravity(gravity); + positioner->set_size(windowGeometry.width(), windowGeometry.height()); + positioner->set_constraint_adjustment(constraintAdjustment); + return positioner; +} + + +void QWaylandXdgSurface::setIcon(const QIcon &icon) +{ + if (!m_shell->m_topLevelIconManager || !m_toplevel) + return; + + m_shell->m_topLevelIconManager->setIcon(icon, m_toplevel->object()); +} + +QWaylandXdgShell::QWaylandXdgShell(QWaylandDisplay *display, QtWayland::xdg_wm_base *xdgWmBase) + : m_display(display), m_xdgWmBase(xdgWmBase) +{ + display->addRegistryListener(&QWaylandXdgShell::handleRegistryGlobal, this); +} + +QWaylandXdgShell::~QWaylandXdgShell() +{ + m_display->removeListener(&QWaylandXdgShell::handleRegistryGlobal, this); +} + +void QWaylandXdgShell::handleRegistryGlobal(void *data, wl_registry *registry, uint id, + const QString &interface, uint version) +{ + QWaylandXdgShell *xdgShell = static_cast(data); + if (interface == QLatin1String(QWaylandXdgDecorationManagerV1::interface()->name)) + xdgShell->m_xdgDecorationManager.reset(new QWaylandXdgDecorationManagerV1(registry, id, version)); + + if (interface == QLatin1String(QWaylandXdgActivationV1::interface()->name)) { + xdgShell->m_xdgActivation.reset(new QWaylandXdgActivationV1(registry, id, version)); + } + + if (interface == QLatin1String(QWaylandXdgExporterV2::interface()->name)) { + xdgShell->m_xdgExporter.reset(new QWaylandXdgExporterV2(registry, id, version)); + } + + if (interface == QLatin1String(QWaylandXdgDialogWmV1::interface()->name)) { + xdgShell->m_xdgDialogWm.reset(new QWaylandXdgDialogWmV1(registry, id, version)); + } + if (interface == QLatin1String(QtWayland::xdg_toplevel_icon_manager_v1::interface()->name)) { + xdgShell->m_topLevelIconManager.reset( + new QWaylandXdgToplevelIconManagerV1(xdgShell->m_display, registry, id, version)); + } +} + +QWaylandXdgSurface::Positioner::Positioner(QWaylandXdgShell *xdgShell) + : QtWayland::xdg_positioner(xdgShell->m_xdgWmBase->create_positioner()) +{ +} + +QWaylandXdgSurface::Positioner::~Positioner() +{ + destroy(); +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylandxdgshell_p.cpp" diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h new file mode 100644 index 00000000000..12f52da5ea6 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h @@ -0,0 +1,200 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2017 Eurogiciel, author: +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDXDGSHELL_H +#define QWAYLANDXDGSHELL_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 "qwayland-xdg-shell.h" + +#include "qwaylandxdgdecorationv1_p.h" +#include "qwaylandxdgactivationv1_p.h" + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; +class QWaylandInputDevice; +class QWaylandXdgShell; +class QWaylandXdgExportedV2; +class QWaylandShmBuffer; +class QWaylandXdgExporterV2; +class QWaylandXdgDialogWmV1; +class QWaylandXdgDialogV1; +class QWaylandXdgToplevelIconManagerV1; + +class Q_WAYLANDCLIENT_EXPORT QWaylandXdgSurface : public QWaylandShellSurface, public QtWayland::xdg_surface +{ + Q_OBJECT +public: + QWaylandXdgSurface(QWaylandXdgShell *shell, ::xdg_surface *surface, QWaylandWindow *window); + ~QWaylandXdgSurface() override; + + bool resize(QWaylandInputDevice *inputDevice, Qt::Edges edges) override; + bool move(QWaylandInputDevice *inputDevice) override; + bool showWindowMenu(QWaylandInputDevice *seat) override; + void setTitle(const QString &title) override; + void setAppId(const QString &appId) override; + void setWindowFlags(Qt::WindowFlags flags) override; + + bool isExposed() const override; + bool handleExpose(const QRegion &) override; + bool handlesActiveState() const { return m_toplevel; } + void applyConfigure() override; + bool wantsDecorations() const override; + void propagateSizeHints() override; + void setContentGeometry(const QRect &rect) override; + bool requestActivate() override; + bool requestActivateOnShow() override; + void setXdgActivationToken(const QString &token) override; + void requestXdgActivationToken(quint32 serial) override; + void setAlertState(bool enabled) override; + bool isAlertState() const override { return m_alertState; } + QString externWindowHandle() override; + void setWindowPosition(const QPoint &position) override; + void setIcon(const QIcon &icon) override; + + void setSizeHints(); + + void *nativeResource(const QByteArray &resource); + + std::any surfaceRole() const override; + +protected: + void requestWindowStates(Qt::WindowStates states) override; + void xdg_surface_configure(uint32_t serial) override; + +private: + class Toplevel: public QtWayland::xdg_toplevel + { + public: + Toplevel(QWaylandXdgSurface *xdgSurface); + ~Toplevel() override; + + void applyConfigure(); + bool wantsDecorations(); + + void xdg_toplevel_configure(int32_t width, int32_t height, wl_array *states) override; + void xdg_toplevel_close() override; + void xdg_toplevel_configure_bounds(int32_t width, int32_t height) override; + + void requestWindowFlags(Qt::WindowFlags flags); + void requestWindowStates(Qt::WindowStates states); + + static resize_edge convertToResizeEdges(Qt::Edges edges); + + struct { + QSize bounds = {0, 0}; + QSize size = {0, 0}; + Qt::WindowStates states = Qt::WindowNoState; + bool suspended = false; + } m_pending, m_applied; + QWaylandWindow::ToplevelWindowTilingStates m_toplevelStates = QWaylandWindow::WindowNoState; + QSize m_normalSize; + + QWaylandXdgSurface *m_xdgSurface = nullptr; + QWaylandXdgToplevelDecorationV1 *m_decoration = nullptr; + QScopedPointer m_exported; + QScopedPointer m_xdgDialog; + }; + + class Positioner : public QtWayland::xdg_positioner { + public: + Positioner(QWaylandXdgShell *xdgShell); + ~Positioner() override; + }; + + class Popup : public QtWayland::xdg_popup { + public: + Popup(QWaylandXdgSurface *xdgSurface, QWaylandWindow *parent, Positioner *positioner); + ~Popup() override; + + void applyConfigure(); + void resetConfiguration(); + + void grab(QWaylandInputDevice *seat, uint serial); + void xdg_popup_configure(int32_t x, int32_t y, int32_t width, int32_t height) override; + void xdg_popup_popup_done() override; + void xdg_popup_repositioned(uint32_t token) override; + + QWaylandXdgSurface *m_xdgSurface = nullptr; + QWaylandXdgSurface *m_parentXdgSurface = nullptr; + QWaylandWindow *m_parent = nullptr; + bool m_grabbing = false; + + QRect m_pendingGeometry; + bool m_waitingForReposition = false; + uint32_t m_waitingForRepositionSerial = 0; + }; + + void setToplevel(); + void setPopup(QWaylandWindow *parent); + void setGrabPopup(QWaylandWindow *parent, QWaylandInputDevice *device, int serial); + std::unique_ptr createPositioner(QWaylandWindow *parent); + + QWaylandXdgShell *m_shell = nullptr; + QWaylandWindow *m_window = nullptr; + Toplevel *m_toplevel = nullptr; + Popup *m_popup = nullptr; + bool m_configured = false; + uint m_pendingConfigureSerial = 0; + uint m_appliedConfigureSerial = 0; + QString m_activationToken; + QString m_appId; + bool m_alertState = false; + + friend class QWaylandXdgShell; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandXdgShell +{ +public: + QWaylandXdgShell(QWaylandDisplay *display, QtWayland::xdg_wm_base *xdg_wm_base); + ~QWaylandXdgShell(); + + QWaylandDisplay *display() const { return m_display; } + + QWaylandXdgDecorationManagerV1 *decorationManager() { return m_xdgDecorationManager.data(); } + QWaylandXdgActivationV1 *activation() const { return m_xdgActivation.data(); } + QWaylandXdgExporterV2 *exporter() const { return m_xdgExporter.data(); } + QWaylandXdgSurface *getXdgSurface(QWaylandWindow *window); + +private: + static void handleRegistryGlobal(void *data, ::wl_registry *registry, uint id, + const QString &interface, uint version); + + QWaylandDisplay *m_display = nullptr; + QtWayland::xdg_wm_base *m_xdgWmBase = nullptr; + QScopedPointer m_xdgDecorationManager; + QScopedPointer m_xdgActivation; + QScopedPointer m_xdgExporter; + QScopedPointer m_xdgDialogWm; + QScopedPointer m_topLevelIconManager; + + friend class QWaylandXdgSurface; +}; + +QT_END_NAMESPACE + +} + +#endif // QWAYLANDXDGSHELL_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration.cpp new file mode 100644 index 00000000000..f1bb8bee478 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration.cpp @@ -0,0 +1,61 @@ +// Copyright (C) 2017 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 + +#include "qwaylandxdgshell_p.h" +#include "qwaylandxdgshellintegration_p.h" +#include "qwaylandxdgdecorationv1_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandXdgShellIntegration::QWaylandXdgShellIntegration() : QWaylandShellIntegrationTemplate(6) +{ + connect(this, &QWaylandShellIntegrationTemplate::activeChanged, this, [this] { + if (isActive()) { + mXdgShell.reset(new QWaylandXdgShell(mDisplay, this)); + } else { + mXdgShell.reset(nullptr); + destroy(); + } + }); +} + +QWaylandXdgShellIntegration::~QWaylandXdgShellIntegration() +{ + if (isActive()) + destroy(); +} + +bool QWaylandXdgShellIntegration::initialize(QWaylandDisplay *display) +{ + mDisplay = display; + return QWaylandShellIntegrationTemplate::initialize(display); +} + +void QWaylandXdgShellIntegration::xdg_wm_base_ping(uint32_t serial) +{ + pong(serial); +} + +QWaylandShellSurface *QWaylandXdgShellIntegration::createShellSurface(QWaylandWindow *window) +{ + return new QWaylandXdgSurface(mXdgShell.get(), get_xdg_surface(window->wlSurface()), window); +} + +void *QWaylandXdgShellIntegration::nativeResourceForWindow(const QByteArray &resource, QWindow *window) +{ + if (auto waylandWindow = static_cast(window->handle())) { + if (auto xdgSurface = qobject_cast(waylandWindow->shellSurface())) { + return xdgSurface->nativeResource(resource); + } + } + return nullptr; +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h new file mode 100644 index 00000000000..b7627d80462 --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h @@ -0,0 +1,51 @@ +// Copyright (C) 2017 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 + +#ifndef QWAYLANDXDGSHELLINTEGRATION_P_H +#define QWAYLANDXDGSHELLINTEGRATION_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 "qwayland-xdg-shell.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandXdgShell; + +class Q_WAYLANDCLIENT_EXPORT QWaylandXdgShellIntegration + : public QWaylandShellIntegrationTemplate, + public QtWayland::xdg_wm_base +{ +public: + QWaylandXdgShellIntegration(); + ~QWaylandXdgShellIntegration() override; + QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override; + void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) override; + bool initialize(QWaylandDisplay *display) override; + +protected: + void xdg_wm_base_ping(uint32_t serial) override; + +private: + QWaylandDisplay *mDisplay; + QScopedPointer mXdgShell; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDXDGSHELLINTEGRATION_P_H diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgtopleveliconv1.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgtopleveliconv1.cpp new file mode 100644 index 00000000000..045f61d2ade --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgtopleveliconv1.cpp @@ -0,0 +1,102 @@ +// Copyright (C) 2024 David Reondo +// Copyright (C) 2024 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandshmbackingstore_p.h" +#include "qwaylandxdgtopleveliconv1_p.h" + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandXdgToplevelIconV1 : public QtWayland::xdg_toplevel_icon_v1 +{ +public: + QWaylandXdgToplevelIconV1(::xdg_toplevel_icon_v1 *object, QWaylandDisplay *display) + : QtWayland::xdg_toplevel_icon_v1(object), mDisplay(display) + { + } + + ~QWaylandXdgToplevelIconV1() override { + destroy(); + } + + void addPixmap(const QPixmap &pixmap) + { + const QSize squareSize = pixmap.size().expandedTo(pixmap.size().transposed()); + auto buffer = std::make_unique(mDisplay, squareSize, QImage::Format_ARGB32, pixmap.devicePixelRatio()); + QRect targetRect = pixmap.rect(); + targetRect.moveCenter(buffer->image()->rect().center()); + QPainter painter(buffer->image()); + painter.drawPixmap(targetRect, pixmap, pixmap.rect()); + add_buffer(buffer->buffer(), buffer->scale()); + mBuffers.push_back(std::move(buffer)); + } + +private: + QWaylandDisplay *mDisplay; + std::vector> mBuffers; +}; + +QWaylandXdgToplevelIconManagerV1::QWaylandXdgToplevelIconManagerV1(QWaylandDisplay *display, + wl_registry *registry, + uint32_t id, int version) + : QtWayland::xdg_toplevel_icon_manager_v1(registry, id, version), mDisplay(display) +{ +} + +QWaylandXdgToplevelIconManagerV1::~QWaylandXdgToplevelIconManagerV1() +{ + destroy(); +} + +void QWaylandXdgToplevelIconManagerV1::xdg_toplevel_icon_manager_v1_icon_size(int32_t size) +{ + mPreferredSizes.push_back(size); +} + +void QWaylandXdgToplevelIconManagerV1::xdg_toplevel_icon_manager_v1_done() { } + +void QWaylandXdgToplevelIconManagerV1::setIcon(const QIcon &icon, xdg_toplevel *window) +{ + if (icon.isNull()) { + set_icon(window, nullptr); + return; + } + + auto toplevelIcon = std::make_unique(create_icon(), mDisplay); + + if (const QString name = icon.name(); !name.isEmpty() && !QDir::isAbsolutePath(name)) { + toplevelIcon->set_name(name); + } + + QList iconSizes = icon.availableSizes(); + // if icon has no default size (an SVG) + if (iconSizes.isEmpty()) { + iconSizes.reserve(mPreferredSizes.size()); + for (int size : std::as_const(mPreferredSizes)) { + iconSizes.append(QSize(size, size)); + } + } + // if the compositor hasn't sent a preferred size + if (iconSizes.isEmpty()) { + iconSizes.append(QSize(64, 64)); + } + + for (const QSize &size : std::as_const(iconSizes)) { + const QPixmap pixmap = icon.pixmap(size, 1.0); + toplevelIcon->addPixmap(pixmap); + } + + set_icon(window, toplevelIcon->object()); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgtopleveliconv1_p.h b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgtopleveliconv1_p.h new file mode 100644 index 00000000000..63e379c4daf --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgtopleveliconv1_p.h @@ -0,0 +1,52 @@ +// Copyright (C) 2024 David Reondo +// Copyright (C) 2024 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDTOPLEVELICONV1_P_H +#define QWAYLANDTOPLEVELICONV1_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 "wayland-xdg-shell-client-protocol.h" +#include + +#include + +QT_BEGIN_NAMESPACE + +class QIcon; + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class QWaylandXdgToplevelIconManagerV1 : public QtWayland::xdg_toplevel_icon_manager_v1 +{ +public: + QWaylandXdgToplevelIconManagerV1(QWaylandDisplay *display, wl_registry *registry, uint32_t id, + int version); + ~QWaylandXdgToplevelIconManagerV1() override; + void setIcon(const QIcon &icon, ::xdg_toplevel *window); + +protected: + void xdg_toplevel_icon_manager_v1_icon_size(int32_t size) override; + void xdg_toplevel_icon_manager_v1_done() override; + +private: + QList mPreferredSizes; + QWaylandDisplay *mDisplay; +}; +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/xdg-shell.json b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/xdg-shell.json new file mode 100644 index 00000000000..2de86b8d0ac --- /dev/null +++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/xdg-shell.json @@ -0,0 +1,3 @@ +{ + "Keys":[ "xdg-shell" ] +} diff --git a/src/plugins/platforms/wayland/qt_cmdline.cmake b/src/plugins/platforms/wayland/qt_cmdline.cmake new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/plugins/platforms/wayland/qtwaylandclientglobal.h b/src/plugins/platforms/wayland/qtwaylandclientglobal.h new file mode 100644 index 00000000000..0e54752947e --- /dev/null +++ b/src/plugins/platforms/wayland/qtwaylandclientglobal.h @@ -0,0 +1,16 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDCLIENTGLOBAL_H +#define QWAYLANDCLIENTGLOBAL_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QT_END_NAMESPACE + +#endif //QWAYLANDCLIENTGLOBAL_H + diff --git a/src/plugins/platforms/wayland/qtwaylandclientglobal_p.h b/src/plugins/platforms/wayland/qtwaylandclientglobal_p.h new file mode 100644 index 00000000000..f98d143d2d0 --- /dev/null +++ b/src/plugins/platforms/wayland/qtwaylandclientglobal_p.h @@ -0,0 +1,23 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDCLIENTGLOBAL_P_H +#define QWAYLANDCLIENTGLOBAL_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 + +#endif //QWAYLANDCLIENTGLOBAL_P_H diff --git a/src/plugins/platforms/wayland/qwayland.json b/src/plugins/platforms/wayland/qwayland.json new file mode 100644 index 00000000000..d07e7e1aa89 --- /dev/null +++ b/src/plugins/platforms/wayland/qwayland.json @@ -0,0 +1,7 @@ +{ + "Keys": [ + "wayland", + "wayland-egl", + "wayland-brcm" + ] +} diff --git a/src/plugins/platforms/wayland/qwaylandabstractdecoration.cpp b/src/plugins/platforms/wayland/qwaylandabstractdecoration.cpp new file mode 100644 index 00000000000..699618f684b --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandabstractdecoration.cpp @@ -0,0 +1,184 @@ +// Copyright (C) 2016 Robin Burchell +// Copyright (C) 2016 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 + +#include "qwaylandabstractdecoration_p.h" + +#include +#include "qwaylandwindow_p.h" +#include "qwaylandshellsurface_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylandscreen_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandAbstractDecorationPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QWaylandAbstractDecoration) + +public: + QWaylandAbstractDecorationPrivate(); + ~QWaylandAbstractDecorationPrivate() override; + + QWindow *m_window = nullptr; + QWaylandWindow *m_wayland_window = nullptr; + + bool m_isDirty = true; + QImage m_decorationContentImage; + + Qt::MouseButtons m_mouseButtons = Qt::NoButton; +}; + +QWaylandAbstractDecorationPrivate::QWaylandAbstractDecorationPrivate() + : m_decorationContentImage(nullptr) +{ +} + +QWaylandAbstractDecorationPrivate::~QWaylandAbstractDecorationPrivate() +{ +} + +QWaylandAbstractDecoration::QWaylandAbstractDecoration() + : QObject(*new QWaylandAbstractDecorationPrivate) +{ +} + +QWaylandAbstractDecoration::~QWaylandAbstractDecoration() +{ +} + +// we do this as a setter to get around plugin factory creates not really +// being a great way to pass arguments +void QWaylandAbstractDecoration::setWaylandWindow(QWaylandWindow *window) +{ + Q_D(QWaylandAbstractDecoration); + + // double initialization is probably not great + Q_ASSERT(!d->m_window && !d->m_wayland_window); + + d->m_window = window->window(); + d->m_wayland_window = window; +} + +// Creates regions like this on the outside of a rectangle with inner size \a size +// ----- +// | | +// ----- +// I.e. the top and bottom extends into the corners +static QRegion marginsRegion(const QSize &size, const QMargins &margins) +{ + QRegion r; + + r += QRect(0, 0, size.width(), margins.top()); // top + r += QRect(0, size.height()-margins.bottom(), size.width(), margins.bottom()); //bottom + r += QRect(0, margins.top(), margins.left(), size.height()); //left + r += QRect(size.width()-margins.left(), margins.top(), margins.right(), size.height()-margins.top()); // right + return r; +} + +const QImage &QWaylandAbstractDecoration::contentImage() +{ + Q_D(QWaylandAbstractDecoration); + if (d->m_isDirty) { + // Update the decoration backingstore + + const qreal bufferScale = waylandWindow()->scale(); + const QSize imageSize = waylandWindow()->surfaceSize() * bufferScale; + d->m_decorationContentImage = QImage(imageSize, QImage::Format_ARGB32_Premultiplied); + // Only scale by buffer scale, not QT_SCALE_FACTOR etc. + d->m_decorationContentImage.setDevicePixelRatio(bufferScale); + d->m_decorationContentImage.fill(Qt::transparent); + this->paint(&d->m_decorationContentImage); + + QRegion damage = marginsRegion(waylandWindow()->surfaceSize(), waylandWindow()->frameMargins()); + for (QRect r : damage) + waylandWindow()->damage(r); + + d->m_isDirty = false; + } + + return d->m_decorationContentImage; +} + +void QWaylandAbstractDecoration::update() +{ + Q_D(QWaylandAbstractDecoration); + d->m_isDirty = true; +} + +void QWaylandAbstractDecoration::setMouseButtons(Qt::MouseButtons mb) +{ + Q_D(QWaylandAbstractDecoration); + d->m_mouseButtons = mb; +} + +void QWaylandAbstractDecoration::startResize(QWaylandInputDevice *inputDevice, Qt::Edges edges, Qt::MouseButtons buttons) +{ + Q_D(QWaylandAbstractDecoration); + if (isLeftClicked(buttons) && d->m_wayland_window->shellSurface()) { + d->m_wayland_window->shellSurface()->resize(inputDevice, edges); + inputDevice->removeMouseButtonFromState(Qt::LeftButton); + } +} + +void QWaylandAbstractDecoration::startMove(QWaylandInputDevice *inputDevice, Qt::MouseButtons buttons) +{ + Q_D(QWaylandAbstractDecoration); + if (isLeftClicked(buttons) && d->m_wayland_window->shellSurface()) { + d->m_wayland_window->shellSurface()->move(inputDevice); + inputDevice->removeMouseButtonFromState(Qt::LeftButton); + } +} + +void QWaylandAbstractDecoration::showWindowMenu(QWaylandInputDevice *inputDevice) +{ + Q_D(QWaylandAbstractDecoration); + if (auto *s = d->m_wayland_window->shellSurface()) + s->showWindowMenu(inputDevice); +} + +bool QWaylandAbstractDecoration::isLeftClicked(Qt::MouseButtons newMouseButtonState) +{ + Q_D(QWaylandAbstractDecoration); + return !(d->m_mouseButtons & Qt::LeftButton) && (newMouseButtonState & Qt::LeftButton); +} + +bool QWaylandAbstractDecoration::isRightClicked(Qt::MouseButtons newMouseButtonState) +{ + Q_D(QWaylandAbstractDecoration); + return !(d->m_mouseButtons & Qt::RightButton) && (newMouseButtonState & Qt::RightButton); +} + +bool QWaylandAbstractDecoration::isLeftReleased(Qt::MouseButtons newMouseButtonState) +{ + Q_D(QWaylandAbstractDecoration); + return (d->m_mouseButtons & Qt::LeftButton) && !(newMouseButtonState & Qt::LeftButton); +} + +bool QWaylandAbstractDecoration::isDirty() const +{ + Q_D(const QWaylandAbstractDecoration); + return d->m_isDirty; +} + +QWindow *QWaylandAbstractDecoration::window() const +{ + Q_D(const QWaylandAbstractDecoration); + return d->m_window; +} + +QWaylandWindow *QWaylandAbstractDecoration::waylandWindow() const +{ + Q_D(const QWaylandAbstractDecoration); + return d->m_wayland_window; +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylandabstractdecoration_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylandabstractdecoration_p.h b/src/plugins/platforms/wayland/qwaylandabstractdecoration_p.h new file mode 100644 index 00000000000..3334e00c10e --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandabstractdecoration_p.h @@ -0,0 +1,92 @@ +// Copyright (C) 2017 Robin Burchell +// 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 + +#ifndef QWAYLANDABSTRACTDECORATION_H +#define QWAYLANDABSTRACTDECORATION_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 + +class QWindow; +class QPaintDevice; +class QPainter; +class QEvent; + +namespace QtWaylandClient { + +class QWaylandScreen; +class QWaylandWindow; +class QWaylandInputDevice; +class QWaylandAbstractDecorationPrivate; + +class Q_WAYLANDCLIENT_EXPORT QWaylandAbstractDecoration : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QWaylandAbstractDecoration) +public: + enum MarginsType { + Full, + ShadowsExcluded, + ShadowsOnly + }; + + QWaylandAbstractDecoration(); + ~QWaylandAbstractDecoration() override; + + void setWaylandWindow(QWaylandWindow *window); + QWaylandWindow *waylandWindow() const; + + void update(); + bool isDirty() const; + + virtual QMargins margins(MarginsType marginsType = Full) const = 0; + + QWindow *window() const; + const QImage &contentImage(); + + virtual bool handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global,Qt::MouseButtons b,Qt::KeyboardModifiers mods) = 0; + virtual bool handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, QEventPoint::State state, Qt::KeyboardModifiers mods) = 0; + +protected: + virtual void paint(QPaintDevice *device) = 0; + + void setMouseButtons(Qt::MouseButtons mb); + + void startResize(QWaylandInputDevice *inputDevice, Qt::Edges edges, Qt::MouseButtons buttons); + void startMove(QWaylandInputDevice *inputDevice, Qt::MouseButtons buttons); + void showWindowMenu(QWaylandInputDevice *inputDevice); + + bool isLeftClicked(Qt::MouseButtons newMouseButtonState); + bool isRightClicked(Qt::MouseButtons newMouseButtonState); + bool isLeftReleased(Qt::MouseButtons newMouseButtonState); +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDABSTRACTDECORATION_H diff --git a/src/plugins/platforms/wayland/qwaylandappmenu.cpp b/src/plugins/platforms/wayland/qwaylandappmenu.cpp new file mode 100644 index 00000000000..3e584e25972 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandappmenu.cpp @@ -0,0 +1,32 @@ +// Copyright (C) 2024 David Redondo +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandappmenu_p.h" + +#include "qwaylandwindow_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandAppMenu::QWaylandAppMenu() : org_kde_kwin_appmenu() { } + +QWaylandAppMenu::~QWaylandAppMenu() +{ + if (object()) + release(); +} + +QWaylandAppMenuManager::QWaylandAppMenuManager(wl_registry *registry, quint32 id, int version) + : org_kde_kwin_appmenu_manager(registry, id, version) +{ +} + +QWaylandAppMenuManager::~QWaylandAppMenuManager() +{ + org_kde_kwin_appmenu_manager_destroy(object()); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandappmenu_p.h b/src/plugins/platforms/wayland/qwaylandappmenu_p.h new file mode 100644 index 00000000000..45d6f9d901d --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandappmenu_p.h @@ -0,0 +1,44 @@ +// Copyright (C) 2024 David Redondo +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDAPPMENU_H +#define QWAYLANDAPPMENU_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandAppMenu : public QObject, public QtWayland::org_kde_kwin_appmenu +{ +public: + QWaylandAppMenu(); + ~QWaylandAppMenu(); +}; + +class QWaylandAppMenuManager : public QtWayland::org_kde_kwin_appmenu_manager +{ +public: + QWaylandAppMenuManager(wl_registry *registry, quint32 id, int version); + ~QWaylandAppMenuManager(); +}; + +} // namespace QtWaylandClient +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/qwaylandbuffer.cpp b/src/plugins/platforms/wayland/qwaylandbuffer.cpp new file mode 100644 index 00000000000..dd99b702bcf --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandbuffer.cpp @@ -0,0 +1,49 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2017 Giulio Camuffo. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandbuffer_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandBuffer::QWaylandBuffer() +{ +} + +QWaylandBuffer::~QWaylandBuffer() +{ + if (mBuffer) + wl_buffer_destroy(mBuffer); +} + +void QWaylandBuffer::init(wl_buffer *buf) +{ + mBuffer = buf; + wl_buffer_add_listener(buf, &listener, this); +} + +void QWaylandBuffer::release(void *data, wl_buffer *) +{ + QWaylandBuffer *self = static_cast(data); + self->mBusy = false; + self->mCommitted = false; + if (self->mDeleteOnRelease) + delete self; +} + +void QWaylandBuffer::setDeleteOnRelease(bool deleteOnRelease) +{ + mDeleteOnRelease = deleteOnRelease; +} + +const wl_buffer_listener QWaylandBuffer::listener = { + QWaylandBuffer::release +}; + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandbuffer_p.h b/src/plugins/platforms/wayland/qwaylandbuffer_p.h new file mode 100644 index 00000000000..c96f213b9e8 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandbuffer_p.h @@ -0,0 +1,64 @@ +// Copyright (C) 2018 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 + +#ifndef QWAYLANDBUFFER_H +#define QWAYLANDBUFFER_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class Q_WAYLANDCLIENT_EXPORT QWaylandBuffer { +public: + QWaylandBuffer(); + virtual ~QWaylandBuffer(); + void init(wl_buffer *buf); + + wl_buffer *buffer() {return mBuffer;} + virtual QSize size() const = 0; + virtual int scale() const { return 1; } + + void setBusy(bool busy) { mBusy = busy; } + bool busy() const { return mBusy; } + + void setCommitted() { mCommitted = true; } + bool committed() const { return mCommitted; } + + void setDeleteOnRelease(bool deleteOnRelease); + +protected: + struct wl_buffer *mBuffer = nullptr; + +private: + bool mBusy = false; + bool mCommitted = false; + bool mDeleteOnRelease = false; + + static void release(void *data, wl_buffer *); + static const wl_buffer_listener listener; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDBUFFER_H diff --git a/src/plugins/platforms/wayland/qwaylandcallback_p.h b/src/plugins/platforms/wayland/qwaylandcallback_p.h new file mode 100644 index 00000000000..b9afa18c6a0 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandcallback_p.h @@ -0,0 +1,31 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDCALLBACK_H +#define QWAYLANDCALLBACK_H + +#include "qwayland-wayland.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class WlCallback : public QtWayland::wl_callback +{ +public: + explicit WlCallback(::wl_callback *callback, std::function fn) + : QtWayland::wl_callback(callback), m_fn(fn) + { + } + ~WlCallback() override { wl_callback_destroy(object()); } + void callback_done(uint32_t callback_data) override { m_fn(callback_data); } + +private: + std::function m_fn; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDCALLBACK_H diff --git a/src/plugins/platforms/wayland/qwaylandclipboard.cpp b/src/plugins/platforms/wayland/qwaylandclipboard.cpp new file mode 100644 index 00000000000..a1737ef69eb --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandclipboard.cpp @@ -0,0 +1,168 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2024 Jie Liu +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandclipboard_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylanddatacontrolv1_p.h" +#include "qwaylanddataoffer_p.h" +#include "qwaylanddatasource_p.h" +#include "qwaylanddatadevice_p.h" +#if QT_CONFIG(wayland_client_primary_selection) +#include "qwaylandprimaryselectionv1_p.h" +#endif + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandClipboard::QWaylandClipboard(QWaylandDisplay *display) + : mDisplay(display) +{ + m_clientClipboard[QClipboard::Clipboard] = nullptr; + m_clientClipboard[QClipboard::Selection] = nullptr; +} + +QWaylandClipboard::~QWaylandClipboard() +{ + if (m_clientClipboard[QClipboard::Clipboard] != m_clientClipboard[QClipboard::Selection]) + delete m_clientClipboard[QClipboard::Clipboard]; + delete m_clientClipboard[QClipboard::Selection]; +} + +QMimeData *QWaylandClipboard::mimeData(QClipboard::Mode mode) +{ + auto *seat = mDisplay->currentInputDevice(); + if (!seat) + return &m_emptyData; + + switch (mode) { + case QClipboard::Clipboard: + if (auto *dataControlDevice = seat->dataControlDevice()) { + if (dataControlDevice->selectionSource()) + return m_clientClipboard[QClipboard::Clipboard]; + if (auto *offer = dataControlDevice->selectionOffer()) + return offer->mimeData(); + } + if (auto *dataDevice = seat->dataDevice()) { + if (dataDevice->selectionSource()) + return m_clientClipboard[QClipboard::Clipboard]; + if (auto *offer = dataDevice->selectionOffer()) + return offer->mimeData(); + } + return &m_emptyData; + case QClipboard::Selection: + if (auto *dataControlDevice = seat->dataControlDevice()) { + if (dataControlDevice->primarySelectionSource()) + return m_clientClipboard[QClipboard::Selection]; + if (auto *offer = dataControlDevice->primarySelectionOffer()) + return offer->mimeData(); + } +#if QT_CONFIG(wayland_client_primary_selection) + if (auto *selectionDevice = seat->primarySelectionDevice()) { + if (selectionDevice->selectionSource()) + return m_clientClipboard[QClipboard::Selection]; + if (auto *offer = selectionDevice->selectionOffer()) + return offer->mimeData(); + } +#endif + return &m_emptyData; + default: + return &m_emptyData; + } +} + +void QWaylandClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) +{ + auto *seat = mDisplay->currentInputDevice(); + if (!seat) { + qCWarning(lcQpaWayland) << "Can't set clipboard contents with no wl_seats available"; + return; + } + + if (data && m_clientClipboard[mode] == data) // Already set before? + return; + + static const QString plain = QStringLiteral("text/plain"); + static const QString utf8 = QStringLiteral("text/plain;charset=utf-8"); + + if (data && data->hasFormat(plain) && !data->hasFormat(utf8)) + data->setData(utf8, data->data(plain)); + + auto oldMimeData = std::exchange(m_clientClipboard[mode], data); + const auto otherMode = mode == QClipboard::Clipboard ? QClipboard::Selection + : QClipboard::Clipboard; + if (oldMimeData != m_clientClipboard[otherMode]) + delete oldMimeData; + + switch (mode) { + case QClipboard::Clipboard: + if (auto *dataControlDevice = seat->dataControlDevice()) { + dataControlDevice->setSelectionSource(data ? new QWaylandDataControlSourceV1(mDisplay->dataControlManager(), + m_clientClipboard[QClipboard::Clipboard]) : nullptr); + emitChanged(mode); + } else if (auto *dataDevice = seat->dataDevice()) { + dataDevice->setSelectionSource(data ? new QWaylandDataSource(mDisplay->dndSelectionHandler(), + m_clientClipboard[QClipboard::Clipboard]) : nullptr); + emitChanged(mode); + } + break; + case QClipboard::Selection: + if (auto *dataControlDevice = seat->dataControlDevice()) { + dataControlDevice->setPrimarySelectionSource(data ? new QWaylandDataControlSourceV1(mDisplay->dataControlManager(), + m_clientClipboard[QClipboard::Selection]) : nullptr); + emitChanged(mode); +#if QT_CONFIG(wayland_client_primary_selection) + } else if (auto *selectionDevice = seat->primarySelectionDevice()) { + selectionDevice->setSelectionSource(data ? new QWaylandPrimarySelectionSourceV1(mDisplay->primarySelectionManager(), + m_clientClipboard[QClipboard::Selection]) : nullptr); + emitChanged(mode); +#endif + } + break; + default: + break; + } +} + +bool QWaylandClipboard::supportsMode(QClipboard::Mode mode) const +{ + if (mode == QClipboard::Selection) { + auto *seat = mDisplay->currentInputDevice(); + if (!seat) + return false; + if (seat->dataControlDevice()) + return true; +#if QT_CONFIG(wayland_client_primary_selection) + if (seat->primarySelectionDevice()) + return true; +#endif + return false; + } + return mode == QClipboard::Clipboard; +} + +bool QWaylandClipboard::ownsMode(QClipboard::Mode mode) const +{ + QWaylandInputDevice *seat = mDisplay->currentInputDevice(); + if (!seat) + return false; + + switch (mode) { + case QClipboard::Clipboard: + return seat->dataDevice() && seat->dataDevice()->selectionSource() != nullptr; + case QClipboard::Selection: + if (seat->dataControlDevice() && seat->dataControlDevice()->primarySelectionSource() != nullptr) + return true; +#if QT_CONFIG(wayland_client_primary_selection) + return seat->primarySelectionDevice() && seat->primarySelectionDevice()->selectionSource() != nullptr; +#endif + default: + return false; + } +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandclipboard_p.h b/src/plugins/platforms/wayland/qwaylandclipboard_p.h new file mode 100644 index 00000000000..414e3dc716b --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandclipboard_p.h @@ -0,0 +1,55 @@ +// Copyright (C) 2018 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 + +#ifndef QWAYLANDCLIPBOARD_H +#define QWAYLANDCLIPBOARD_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 + +QT_REQUIRE_CONFIG(clipboard); + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class Q_WAYLANDCLIENT_EXPORT QWaylandClipboard : public QPlatformClipboard +{ +public: + QWaylandClipboard(QWaylandDisplay *display); + + ~QWaylandClipboard() override; + + QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override; + void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) override; + bool supportsMode(QClipboard::Mode mode) const override; + bool ownsMode(QClipboard::Mode mode) const override; + +private: + QWaylandDisplay *mDisplay = nullptr; + QMimeData m_emptyData; + QMimeData *m_clientClipboard[2]; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDCLIPBOARD_H diff --git a/src/plugins/platforms/wayland/qwaylandcolormanagement.cpp b/src/plugins/platforms/wayland/qwaylandcolormanagement.cpp new file mode 100644 index 00000000000..2114e59328b --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandcolormanagement.cpp @@ -0,0 +1,260 @@ +// Copyright (C) 2024 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 + +#include "qwaylandcolormanagement_p.h" +#include "qwaylanddisplay_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +ColorManager::ColorManager(struct ::wl_registry *registry, uint32_t id, int version) + : QtWayland::xx_color_manager_v4(registry, id, version) +{ +} + +ColorManager::~ColorManager() +{ + destroy(); +} + +void ColorManager::xx_color_manager_v4_supported_feature(uint32_t feature) +{ + switch (feature) { + case feature_icc_v2_v4: + mFeatures |= Feature::ICC; + break; + case feature_parametric: + mFeatures |= Feature::Parametric; + break; + case feature_set_primaries: + mFeatures |= Feature::SetPrimaries; + break; + case feature_set_tf_power: + mFeatures |= Feature::PowerTransferFunction; + break; + case feature_set_luminances: + mFeatures |= Feature::SetLuminances; + break; + case feature_set_mastering_display_primaries: + mFeatures |= Feature::SetMasteringDisplayPrimaries; + break; + case feature_extended_target_volume: + mFeatures |= Feature::ExtendedTargetVolume; + break; + } +} + +void ColorManager::xx_color_manager_v4_supported_primaries_named(uint32_t primaries) +{ + mPrimaries.push_back(QtWayland::xx_color_manager_v4::primaries(primaries)); +} + +void ColorManager::xx_color_manager_v4_supported_tf_named(uint32_t transferFunction) +{ + mTransferFunctions.push_back(QtWayland::xx_color_manager_v4::transfer_function(transferFunction)); +} + +ColorManager::Features ColorManager::supportedFeatures() const +{ + return mFeatures; +} + +bool ColorManager::supportsNamedPrimary(QtWayland::xx_color_manager_v4::primaries primaries) const +{ + return mPrimaries.contains(primaries); +} + +bool ColorManager::supportsTransferFunction(QtWayland::xx_color_manager_v4::transfer_function transferFunction) const +{ + return mTransferFunctions.contains(transferFunction); +} + +std::unique_ptr ColorManager::createImageDescription(const QColorSpace &colorspace) +{ + if (!(mFeatures & Feature::Parametric)) + return nullptr; + + constexpr std::array primaryMapping = { + std::make_pair(QColorSpace::Primaries::SRgb, primaries_srgb), + std::make_pair(QColorSpace::Primaries::AdobeRgb, primaries_adobe_rgb), + std::make_pair(QColorSpace::Primaries::DciP3D65, primaries_display_p3), + std::make_pair(QColorSpace::Primaries::Bt2020, primaries_bt2020), + }; + const auto primary = std::find_if(primaryMapping.begin(), primaryMapping.end(), [&colorspace](const auto &pair) { + return pair.first == colorspace.primaries(); + }); + if (!(supportedFeatures() & Feature::SetPrimaries) && (primary == primaryMapping.end() || !supportsNamedPrimary(primary->second))) + return nullptr; + + constexpr std::array tfMapping = { + std::make_pair(QColorSpace::TransferFunction::Linear, transfer_function_linear), + std::make_pair(QColorSpace::TransferFunction::SRgb, transfer_function_srgb), + std::make_pair(QColorSpace::TransferFunction::St2084, transfer_function_st2084_pq), + std::make_pair(QColorSpace::TransferFunction::Hlg, transfer_function_hlg), + }; + const auto tfIt = std::find_if(tfMapping.begin(), tfMapping.end(), [&colorspace](const auto &pair) { + return pair.first == colorspace.transferFunction(); + }); + auto transferFunction = tfIt == tfMapping.end() ? std::nullopt : std::make_optional(tfIt->second); + if (colorspace.transferFunction() == QColorSpace::TransferFunction::Gamma) { + if (qFuzzyCompare(colorspace.gamma(), 2.2f) && supportsTransferFunction(transfer_function_gamma22)) + transferFunction = transfer_function_gamma22; + else if (qFuzzyCompare(colorspace.gamma(), 2.8f) && supportsTransferFunction(transfer_function_gamma28)) + transferFunction = transfer_function_gamma28; + if (!transferFunction && !(mFeatures & Feature::PowerTransferFunction)) + return nullptr; + } else if (!transferFunction) { + return nullptr; + } + + auto creator = new_parametric_creator(); + if (primary != primaryMapping.end()) { + xx_image_description_creator_params_v4_set_primaries_named(creator, primary->second); + } else { + const auto primaries = colorspace.primaryPoints(); + xx_image_description_creator_params_v4_set_primaries(creator, + std::round(10'000 * primaries.redPoint.x()), std::round(10'000 * primaries.redPoint.y()), + std::round(10'000 * primaries.greenPoint.x()), std::round(10'000 * primaries.greenPoint.y()), + std::round(10'000 * primaries.bluePoint.x()), std::round(10'000 * primaries.bluePoint.y()), + std::round(10'000 * primaries.whitePoint.x()), std::round(10'000 * primaries.whitePoint.y()) + ); + } + if (transferFunction) { + xx_image_description_creator_params_v4_set_tf_named(creator, *transferFunction); + } else { + Q_ASSERT(colorspace.transferFunction() == QColorSpace::TransferFunction::Gamma); + xx_image_description_creator_params_v4_set_tf_power(creator, std::round(colorspace.gamma() * 10'000)); + } + return std::make_unique(xx_image_description_creator_params_v4_create(creator)); +} + +ImageDescriptionInfo::ImageDescriptionInfo(ImageDescription *descr) + : QtWayland::xx_image_description_info_v4(descr->get_information()) +{ +} + +ImageDescriptionInfo::~ImageDescriptionInfo() +{ + xx_image_description_info_v4_destroy(object()); +} + +void ImageDescriptionInfo::xx_image_description_info_v4_done() +{ + Q_EMIT done(); +} + +void ImageDescriptionInfo::xx_image_description_info_v4_icc_file(int32_t icc, uint32_t icc_size) +{ + Q_UNUSED(icc_size) + close(icc); +} + +void ImageDescriptionInfo::xx_image_description_info_v4_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) +{ + mContainerRed = QPointF(r_x, r_y) / 10'000.0; + mContainerGreen = QPointF(g_x, g_y) / 10'000.0; + mContainerBlue = QPointF(b_x, b_y) / 10'000.0; + mContainerWhite = QPointF(w_x, w_y) / 10'000.0; +} + +void ImageDescriptionInfo::xx_image_description_info_v4_tf_named(uint32_t transferFunction) +{ + mTransferFunction = transferFunction; +} + +void ImageDescriptionInfo::xx_image_description_info_v4_luminances(uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) +{ + mMinLuminance = min_lum / 10'000.0; + mMaxLuminance = max_lum; + mReferenceLuminance = reference_lum; +} + +void ImageDescriptionInfo::xx_image_description_info_v4_target_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) +{ + mTargetRed = QPointF(r_x, r_y) / 10'000.0; + mTargetGreen = QPointF(g_x, g_y) / 10'000.0; + mTargetBlue = QPointF(b_x, b_y) / 10'000.0; + mTargetWhite = QPointF(w_x, w_y) / 10'000.0; +} + +void ImageDescriptionInfo::xx_image_description_info_v4_target_luminance(uint32_t min_lum, uint32_t max_lum) +{ + mTargetMinLuminance = min_lum / 10'000.0; + mTargetMaxLuminance = max_lum; +} + +ImageDescription::ImageDescription(::xx_image_description_v4 *descr) + : QtWayland::xx_image_description_v4(descr) +{ +} + +ImageDescription::~ImageDescription() +{ + xx_image_description_v4_destroy(object()); +} + +void ImageDescription::xx_image_description_v4_failed(uint32_t cause, const QString &msg) +{ + Q_UNUSED(cause); + qCWarning(lcQpaWayland) << "image description failed!" << msg; + // TODO handle this, somehow + // maybe fall back to the previous or preferred image description +} + +void ImageDescription::xx_image_description_v4_ready(uint32_t identity) +{ + Q_UNUSED(identity); + Q_EMIT ready(); +} + +ColorManagementFeedback::ColorManagementFeedback(::xx_color_management_feedback_surface_v4 *obj) + : QtWayland::xx_color_management_feedback_surface_v4(obj) + , mPreferred(std::make_unique(get_preferred())) +{ +} + +ColorManagementFeedback::~ColorManagementFeedback() +{ + xx_color_management_feedback_surface_v4_destroy(object()); +} + +void ColorManagementFeedback::xx_color_management_feedback_surface_v4_preferred_changed() +{ + mPreferred = std::make_unique(get_preferred()); + mPendingPreferredInfo = std::make_unique(mPreferred.get()); + connect(mPendingPreferredInfo.get(), &ImageDescriptionInfo::done, this, &ColorManagementFeedback::preferredChanged); +} + +void ColorManagementFeedback::handlePreferredDone() +{ + mPreferredInfo = std::move(mPendingPreferredInfo); +} + +ColorManagementSurface::ColorManagementSurface(::xx_color_management_surface_v4 *obj) + : QtWayland::xx_color_management_surface_v4(obj) +{ +} + +ColorManagementSurface::~ColorManagementSurface() +{ + xx_color_management_surface_v4_destroy(object()); +} + +void ColorManagementSurface::setImageDescription(ImageDescription *descr) +{ + if (descr) + xx_color_management_surface_v4_set_image_description(object(), descr->object(), QtWayland::xx_color_manager_v4::render_intent::render_intent_perceptual); + else + xx_color_management_surface_v4_unset_image_description(object()); +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylandcolormanagement_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylandcolormanagement_p.h b/src/plugins/platforms/wayland/qwaylandcolormanagement_p.h new file mode 100644 index 00000000000..8e44bd66b7b --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandcolormanagement_p.h @@ -0,0 +1,148 @@ +// Copyright (C) 2024 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 + +#ifndef QWAYLANDCOLORMANAGEMENT_H +#define QWAYLANDCOLORMANAGEMENT_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 "qwayland-xx-color-management-v4.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class ImageDescription; + +class ColorManager : public QObject, public QtWayland::xx_color_manager_v4 +{ + Q_OBJECT +public: + enum class Feature { + ICC = 1 << 0, + Parametric = 1 << 1, + SetPrimaries = 1 << 2, + PowerTransferFunction = 1 << 3, + SetLuminances = 1 << 4, + SetMasteringDisplayPrimaries = 1 << 5, + ExtendedTargetVolume = 1 << 6, + }; + Q_ENUM(Feature); + Q_DECLARE_FLAGS(Features, Feature); + + explicit ColorManager(struct ::wl_registry *registry, uint32_t id, int version); + ~ColorManager() override; + + Features supportedFeatures() const; + bool supportsNamedPrimary(QtWayland::xx_color_manager_v4::primaries primaries) const; + bool supportsTransferFunction(QtWayland::xx_color_manager_v4::transfer_function transferFunction) const; + + std::unique_ptr createImageDescription(const QColorSpace &colorspace); + +private: + void xx_color_manager_v4_supported_feature(uint32_t feature) override; + void xx_color_manager_v4_supported_primaries_named(uint32_t primaries) override; + void xx_color_manager_v4_supported_tf_named(uint32_t transferFunction) override; + + Features mFeatures; + QList mPrimaries; + QList mTransferFunctions; +}; + +class ImageDescriptionInfo : public QObject, public QtWayland::xx_image_description_info_v4 +{ + Q_OBJECT +public: + explicit ImageDescriptionInfo(ImageDescription *descr); + ~ImageDescriptionInfo(); + + Q_SIGNAL void done(); + + uint32_t mTransferFunction = 0; + QPointF mContainerRed; + QPointF mContainerGreen; + QPointF mContainerBlue; + QPointF mContainerWhite; + QPointF mTargetRed; + QPointF mTargetGreen; + QPointF mTargetBlue; + QPointF mTargetWhite; + double mMinLuminance; + double mMaxLuminance; + double mReferenceLuminance; + double mTargetMinLuminance; + double mTargetMaxLuminance; + +private: + void xx_image_description_info_v4_done() override; + void xx_image_description_info_v4_icc_file(int32_t icc, uint32_t icc_size) override; + void xx_image_description_info_v4_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) override; + void xx_image_description_info_v4_tf_named(uint32_t transferFunction) override; + void xx_image_description_info_v4_luminances(uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) override; + void xx_image_description_info_v4_target_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) override; + void xx_image_description_info_v4_target_luminance(uint32_t min_lum, uint32_t max_lum) override; +}; + +class ImageDescription : public QObject, public QtWayland::xx_image_description_v4 +{ + Q_OBJECT +public: + explicit ImageDescription(::xx_image_description_v4 *descr); + ~ImageDescription(); + + Q_SIGNAL void ready(); + +private: + void xx_image_description_v4_failed(uint32_t cause, const QString &msg) override; + void xx_image_description_v4_ready(uint32_t identity) override; +}; + +class ColorManagementFeedback : public QObject, public QtWayland::xx_color_management_feedback_surface_v4 +{ + Q_OBJECT +public: + explicit ColorManagementFeedback(::xx_color_management_feedback_surface_v4 *obj); + ~ColorManagementFeedback(); + + Q_SIGNAL void preferredChanged(); + + std::unique_ptr mPreferredInfo; + +private: + void xx_color_management_feedback_surface_v4_preferred_changed() override; + void handlePreferredDone(); + + std::unique_ptr mPreferred; + std::unique_ptr mPendingPreferredInfo; + +}; + +class ColorManagementSurface : public QObject, public QtWayland::xx_color_management_surface_v4 +{ + Q_OBJECT +public: + explicit ColorManagementSurface(::xx_color_management_surface_v4 *obj); + ~ColorManagementSurface(); + + void setImageDescription(ImageDescription *descr); +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/qwaylandcursor.cpp b/src/plugins/platforms/wayland/qwaylandcursor.cpp new file mode 100644 index 00000000000..00b1d7df2a3 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandcursor.cpp @@ -0,0 +1,356 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2023 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandcursor_p.h" + +#include "qwaylanddisplay_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylandshmbackingstore_p.h" + +#include +#include + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +std::unique_ptr QWaylandCursorTheme::create(QWaylandShm *shm, int size, const QString &themeName) +{ + QByteArray nameBytes = themeName.toLocal8Bit(); + struct ::wl_cursor_theme *theme = wl_cursor_theme_load(nameBytes.constData(), size, shm->object()); + + if (!theme) { + qCWarning(lcQpaWayland) << "Could not load cursor theme" << themeName << "size" << size; + return nullptr; + } + + return std::unique_ptr{new QWaylandCursorTheme(theme)}; +} + +QWaylandCursorTheme::~QWaylandCursorTheme() +{ + wl_cursor_theme_destroy(m_theme); +} + +wl_cursor *QWaylandCursorTheme::requestCursor(WaylandCursor shape) +{ + if (struct wl_cursor *cursor = m_cursors[shape]) + return cursor; + + static Q_CONSTEXPR struct ShapeAndName { + WaylandCursor shape; + const char name[33]; + } cursorNamesMap[] = { + {ArrowCursor, "left_ptr"}, + {ArrowCursor, "default"}, + {ArrowCursor, "top_left_arrow"}, + {ArrowCursor, "left_arrow"}, + + {UpArrowCursor, "up_arrow"}, + + {CrossCursor, "cross"}, + + {WaitCursor, "wait"}, + {WaitCursor, "watch"}, + {WaitCursor, "0426c94ea35c87780ff01dc239897213"}, + + {IBeamCursor, "ibeam"}, + {IBeamCursor, "text"}, + {IBeamCursor, "xterm"}, + + {SizeVerCursor, "size_ver"}, + {SizeVerCursor, "ns-resize"}, + {SizeVerCursor, "v_double_arrow"}, + {SizeVerCursor, "00008160000006810000408080010102"}, + + {SizeHorCursor, "size_hor"}, + {SizeHorCursor, "ew-resize"}, + {SizeHorCursor, "h_double_arrow"}, + {SizeHorCursor, "028006030e0e7ebffc7f7070c0600140"}, + + {SizeBDiagCursor, "size_bdiag"}, + {SizeBDiagCursor, "nesw-resize"}, + {SizeBDiagCursor, "50585d75b494802d0151028115016902"}, + {SizeBDiagCursor, "fcf1c3c7cd4491d801f1e1c78f100000"}, + + {SizeFDiagCursor, "size_fdiag"}, + {SizeFDiagCursor, "nwse-resize"}, + {SizeFDiagCursor, "38c5dff7c7b8962045400281044508d2"}, + {SizeFDiagCursor, "c7088f0f3e6c8088236ef8e1e3e70000"}, + + {SizeAllCursor, "size_all"}, + + {BlankCursor, "blank"}, + + {SplitVCursor, "split_v"}, + {SplitVCursor, "row-resize"}, + {SplitVCursor, "sb_v_double_arrow"}, + {SplitVCursor, "2870a09082c103050810ffdffffe0204"}, + {SplitVCursor, "c07385c7190e701020ff7ffffd08103c"}, + + {SplitHCursor, "split_h"}, + {SplitHCursor, "col-resize"}, + {SplitHCursor, "sb_h_double_arrow"}, + {SplitHCursor, "043a9f68147c53184671403ffa811cc5"}, + {SplitHCursor, "14fef782d02440884392942c11205230"}, + + {PointingHandCursor, "pointing_hand"}, + {PointingHandCursor, "pointer"}, + {PointingHandCursor, "hand1"}, + {PointingHandCursor, "e29285e634086352946a0e7090d73106"}, + + {ForbiddenCursor, "forbidden"}, + {ForbiddenCursor, "not-allowed"}, + {ForbiddenCursor, "crossed_circle"}, + {ForbiddenCursor, "circle"}, + {ForbiddenCursor, "03b6e0fcb3499374a867c041f52298f0"}, + + {WhatsThisCursor, "whats_this"}, + {WhatsThisCursor, "help"}, + {WhatsThisCursor, "question_arrow"}, + {WhatsThisCursor, "5c6cd98b3f3ebcb1f9c7f1c204630408"}, + {WhatsThisCursor, "d9ce0ab605698f320427677b458ad60b"}, + + {BusyCursor, "left_ptr_watch"}, + {BusyCursor, "half-busy"}, + {BusyCursor, "progress"}, + {BusyCursor, "00000000000000020006000e7e9ffc3f"}, + {BusyCursor, "08e8e1c95fe2fc01f976f1e063a24ccd"}, + + {OpenHandCursor, "openhand"}, + {OpenHandCursor, "fleur"}, + {OpenHandCursor, "5aca4d189052212118709018842178c0"}, + {OpenHandCursor, "9d800788f1b08800ae810202380a0822"}, + + {ClosedHandCursor, "closedhand"}, + {ClosedHandCursor, "grabbing"}, + {ClosedHandCursor, "208530c400c041818281048008011002"}, + + {DragCopyCursor, "dnd-copy"}, + {DragCopyCursor, "copy"}, + + {DragMoveCursor, "dnd-move"}, + {DragMoveCursor, "move"}, + + {DragLinkCursor, "dnd-link"}, + {DragLinkCursor, "link"}, + + {ResizeNorthCursor, "n-resize"}, + {ResizeNorthCursor, "top_side"}, + + {ResizeSouthCursor, "s-resize"}, + {ResizeSouthCursor, "bottom_side"}, + + {ResizeEastCursor, "e-resize"}, + {ResizeEastCursor, "right_side"}, + + {ResizeWestCursor, "w-resize"}, + {ResizeWestCursor, "left_side"}, + + {ResizeNorthWestCursor, "nw-resize"}, + {ResizeNorthWestCursor, "top_left_corner"}, + + {ResizeSouthEastCursor, "se-resize"}, + {ResizeSouthEastCursor, "bottom_right_corner"}, + + {ResizeNorthEastCursor, "ne-resize"}, + {ResizeNorthEastCursor, "top_right_corner"}, + + {ResizeSouthWestCursor, "sw-resize"}, + {ResizeSouthWestCursor, "bottom_left_corner"}, + }; + + const auto byShape = [](ShapeAndName lhs, ShapeAndName rhs) { + return lhs.shape < rhs.shape; + }; + Q_ASSERT(std::is_sorted(std::begin(cursorNamesMap), std::end(cursorNamesMap), byShape)); + const auto p = std::equal_range(std::begin(cursorNamesMap), std::end(cursorNamesMap), + ShapeAndName{shape, ""}, byShape); + for (auto it = p.first; it != p.second; ++it) { + if (wl_cursor *cursor = wl_cursor_theme_get_cursor(m_theme, it->name)) { + m_cursors[shape] = cursor; + return cursor; + } + } + + // Fallback to arrow cursor + if (shape != ArrowCursor) + return requestCursor(ArrowCursor); + + // Give up + return nullptr; +} + +::wl_cursor *QWaylandCursorTheme::cursor(Qt::CursorShape shape) +{ + struct wl_cursor *waylandCursor = nullptr; + + if (shape < Qt::BitmapCursor) { + waylandCursor = requestCursor(WaylandCursor(shape)); + } else if (shape == Qt::BitmapCursor) { + qCWarning(lcQpaWayland) << "cannot create a wl_cursor_image for a CursorShape"; + return nullptr; + } else { + //TODO: Custom cursor logic (for resize arrows) + } + + if (!waylandCursor) { + qCWarning(lcQpaWayland) << "Could not find cursor for shape" << shape; + return nullptr; + } + + return waylandCursor; +} + +QWaylandCursorShape::QWaylandCursorShape(::wp_cursor_shape_device_v1 *object) + : QtWayland::wp_cursor_shape_device_v1(object) +{} + +QWaylandCursorShape::~QWaylandCursorShape() +{ + destroy(); +} + +static QtWayland::wp_cursor_shape_device_v1::shape qtCursorShapeToWaylandShape(Qt::CursorShape cursorShape) +{ + using QtWayland::wp_cursor_shape_device_v1; + + switch (cursorShape) { + case Qt::BlankCursor: + case Qt::CustomCursor: + case Qt::BitmapCursor: + // these should have been handled separately before using the shape protocol + Q_ASSERT(false); + break; + case Qt::ArrowCursor: + return wp_cursor_shape_device_v1::shape_default; + case Qt::SizeVerCursor: + return wp_cursor_shape_device_v1::shape_ns_resize; + case Qt::UpArrowCursor: + return wp_cursor_shape_device_v1::shape_n_resize; + case Qt::SizeHorCursor: + return wp_cursor_shape_device_v1::shape_ew_resize; + case Qt::CrossCursor: + return wp_cursor_shape_device_v1::shape_crosshair; + case Qt::SizeBDiagCursor: + return wp_cursor_shape_device_v1::shape_nesw_resize; + case Qt::IBeamCursor: + return wp_cursor_shape_device_v1::shape_text; + case Qt::SizeFDiagCursor: + return wp_cursor_shape_device_v1::shape_nwse_resize; + case Qt::WaitCursor: + return wp_cursor_shape_device_v1::shape_wait; + case Qt::SizeAllCursor: + return wp_cursor_shape_device_v1::shape_all_scroll; + case Qt::BusyCursor: + return wp_cursor_shape_device_v1::shape_progress; + case Qt::SplitVCursor: + return wp_cursor_shape_device_v1::shape_row_resize; + case Qt::ForbiddenCursor: + return wp_cursor_shape_device_v1::shape_not_allowed; + case Qt::SplitHCursor: + return wp_cursor_shape_device_v1::shape_col_resize; + case Qt::PointingHandCursor: + return wp_cursor_shape_device_v1::shape_pointer; + case Qt::OpenHandCursor: + return wp_cursor_shape_device_v1::shape_grab; + case Qt::WhatsThisCursor: + return wp_cursor_shape_device_v1::shape_help; + case Qt::ClosedHandCursor: + return wp_cursor_shape_device_v1::shape_grabbing; + case Qt::DragMoveCursor: + return wp_cursor_shape_device_v1::shape_move; + case Qt::DragCopyCursor: + return wp_cursor_shape_device_v1::shape_copy; + case Qt::DragLinkCursor: + return wp_cursor_shape_device_v1::shape_alias; + } + return wp_cursor_shape_device_v1::shape_default; +} + +void QWaylandCursorShape::setShape(uint32_t serial, Qt::CursorShape shape) +{ + set_shape(serial, qtCursorShapeToWaylandShape(shape)); +} + +QWaylandCursor::QWaylandCursor(QWaylandDisplay *display) + : mDisplay(display) +{ +} + +QSharedPointer QWaylandCursor::cursorBitmapBuffer(QWaylandDisplay *display, const QCursor *cursor) +{ + Q_ASSERT(cursor->shape() == Qt::BitmapCursor); + QImage img = !cursor->pixmap().isNull() ? cursor->pixmap().toImage() : cursor->bitmap().toImage(); + + // convert to supported format if necessary + if (!display->shm()->formatSupported(img.format())) { + if (cursor->mask().isNull()) { + img.convertTo(QImage::Format_RGB32); + } else { + // preserve mask + img.convertTo(QImage::Format_ARGB32); + QPixmap pixmap = QPixmap::fromImage(img); + pixmap.setMask(cursor->mask()); + img = pixmap.toImage(); + } + } + + QSharedPointer buffer(new QWaylandShmBuffer(display, img.size(), img.format())); + memcpy(buffer->image()->bits(), img.bits(), size_t(img.sizeInBytes())); + return buffer; +} + +void QWaylandCursor::changeCursor(QCursor *cursor, QWindow *window) +{ + Q_UNUSED(window); + // Create the buffer here so we don't have to create one per input device + QSharedPointer bitmapBuffer; + if (cursor && cursor->shape() == Qt::BitmapCursor) + bitmapBuffer = cursorBitmapBuffer(mDisplay, cursor); + + int fallbackOutputScale = qCeil(window->handle()->devicePixelRatio()); + const auto seats = mDisplay->inputDevices(); + for (auto *seat : seats) + seat->setCursor(cursor, bitmapBuffer, fallbackOutputScale); +} + +void QWaylandCursor::pointerEvent(const QMouseEvent &event) +{ + mLastPos = event.globalPosition().toPoint(); +} + +QPoint QWaylandCursor::pos() const +{ + return mLastPos; +} + +void QWaylandCursor::setPos(const QPoint &pos) +{ + Q_UNUSED(pos); + qCWarning(lcQpaWayland) << "Setting cursor position is not possible on wayland"; +} + +void QWaylandCursor::setPosFromEnterEvent(const QPoint &pos) +{ + mLastPos = pos; +} + +QSize QWaylandCursor::size() const +{ + if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) + return theme->themeHint(QPlatformTheme::MouseCursorSize).toSize(); + return QSize(24, 24); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandcursor_p.h b/src/plugins/platforms/wayland/qwaylandcursor_p.h new file mode 100644 index 00000000000..e31019e7f25 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandcursor_p.h @@ -0,0 +1,124 @@ +// 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 + +#ifndef QWAYLANDCURSOR_H +#define QWAYLANDCURSOR_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 + +#if QT_CONFIG(cursor) + +#include + +struct wl_cursor; +struct wl_cursor_image; +struct wl_cursor_theme; + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandBuffer; +class QWaylandDisplay; +class QWaylandScreen; +class QWaylandShm; + +class Q_WAYLANDCLIENT_EXPORT QWaylandCursorTheme +{ +public: + static std::unique_ptr create(QWaylandShm *shm, int size, const QString &themeName); + ~QWaylandCursorTheme(); + ::wl_cursor *cursor(Qt::CursorShape shape); + +protected: + enum WaylandCursor { + ArrowCursor = Qt::ArrowCursor, + UpArrowCursor, + CrossCursor, + WaitCursor, + IBeamCursor, + SizeVerCursor, + SizeHorCursor, + SizeBDiagCursor, + SizeFDiagCursor, + SizeAllCursor, + BlankCursor, + SplitVCursor, + SplitHCursor, + PointingHandCursor, + ForbiddenCursor, + WhatsThisCursor, + BusyCursor, + OpenHandCursor, + ClosedHandCursor, + DragCopyCursor, + DragMoveCursor, + DragLinkCursor, + // The following are used for cursors that don't have equivalents in Qt + ResizeNorthCursor = Qt::CustomCursor + 1, + ResizeSouthCursor, + ResizeEastCursor, + ResizeWestCursor, + ResizeNorthWestCursor, + ResizeSouthEastCursor, + ResizeNorthEastCursor, + ResizeSouthWestCursor, + + NumWaylandCursors + }; + + explicit QWaylandCursorTheme(struct ::wl_cursor_theme *theme) : m_theme(theme) {} + struct ::wl_cursor *requestCursor(WaylandCursor shape); + struct ::wl_cursor_theme *m_theme = nullptr; + wl_cursor *m_cursors[NumWaylandCursors] = {}; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandCursorShape : public QtWayland::wp_cursor_shape_device_v1 +{ +public: + QWaylandCursorShape(struct ::wp_cursor_shape_device_v1 *object); + ~QWaylandCursorShape(); + void setShape(uint32_t serial, Qt::CursorShape shape); +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandCursor : public QPlatformCursor +{ +public: + explicit QWaylandCursor(QWaylandDisplay *display); + + void changeCursor(QCursor *cursor, QWindow *window) override; + void pointerEvent(const QMouseEvent &event) override; + QPoint pos() const override; + void setPos(const QPoint &pos) override; + void setPosFromEnterEvent(const QPoint &pos); + + QSize size() const override; + + static QSharedPointer cursorBitmapBuffer(QWaylandDisplay *display, const QCursor *cursor); + +protected: + QWaylandDisplay *mDisplay = nullptr; + QPoint mLastPos; +}; + +} + +QT_END_NAMESPACE + +#endif // cursor +#endif // QWAYLANDCURSOR_H diff --git a/src/plugins/platforms/wayland/qwaylandcursorsurface_p.h b/src/plugins/platforms/wayland/qwaylandcursorsurface_p.h new file mode 100644 index 00000000000..3e011ead28f --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandcursorsurface_p.h @@ -0,0 +1,81 @@ +// 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 + +#ifndef QWAYLANDCURSORSURFACE_H +#define QWAYLANDCURSORSURFACE_H + +#include "qwaylandsurface_p.h" +#include "qwaylandcallback_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +#if QT_CONFIG(cursor) +template +class CursorSurface : public QWaylandSurface +{ +public: + explicit CursorSurface(InputDevice *pointer, QWaylandDisplay *display) + : QWaylandSurface(display), m_pointer(pointer) + { + connect(this, &QWaylandSurface::screensChanged, m_pointer, &InputDevice::updateCursor); + } + + void reset() + { + m_setSerial = 0; + m_hotspot = QPoint(); + } + + // Size and hotspot are in surface coordinates + void update(wl_buffer *buffer, const QPoint &hotspot, const QSize &size, int bufferScale, + bool animated = false) + { + // Calling code needs to ensure buffer scale is supported if != 1 + Q_ASSERT(bufferScale == 1 || version() >= 3); + + auto enterSerial = m_pointer->mEnterSerial; + if (m_setSerial < enterSerial || m_hotspot != hotspot) { + m_pointer->set_cursor(m_pointer->mEnterSerial, object(), hotspot.x(), hotspot.y()); + m_setSerial = enterSerial; + m_hotspot = hotspot; + } + + if (version() >= 3) + set_buffer_scale(bufferScale); + + attach(buffer, 0, 0); + damage(0, 0, size.width(), size.height()); + m_frameCallback.reset(); + if (animated) { + m_frameCallback.reset(new WlCallback(frame(), [this](uint32_t time) { + Q_UNUSED(time); + m_pointer->cursorFrameCallback(); + })); + } + commit(); + } + + int outputScale() const + { + int scale = 0; + for (auto *screen : m_screens) + scale = qMax(scale, screen->scale()); + return scale; + } + +private: + QScopedPointer m_frameCallback; + InputDevice *m_pointer = nullptr; + uint m_setSerial = 0; + QPoint m_hotspot; +}; + +#endif // QT_CONFIG(cursor) + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDCURSORSURFACE_H diff --git a/src/plugins/platforms/wayland/qwaylanddatacontrolv1.cpp b/src/plugins/platforms/wayland/qwaylanddatacontrolv1.cpp new file mode 100644 index 00000000000..294c74c989b --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddatacontrolv1.cpp @@ -0,0 +1,166 @@ +// Copyright (C) 2024 Jie Liu +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylanddatacontrolv1_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandmimehelper_p.h" + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandDataControlManagerV1::QWaylandDataControlManagerV1(QWaylandDisplay *display, uint id, uint version) + : zwlr_data_control_manager_v1(display->wl_registry(), id, qMin(version, uint(2))) + , m_display(display) +{ +} + +QWaylandDataControlDeviceV1 *QWaylandDataControlManagerV1::createDevice(QWaylandInputDevice *seat) +{ + return new QWaylandDataControlDeviceV1(this, seat); +} + +QWaylandDataControlOfferV1::QWaylandDataControlOfferV1(QWaylandDisplay *display, ::zwlr_data_control_offer_v1 *offer) + : zwlr_data_control_offer_v1(offer) + , m_display(display) + , m_mimeData(new QWaylandMimeData(this)) +{} + +void QWaylandDataControlOfferV1::startReceiving(const QString &mimeType, int fd) +{ + receive(mimeType, fd); + wl_display_flush(m_display->wl_display()); +} + +void QWaylandDataControlOfferV1::zwlr_data_control_offer_v1_offer(const QString &mime_type) +{ + m_mimeData->appendFormat(mime_type); +} + +QWaylandDataControlDeviceV1::QWaylandDataControlDeviceV1( + QWaylandDataControlManagerV1 *manager, QWaylandInputDevice *seat) + : QtWayland::zwlr_data_control_device_v1(manager->get_data_device(seat->wl_seat())) + , m_display(manager->display()) + , m_seat(seat) +{ +} + +QWaylandDataControlDeviceV1::~QWaylandDataControlDeviceV1() +{ + destroy(); +} + +void QWaylandDataControlDeviceV1::invalidateSelectionOffer() +{ + if (!m_selectionOffer) + return; + + m_selectionOffer.reset(); + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Clipboard); +} + +void QWaylandDataControlDeviceV1::setSelectionSource(QWaylandDataControlSourceV1 *source) +{ + if (source) { + connect(source, &QWaylandDataControlSourceV1::cancelled, this, [this]() { + m_selectionSource.reset(); + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Clipboard); + }); + } + set_selection(source ? source->object() : nullptr); + m_selectionSource.reset(source); +} + +void QWaylandDataControlDeviceV1::setPrimarySelectionSource(QWaylandDataControlSourceV1 *source) +{ + if (source) { + connect(source, &QWaylandDataControlSourceV1::cancelled, this, [this]() { + m_primarySelectionSource.reset(); + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Selection); + }); + } + set_primary_selection(source ? source->object() : nullptr); + m_primarySelectionSource.reset(source); +} + +void QWaylandDataControlDeviceV1::zwlr_data_control_device_v1_data_offer(zwlr_data_control_offer_v1 *offer) +{ + new QWaylandDataControlOfferV1(m_display, offer); +} + +void QWaylandDataControlDeviceV1::zwlr_data_control_device_v1_selection(zwlr_data_control_offer_v1 *id) +{ + if (!id) + m_selectionOffer.reset(); + else + m_selectionOffer.reset(static_cast(zwlr_data_control_offer_v1_get_user_data(id))); + + // The selection event may be sent before platfrmIntegration is set. + if (auto* integration = QGuiApplicationPrivate::platformIntegration()) + integration->clipboard()->emitChanged(QClipboard::Clipboard); +} + +void QWaylandDataControlDeviceV1::zwlr_data_control_device_v1_finished() +{ + m_selectionOffer.reset(); + m_primarySelectionOffer.reset(); + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Clipboard); +} + +void QWaylandDataControlDeviceV1::zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *id) +{ + if (!id) + m_primarySelectionOffer.reset(); + else + m_primarySelectionOffer.reset(static_cast(zwlr_data_control_offer_v1_get_user_data(id))); + + // The selection event may be sent before platfrmIntegration is set. + if (auto* integration = QGuiApplicationPrivate::platformIntegration()) + integration->clipboard()->emitChanged(QClipboard::Selection); +} + +QWaylandDataControlSourceV1::QWaylandDataControlSourceV1(QWaylandDataControlManagerV1 *manager, QMimeData *mimeData) + : QtWayland::zwlr_data_control_source_v1(manager->create_data_source()) + , m_mimeData(mimeData) +{ + if (!mimeData) + return; + for (auto &format : mimeData->formats()) + offer(format); +} + +QWaylandDataControlSourceV1::~QWaylandDataControlSourceV1() +{ + destroy(); +} + +void QWaylandDataControlSourceV1::zwlr_data_control_source_v1_send(const QString &mime_type, int32_t fd) +{ + QByteArray content = QWaylandMimeHelper::getByteArray(m_mimeData, mime_type); + if (!content.isEmpty()) { + // Create a sigpipe handler that does nothing, or clients may be forced to terminate + // if the pipe is closed in the other end. + struct sigaction action, oldAction; + action.sa_handler = SIG_IGN; + sigemptyset (&action.sa_mask); + action.sa_flags = 0; + + sigaction(SIGPIPE, &action, &oldAction); + ssize_t unused = write(fd, content.constData(), size_t(content.size())); + Q_UNUSED(unused); + sigaction(SIGPIPE, &oldAction, nullptr); + } + close(fd); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylanddatacontrolv1_p.h b/src/plugins/platforms/wayland/qwaylanddatacontrolv1_p.h new file mode 100644 index 00000000000..79f6378b3f9 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddatacontrolv1_p.h @@ -0,0 +1,119 @@ +// Copyright (C) 2024 Jie Liu +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDDATACONTROLV1_H +#define QWAYLANDDATACONTROLV1_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 + +QT_REQUIRE_CONFIG(clipboard); + +QT_BEGIN_NAMESPACE + +class QMimeData; + +namespace QtWaylandClient { + +class QWaylandInputDevice; +class QWaylandDataControlDeviceV1; + +class QWaylandDataControlManagerV1 : public QtWayland::zwlr_data_control_manager_v1 +{ +public: + explicit QWaylandDataControlManagerV1(QWaylandDisplay *display, uint id, uint version); + QWaylandDataControlDeviceV1 *createDevice(QWaylandInputDevice *seat); + QWaylandDisplay *display() const { return m_display; } + +private: + QWaylandDisplay *m_display = nullptr; +}; + +class QWaylandDataControlOfferV1 : public QtWayland::zwlr_data_control_offer_v1, public QWaylandAbstractDataOffer +{ +public: + explicit QWaylandDataControlOfferV1(QWaylandDisplay *display, ::zwlr_data_control_offer_v1 *offer); + ~QWaylandDataControlOfferV1() override { destroy(); } + void startReceiving(const QString &mimeType, int fd) override; + QMimeData *mimeData() override { return m_mimeData.data(); } + +protected: + void zwlr_data_control_offer_v1_offer(const QString &mime_type) override; + +private: + QWaylandDisplay *m_display = nullptr; + QScopedPointer m_mimeData; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandDataControlSourceV1 : public QObject, public QtWayland::zwlr_data_control_source_v1 +{ + Q_OBJECT +public: + explicit QWaylandDataControlSourceV1(QWaylandDataControlManagerV1 *manager, QMimeData *mimeData); + ~QWaylandDataControlSourceV1() override; + + QMimeData *mimeData() const { return m_mimeData; } + +Q_SIGNALS: + void cancelled(); + +protected: + void zwlr_data_control_source_v1_send(const QString &mime_type, int32_t fd) override; + void zwlr_data_control_source_v1_cancelled() override { Q_EMIT cancelled(); } + +private: + QWaylandDisplay *m_display = nullptr; + QMimeData *m_mimeData = nullptr; +}; + +class QWaylandDataControlDeviceV1 : public QObject, public QtWayland::zwlr_data_control_device_v1 +{ + Q_OBJECT + QWaylandDataControlDeviceV1(QWaylandDataControlManagerV1 *manager, QWaylandInputDevice *seat); + +public: + ~QWaylandDataControlDeviceV1() override; + QWaylandDataControlOfferV1 *primarySelectionOffer() const { return m_primarySelectionOffer.data(); } + QWaylandDataControlOfferV1 *selectionOffer() const { return m_selectionOffer.data(); } + void invalidateSelectionOffer(); + QWaylandDataControlSourceV1 *selectionSource() const { return m_selectionSource.data(); } + QWaylandDataControlSourceV1 *primarySelectionSource() const { return m_primarySelectionSource.data(); } + void setSelectionSource(QWaylandDataControlSourceV1 *source); + void setPrimarySelectionSource(QWaylandDataControlSourceV1 *source); + +protected: + void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *id) override; + void zwlr_data_control_device_v1_selection(struct ::zwlr_data_control_offer_v1 *id) override; + void zwlr_data_control_device_v1_finished() override; + void zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *id) override; + +private: + QWaylandDisplay *m_display = nullptr; + QWaylandInputDevice *m_seat = nullptr; + QScopedPointer m_selectionOffer; + QScopedPointer m_primarySelectionOffer; + QScopedPointer m_selectionSource; + QScopedPointer m_primarySelectionSource; + friend class QWaylandDataControlManagerV1; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDDATACONTROLV1_H diff --git a/src/plugins/platforms/wayland/qwaylanddatadevice.cpp b/src/plugins/platforms/wayland/qwaylanddatadevice.cpp new file mode 100644 index 00000000000..4505a487bc4 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddatadevice.cpp @@ -0,0 +1,369 @@ +// Copyright (C) 2016 Klarälvdalens Datakonsult AB (KDAB). +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + +#include "qwaylanddatadevice_p.h" + +#include "qwaylanddatadevicemanager_p.h" +#include "qwaylanddataoffer_p.h" +#include "qwaylanddatasource_p.h" +#include "qwaylanddnd_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandabstractdecoration_p.h" +#include "qwaylandsurface_p.h" + +#include + +#include +#include +#include + +#if QT_CONFIG(clipboard) +#include +#endif +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +using namespace Qt::StringLiterals; + +QWaylandDataDevice::QWaylandDataDevice(QWaylandDataDeviceManager *manager, QWaylandInputDevice *inputDevice) + : QObject(inputDevice) + , QtWayland::wl_data_device(manager->get_data_device(inputDevice->wl_seat())) + , m_display(manager->display()) + , m_inputDevice(inputDevice) +{ +} + +QWaylandDataDevice::~QWaylandDataDevice() +{ + if (version() >= WL_DATA_DEVICE_RELEASE_SINCE_VERSION) + release(); + else + wl_data_device_destroy(object()); +} + +QWaylandDataOffer *QWaylandDataDevice::selectionOffer() const +{ + return m_selectionOffer.data(); +} + +void QWaylandDataDevice::invalidateSelectionOffer() +{ + if (m_selectionOffer.isNull()) + return; + + m_selectionOffer.reset(); + +#if QT_CONFIG(clipboard) + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Clipboard); +#endif +} + +QWaylandDataSource *QWaylandDataDevice::selectionSource() const +{ + return m_selectionSource.data(); +} + +void QWaylandDataDevice::setSelectionSource(QWaylandDataSource *source) +{ + if (source) + connect(source, &QWaylandDataSource::cancelled, this, &QWaylandDataDevice::selectionSourceCancelled); + set_selection(source ? source->object() : nullptr, m_inputDevice->serial()); + m_selectionSource.reset(source); +} + +#if QT_CONFIG(draganddrop) +QWaylandDataOffer *QWaylandDataDevice::dragOffer() const +{ + return m_dragOffer.data(); +} + +bool QWaylandDataDevice::startDrag(QMimeData *mimeData, Qt::DropActions supportedActions, QWaylandWindow *icon) +{ + auto *origin = m_display->lastInputWindow(); + + if (!origin) { + qCDebug(lcQpaWayland) << "Couldn't start a drag because the origin window could not be found."; + return false; + } + + // dragging data without mimetypes is a legal operation in Qt terms + // but Wayland uses a mimetype to determine if a drag is accepted or not + // In this rare case, insert a placeholder + if (mimeData->formats().isEmpty()) + mimeData->setData("application/x-qt-avoid-empty-placeholder"_L1, QByteArray("1")); + + static const QString plain = QStringLiteral("text/plain"); + static const QString utf8 = QStringLiteral("text/plain;charset=utf-8"); + + if (mimeData->hasFormat(plain) && !mimeData->hasFormat(utf8)) + mimeData->setData(utf8, mimeData->data(plain)); + + m_dragSource.reset(new QWaylandDataSource(m_display->dndSelectionHandler(), mimeData)); + + if (version() >= 3) + m_dragSource->set_actions(dropActionsToWl(supportedActions)); + + connect(m_dragSource.data(), &QWaylandDataSource::cancelled, this, &QWaylandDataDevice::dragSourceCancelled); + connect(m_dragSource.data(), &QWaylandDataSource::dndResponseUpdated, this, [this](bool accepted, Qt::DropAction action) { + auto drag = static_cast(QGuiApplicationPrivate::platformIntegration()->drag()); + if (!drag->currentDrag()) { + return; + } + // in old versions drop action is not set, so we guess + if (m_dragSource->version() < 3) { + drag->setResponse(accepted); + } else { + QPlatformDropQtResponse response(accepted, action); + drag->setResponse(response); + } + }); + connect(m_dragSource.data(), &QWaylandDataSource::dndDropped, this, + [this](bool accepted, Qt::DropAction action) { + QPlatformDropQtResponse response(accepted, action); + if (m_toplevelDrag) { + // If the widget was dropped but the drag not accepted it + // should be its own window in the future. To distinguish + // from canceling mid-drag the drag is accepted here as the + // we know if the widget is over a zone where it can be + // incorporated or not + response = { accepted, Qt::MoveAction }; + } + static_cast(QGuiApplicationPrivate::platformIntegration()->drag()) + ->setDropResponse(response); + }); + connect(m_dragSource.data(), &QWaylandDataSource::finished, this, [this]() { + static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag(); + if (m_toplevelDrag) { + m_toplevelDrag->destroy(); + m_toplevelDrag = nullptr; + } + }); + + if (mimeData->hasFormat("application/x-qt-mainwindowdrag-window"_L1) + && m_display->xdgToplevelDragManager()) { + qintptr dockWindowPtr; + QPoint offset; + QDataStream windowStream(mimeData->data("application/x-qt-mainwindowdrag-window"_L1)); + windowStream >> dockWindowPtr; + QWindow *dockWindow = reinterpret_cast(dockWindowPtr); + QDataStream offsetStream(mimeData->data("application/x-qt-mainwindowdrag-position"_L1)); + offsetStream >> offset; + if (auto waylandWindow = static_cast(dockWindow->handle())) { + if (auto toplevel = waylandWindow->surfaceRole()) { + m_toplevelDrag = new QtWayland::xdg_toplevel_drag_v1( + m_display->xdgToplevelDragManager()->get_xdg_toplevel_drag( + m_dragSource->object())); + m_toplevelDrag->attach(toplevel, offset.x(), offset.y()); + } + } + } + + start_drag(m_dragSource->object(), origin->wlSurface(), icon->wlSurface(), m_display->lastInputSerial()); + return true; +} + +void QWaylandDataDevice::cancelDrag() +{ + m_dragSource.reset(); +} +#endif + +void QWaylandDataDevice::data_device_data_offer(struct ::wl_data_offer *id) +{ + new QWaylandDataOffer(m_display, id); +} + +#if QT_CONFIG(draganddrop) +void QWaylandDataDevice::data_device_drop() +{ + QDrag *drag = static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag(); + + QMimeData *dragData = nullptr; + Qt::DropActions supportedActions; + if (drag) { + dragData = drag->mimeData(); + supportedActions = drag->supportedActions(); + } else if (m_dragOffer) { + dragData = m_dragOffer->mimeData(); + supportedActions = m_dragOffer->supportedActions(); + } else { + return; + } + + QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(m_dragWindow, dragData, m_dragPoint, supportedActions, + QGuiApplication::mouseButtons(), + m_inputDevice->modifiers()); + if (drag) { + auto drag = static_cast(QGuiApplicationPrivate::platformIntegration()->drag()); + drag->setDropResponse(response); + drag->finishDrag(); + } else if (m_dragOffer) { + m_dragOffer->finish(); + } +} + +void QWaylandDataDevice::data_device_enter(uint32_t serial, wl_surface *surface, wl_fixed_t x, wl_fixed_t y, wl_data_offer *id) +{ + auto *dragWaylandWindow = surface ? QWaylandWindow::fromWlSurface(surface) : nullptr; + if (!dragWaylandWindow) + return; // Ignore foreign surfaces + + m_dragWindow = dragWaylandWindow->window(); + m_dragPoint = calculateDragPosition(x, y, m_dragWindow); + m_enterSerial = serial; + + QMimeData *dragData = nullptr; + Qt::DropActions supportedActions; + + m_dragOffer.reset(static_cast(wl_data_offer_get_user_data(id))); + QDrag *drag = static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag(); + if (drag) { + dragData = drag->mimeData(); + supportedActions = drag->supportedActions(); + } else if (m_dragOffer) { + dragData = m_dragOffer->mimeData(); + supportedActions = m_dragOffer->supportedActions(); + } + + const QPlatformDragQtResponse &response = QWindowSystemInterface::handleDrag( + m_dragWindow, dragData, m_dragPoint, supportedActions, QGuiApplication::mouseButtons(), + m_inputDevice->modifiers()); + if (drag) { + static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response); + } + + sendResponse(supportedActions, response); +} + +void QWaylandDataDevice::data_device_leave() +{ + if (m_dragWindow) + QWindowSystemInterface::handleDrag(m_dragWindow, nullptr, QPoint(), Qt::IgnoreAction, + QGuiApplication::mouseButtons(), + m_inputDevice->modifiers()); + + QDrag *drag = static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag(); + if (!drag) { + m_dragOffer.reset(); + } +} + +void QWaylandDataDevice::data_device_motion(uint32_t time, wl_fixed_t x, wl_fixed_t y) +{ + Q_UNUSED(time); + + QDrag *drag = static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag(); + + if (!drag && !m_dragOffer) + return; + + if (!m_dragWindow) + return; + + m_dragPoint = calculateDragPosition(x, y, m_dragWindow); + + QMimeData *dragData = nullptr; + Qt::DropActions supportedActions; + if (drag) { + dragData = drag->mimeData(); + supportedActions = drag->supportedActions(); + } else { + dragData = m_dragOffer->mimeData(); + supportedActions = m_dragOffer->supportedActions(); + } + + const QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag(m_dragWindow, dragData, m_dragPoint, supportedActions, + QGuiApplication::mouseButtons(), + m_inputDevice->modifiers()); + + if (drag) { + static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response); + } + + sendResponse(supportedActions, response); +} +#endif // QT_CONFIG(draganddrop) + +void QWaylandDataDevice::data_device_selection(wl_data_offer *id) +{ + if (id) + m_selectionOffer.reset(static_cast(wl_data_offer_get_user_data(id))); + else + m_selectionOffer.reset(); + +#if QT_CONFIG(clipboard) + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Clipboard); +#endif +} + +void QWaylandDataDevice::selectionSourceCancelled() +{ + m_selectionSource.reset(); +#if QT_CONFIG(clipboard) + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Clipboard); +#endif +} + +#if QT_CONFIG(draganddrop) +void QWaylandDataDevice::dragSourceCancelled() +{ + static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag(); + m_dragSource.reset(); + if (m_toplevelDrag) { + m_toplevelDrag->destroy(); + m_toplevelDrag = nullptr; + } +} + +QPoint QWaylandDataDevice::calculateDragPosition(int x, int y, QWindow *wnd) const +{ + QPoint pnt(wl_fixed_to_int(x), wl_fixed_to_int(y)); + if (wnd) { + QWaylandWindow *wwnd = static_cast(m_dragWindow->handle()); + if (wwnd && wwnd->decoration()) { + pnt -= QPoint(wwnd->decoration()->margins().left(), + wwnd->decoration()->margins().top()); + } + } + return pnt; +} + +void QWaylandDataDevice::sendResponse(Qt::DropActions supportedActions, const QPlatformDragQtResponse &response) +{ + if (response.isAccepted()) { + if (version() >= 3) + m_dragOffer->set_actions(dropActionsToWl(supportedActions), dropActionsToWl(response.acceptedAction())); + + m_dragOffer->accept(m_enterSerial, m_dragOffer->firstFormat()); + } else { + m_dragOffer->accept(m_enterSerial, QString()); + } +} + +int QWaylandDataDevice::dropActionsToWl(Qt::DropActions actions) +{ + + int wlActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (actions & Qt::CopyAction) + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + if (actions & (Qt::MoveAction | Qt::TargetMoveAction)) + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + + // wayland does not support LinkAction at the time of writing + return wlActions; +} + + +#endif // QT_CONFIG(draganddrop) + +} + +QT_END_NAMESPACE + +#include "moc_qwaylanddatadevice_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylanddatadevice_p.h b/src/plugins/platforms/wayland/qwaylanddatadevice_p.h new file mode 100644 index 00000000000..3dc4fcaf60e --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddatadevice_p.h @@ -0,0 +1,108 @@ +// Copyright (C) 2016 Klarälvdalens Datakonsult AB (KDAB). +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + +#ifndef QWAYLANDDATADEVICE_H +#define QWAYLANDDATADEVICE_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 + +QT_REQUIRE_CONFIG(wayland_datadevice); + +QT_BEGIN_NAMESPACE + +class QMimeData; +class QPlatformDragQtResponse; +class QWindow; + +namespace QtWayland { +class xdg_toplevel_drag_v1; +} + +namespace QtWaylandClient { + +class QWaylandDisplay; +class QWaylandDataDeviceManager; +class QWaylandDataOffer; +class QWaylandDataSource; +class QWaylandInputDevice; +class QWaylandWindow; + +class QWaylandDataDevice : public QObject, public QtWayland::wl_data_device +{ + Q_OBJECT +public: + QWaylandDataDevice(QWaylandDataDeviceManager *manager, QWaylandInputDevice *inputDevice); + ~QWaylandDataDevice() override; + + QWaylandDataOffer *selectionOffer() const; + void invalidateSelectionOffer(); + QWaylandDataSource *selectionSource() const; + void setSelectionSource(QWaylandDataSource *source); + +#if QT_CONFIG(draganddrop) + QWaylandDataOffer *dragOffer() const; + bool startDrag(QMimeData *mimeData, Qt::DropActions supportedActions, QWaylandWindow *icon); + void cancelDrag(); +#endif + +protected: + void data_device_data_offer(struct ::wl_data_offer *id) override; + +#if QT_CONFIG(draganddrop) + void data_device_drop() override; + void data_device_enter(uint32_t serial, struct ::wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct ::wl_data_offer *id) override; + void data_device_leave() override; + void data_device_motion(uint32_t time, wl_fixed_t x, wl_fixed_t y) override; +#endif + void data_device_selection(struct ::wl_data_offer *id) override; + +private Q_SLOTS: + void selectionSourceCancelled(); + +#if QT_CONFIG(draganddrop) + void dragSourceCancelled(); +#endif + +private: +#if QT_CONFIG(draganddrop) + QPoint calculateDragPosition(int x, int y, QWindow *wnd) const; +#endif + void sendResponse(Qt::DropActions supportedActions, const QPlatformDragQtResponse &response); + + static int dropActionsToWl(Qt::DropActions dropActions); + + + QWaylandDisplay *m_display = nullptr; + QWaylandInputDevice *m_inputDevice = nullptr; + uint32_t m_enterSerial = 0; + QPointer m_dragWindow; + QPoint m_dragPoint; + QScopedPointer m_dragOffer; + QScopedPointer m_selectionOffer; + QScopedPointer m_selectionSource; + QScopedPointer m_dragSource; + QtWayland::xdg_toplevel_drag_v1 *m_toplevelDrag = nullptr; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDDATADEVICE_H diff --git a/src/plugins/platforms/wayland/qwaylanddatadevicemanager.cpp b/src/plugins/platforms/wayland/qwaylanddatadevicemanager.cpp new file mode 100644 index 00000000000..961d055ea6d --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddatadevicemanager.cpp @@ -0,0 +1,46 @@ +// Copyright (C) 2016 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 + +#include "qwaylanddatadevicemanager_p.h" + +#include "qwaylandinputdevice_p.h" +#include "qwaylanddatadevice_p.h" +#include "qwaylanddataoffer_p.h" +#include "qwaylanddisplay_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandDataDeviceManager::QWaylandDataDeviceManager(QWaylandDisplay *display, int version, uint32_t id) + : wl_data_device_manager(display->wl_registry(), id, qMin(version, 3)) + , m_display(display) +{ + // Create transfer devices for all input devices. + // ### This only works if we get the global before all devices and is surely wrong when hotplugging. + QList inputDevices = m_display->inputDevices(); + for (int i = 0; i < inputDevices.size();i++) { + inputDevices.at(i)->setDataDevice(getDataDevice(inputDevices.at(i))); + } +} + +QWaylandDataDeviceManager::~QWaylandDataDeviceManager() +{ + wl_data_device_manager_destroy(object()); +} + +QWaylandDataDevice *QWaylandDataDeviceManager::getDataDevice(QWaylandInputDevice *inputDevice) +{ + return new QWaylandDataDevice(this, inputDevice); +} + +QWaylandDisplay *QWaylandDataDeviceManager::display() const +{ + return m_display; +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylanddatadevicemanager_p.h b/src/plugins/platforms/wayland/qwaylanddatadevicemanager_p.h new file mode 100644 index 00000000000..7e1cb1e456f --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddatadevicemanager_p.h @@ -0,0 +1,50 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDDATADEVICEMANAGER_H +#define QWAYLANDDATADEVICEMANAGER_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 + +QT_REQUIRE_CONFIG(wayland_datadevice); + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; +class QWaylandDataDevice; +class QWaylandDataSource; +class QWaylandInputDevice; + +class Q_WAYLANDCLIENT_EXPORT QWaylandDataDeviceManager : public QtWayland::wl_data_device_manager +{ +public: + QWaylandDataDeviceManager(QWaylandDisplay *display, int version, uint32_t id); + ~QWaylandDataDeviceManager() override; + + QWaylandDataDevice *getDataDevice(QWaylandInputDevice *inputDevice); + + QWaylandDisplay *display() const; + +private: + QWaylandDisplay *m_display = nullptr; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDDATADEVICEMANAGER_H diff --git a/src/plugins/platforms/wayland/qwaylanddataoffer.cpp b/src/plugins/platforms/wayland/qwaylanddataoffer.cpp new file mode 100644 index 00000000000..e15a5efdc34 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddataoffer.cpp @@ -0,0 +1,273 @@ +// Copyright (C) 2016 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 + +#include "qwaylanddataoffer_p.h" +#include "qwaylanddatadevicemanager_p.h" +#include "qwaylanddisplay_p.h" + +#include +#include +#include + +#include + +using namespace std::chrono; + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +static QString plainText() +{ + return QStringLiteral("text/plain"); +} + +static QString utf8Text() +{ + return QStringLiteral("text/plain;charset=utf-8"); +} + +static QString uriList() +{ + return QStringLiteral("text/uri-list"); +} + +static QString mozUrl() +{ + return QStringLiteral("text/x-moz-url"); +} + +static QString portalFileTransfer() +{ + return QStringLiteral("application/vnd.portal.filetransfer"); +} + +static QByteArray convertData(const QString &originalMime, const QString &newMime, const QByteArray &data) +{ + if (originalMime == newMime) + return data; + + // Convert text/x-moz-url, which is an UTF-16 string of + // URL and page title pairs, all separated by line breaks, to text/uri-list. + // see also qtbase/src/plugins/platforms/xcb/qxcbmime.cpp + if (originalMime == uriList() && newMime == mozUrl()) { + if (data.size() > 1) { + const quint8 byte0 = data.at(0); + const quint8 byte1 = data.at(1); + + if ((byte0 == 0xff && byte1 == 0xfe) || (byte0 == 0xfe && byte1 == 0xff) + || (byte0 != 0 && byte1 == 0) || (byte0 == 0 && byte1 != 0)) { + QByteArray converted; + const QString str = QString::fromUtf16( + reinterpret_cast(data.constData()), data.size() / 2); + if (!str.isNull()) { + const auto urls = QStringView{str}.split(u'\n'); + // Only the URL is interesting, skip the page title. + for (int i = 0; i < urls.size(); i += 2) { + const QUrl url(urls.at(i).trimmed().toString()); + if (url.isValid()) { + converted += url.toEncoded(); + converted += "\r\n"; + } + } + } + return converted; + // 8 byte encoding, remove a possible 0 at the end. + } else { + QByteArray converted = data; + if (converted.endsWith('\0')) + converted.chop(1); + converted += "\r\n"; + return converted; + } + } + } + + return data; +} + +QWaylandDataOffer::QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer) + : QtWayland::wl_data_offer(offer) + , m_display(display) + , m_mimeData(new QWaylandMimeData(this)) +{ +} + +QWaylandDataOffer::~QWaylandDataOffer() +{ + destroy(); +} + + +QString QWaylandDataOffer::firstFormat() const +{ + if (m_mimeData->formats().isEmpty()) + return QString(); + + return m_mimeData->formats().first(); +} + +QMimeData *QWaylandDataOffer::mimeData() +{ + return m_mimeData.data(); +} + +Qt::DropActions QWaylandDataOffer::supportedActions() const +{ + if (version() < 3) { + return Qt::MoveAction | Qt::CopyAction; + } + + return m_supportedActions; +} + +void QWaylandDataOffer::startReceiving(const QString &mimeType, int fd) +{ + receive(mimeType, fd); + wl_display_flush(m_display->wl_display()); +} + +void QWaylandDataOffer::data_offer_offer(const QString &mime_type) +{ + m_mimeData->appendFormat(mime_type); +} + +void QWaylandDataOffer::data_offer_action(uint32_t dnd_action) +{ + Q_UNUSED(dnd_action); + // This is the compositor telling the drag target what action it should perform + // It does not map nicely into Qt final drop semantics, other than pretending there is only one supported action? +} + +void QWaylandDataOffer::data_offer_source_actions(uint32_t source_actions) +{ + m_supportedActions = Qt::DropActions(); + if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + m_supportedActions |= Qt::MoveAction; + if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + m_supportedActions |= Qt::CopyAction; +} + +QWaylandMimeData::QWaylandMimeData(QWaylandAbstractDataOffer *dataOffer) + : m_dataOffer(dataOffer) +{ +} + +QWaylandMimeData::~QWaylandMimeData() +{ +} + +void QWaylandMimeData::appendFormat(const QString &mimeType) +{ + // "DELETE" is a potential leftover from XdndActionMode sent by e.g. Firefox, ignore it. + if (mimeType != QLatin1String("DELETE")) { + m_types << mimeType; + m_data.remove(mimeType); // Clear previous contents + } +} + +bool QWaylandMimeData::hasFormat_sys(const QString &mimeType) const +{ + return formats().contains(mimeType); +} + +QStringList QWaylandMimeData::formats_sys() const +{ + QStringList types; + types.reserve(m_types.size()); + + for (const QString &type : m_types) { + QString mime = type; + + if (mime == utf8Text()) { + mime = plainText(); + } else if (mime == mozUrl()) { + mime = uriList(); + } + + if (!types.contains(mime)) { + types << mime; + } + } + + return types; +} + +QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QMetaType type) const +{ + Q_UNUSED(type); + + auto it = m_data.constFind(mimeType); + if (it != m_data.constEnd()) + return *it; + + QString mime = mimeType; + + if (!m_types.contains(mimeType)) { + if (mimeType == plainText() && m_types.contains(utf8Text())) + mime = utf8Text(); + else if (mimeType == uriList() && m_types.contains(mozUrl())) + mime = mozUrl(); + else + return QVariant(); + } + + int pipefd[2]; + if (qt_safe_pipe(pipefd) == -1) { + qWarning("QWaylandMimeData: pipe2() failed"); + return QVariant(); + } + + m_dataOffer->startReceiving(mime, pipefd[1]); + + close(pipefd[1]); + + QByteArray content; + if (readData(pipefd[0], content) != 0) { + qWarning("QWaylandDataOffer: error reading data for mimeType %s", qPrintable(mimeType)); + content = QByteArray(); + } + + close(pipefd[0]); + + content = convertData(mimeType, mime, content); + + if (mimeType != portalFileTransfer()) + m_data.insert(mimeType, content); + + return content; +} + +int QWaylandMimeData::readData(int fd, QByteArray &data) const +{ + struct pollfd readset; + readset.fd = fd; + readset.events = POLLIN; + + Q_FOREVER { + int ready = qt_safe_poll(&readset, 1, QDeadlineTimer(1s)); + if (ready < 0) { + qWarning() << "QWaylandDataOffer: qt_safe_poll() failed"; + return -1; + } else if (ready == 0) { + qWarning("QWaylandDataOffer: timeout reading from pipe"); + return -1; + } else { + char buf[4096]; + int n = QT_READ(fd, buf, sizeof buf); + + if (n < 0) { + qWarning("QWaylandDataOffer: read() failed"); + return -1; + } else if (n == 0) { + return 0; + } else if (n > 0) { + data.append(buf, n); + } + } + } +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylanddataoffer_p.h b/src/plugins/platforms/wayland/qwaylanddataoffer_p.h new file mode 100644 index 00000000000..1c99147d233 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddataoffer_p.h @@ -0,0 +1,93 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDDATAOFFER_H +#define QWAYLANDDATAOFFER_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 + +QT_REQUIRE_CONFIG(wayland_datadevice); + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; +class QWaylandMimeData; + +class QWaylandAbstractDataOffer +{ +public: + virtual void startReceiving(const QString &mimeType, int fd) = 0; + virtual QMimeData *mimeData() = 0; + + virtual ~QWaylandAbstractDataOffer() = default; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandDataOffer + : public QtWayland::wl_data_offer // needs to be the first because we do static casts from the user pointer to the wrapper + , public QWaylandAbstractDataOffer +{ +public: + explicit QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer); + ~QWaylandDataOffer() override; + QMimeData *mimeData() override; + Qt::DropActions supportedActions() const; + + QString firstFormat() const; + + void startReceiving(const QString &mimeType, int fd) override; + +protected: + void data_offer_offer(const QString &mime_type) override; + void data_offer_source_actions(uint32_t source_actions) override; + void data_offer_action(uint32_t dnd_action) override; + +private: + QWaylandDisplay *m_display = nullptr; + QScopedPointer m_mimeData; + Qt::DropActions m_supportedActions; +}; + + +class QWaylandMimeData : public QInternalMimeData { +public: + explicit QWaylandMimeData(QWaylandAbstractDataOffer *dataOffer); + ~QWaylandMimeData() override; + + void appendFormat(const QString &mimeType); + +protected: + bool hasFormat_sys(const QString &mimeType) const override; + QStringList formats_sys() const override; + QVariant retrieveData_sys(const QString &mimeType, QMetaType type) const override; + +private: + int readData(int fd, QByteArray &data) const; + + QWaylandAbstractDataOffer *m_dataOffer = nullptr; + mutable QStringList m_types; + mutable QHash m_data; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE +#endif diff --git a/src/plugins/platforms/wayland/qwaylanddatasource.cpp b/src/plugins/platforms/wayland/qwaylanddatasource.cpp new file mode 100644 index 00000000000..966d5ef7f1d --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddatasource.cpp @@ -0,0 +1,104 @@ +// Copyright (C) 2016 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 + +#include "qwaylanddatasource_p.h" +#include "qwaylanddataoffer_p.h" +#include "qwaylanddatadevicemanager_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylandmimehelper_p.h" + +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandDataSource::QWaylandDataSource(QWaylandDataDeviceManager *dataDeviceManager, QMimeData *mimeData) + : QtWayland::wl_data_source(dataDeviceManager->create_data_source()) + , m_mime_data(mimeData) +{ + if (!mimeData) + return; + const auto formats = QInternalMimeData::formatsHelper(mimeData); + for (const QString &format : formats) { + offer(format); + } +} + +QWaylandDataSource::~QWaylandDataSource() +{ + destroy(); +} + +void QWaylandDataSource::data_source_cancelled() +{ + Q_EMIT cancelled(); +} + +void QWaylandDataSource::data_source_send(const QString &mime_type, int32_t fd) +{ + QByteArray content = QWaylandMimeHelper::getByteArray(m_mime_data, mime_type); + if (!content.isEmpty()) { + // Create a sigpipe handler that does nothing, or clients may be forced to terminate + // if the pipe is closed in the other end. + struct sigaction action, oldAction; + action.sa_handler = SIG_IGN; + sigemptyset (&action.sa_mask); + action.sa_flags = 0; + + sigaction(SIGPIPE, &action, &oldAction); + // Some compositors (e.g., mutter) make fd with O_NONBLOCK. + // Since wl_data_source.send describes that fd is closed here, + // it should be done in a loop and don't have any advantage. + // Blocking operation will be used. + // According to fcntl(2), FSETFL ignores O_WRONLY. So this + // call will just remove O_NONBLOCK. + fcntl(fd, F_SETFL, O_WRONLY); + ssize_t unused = write(fd, content.constData(), content.size()); + Q_UNUSED(unused); + sigaction(SIGPIPE, &oldAction, nullptr); + } + close(fd); +} + +void QWaylandDataSource::data_source_target(const QString &mime_type) +{ + m_accepted = !mime_type.isEmpty(); + Q_EMIT dndResponseUpdated(m_accepted, m_dropAction); +} + +void QWaylandDataSource::data_source_action(uint32_t action) +{ + Qt::DropAction qtAction = Qt::IgnoreAction; + + if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + qtAction = Qt::MoveAction; + else if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + qtAction = Qt::CopyAction; + + m_dropAction = qtAction; + Q_EMIT dndResponseUpdated(m_accepted, m_dropAction); +} + +void QWaylandDataSource::data_source_dnd_finished() +{ + Q_EMIT finished(); +} + +void QWaylandDataSource::data_source_dnd_drop_performed() +{ + + Q_EMIT dndDropped(m_accepted, m_dropAction); +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylanddatasource_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylanddatasource_p.h b/src/plugins/platforms/wayland/qwaylanddatasource_p.h new file mode 100644 index 00000000000..a4b317c2626 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddatasource_p.h @@ -0,0 +1,66 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDDATASOURCE_H +#define QWAYLANDDATASOURCE_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 + +QT_REQUIRE_CONFIG(wayland_datadevice); + +QT_BEGIN_NAMESPACE + +class QMimeData; + +namespace QtWaylandClient { + +class QWaylandDataDeviceManager; +class QWaylandDisplay; + +class Q_WAYLANDCLIENT_EXPORT QWaylandDataSource : public QObject, public QtWayland::wl_data_source +{ + Q_OBJECT +public: + QWaylandDataSource(QWaylandDataDeviceManager *dataDeviceManager, QMimeData *mimeData); + ~QWaylandDataSource() override; + +Q_SIGNALS: + void cancelled(); + void finished(); + + void dndResponseUpdated(bool accepted, Qt::DropAction action); + void dndDropped(bool accepted, Qt::DropAction action); + +protected: + void data_source_cancelled() override; + void data_source_send(const QString &mime_type, int32_t fd) override; + void data_source_target(const QString &mime_type) override; + void data_source_dnd_drop_performed() override; + void data_source_dnd_finished() override; + void data_source_action(uint32_t action) override; + +private: + QMimeData *m_mime_data = nullptr; + bool m_accepted = false; + Qt::DropAction m_dropAction = Qt::IgnoreAction; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDDATASOURCE_H diff --git a/src/plugins/platforms/wayland/qwaylanddecorationfactory.cpp b/src/plugins/platforms/wayland/qwaylanddecorationfactory.cpp new file mode 100644 index 00000000000..b716a4aeea5 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddecorationfactory.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2016 Robin Burchell +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylanddecorationfactory_p.h" +#include "qwaylanddecorationplugin_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, qwdfiLoader, + (QWaylandDecorationFactoryInterface_iid, QLatin1String("/wayland-decoration-client"), Qt::CaseInsensitive)) + +QStringList QWaylandDecorationFactory::keys() +{ + return qwdfiLoader->keyMap().values(); +} + +QWaylandAbstractDecoration *QWaylandDecorationFactory::create(const QString &name, const QStringList &args) +{ + return qLoadPlugin(qwdfiLoader(), name, args); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylanddecorationfactory_p.h b/src/plugins/platforms/wayland/qwaylanddecorationfactory_p.h new file mode 100644 index 00000000000..bd0998631c4 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddecorationfactory_p.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 Robin Burchell +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDDECORATIONFACTORY_H +#define QWAYLANDDECORATIONFACTORY_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandAbstractDecoration; + +class Q_WAYLANDCLIENT_EXPORT QWaylandDecorationFactory +{ +public: + static QStringList keys(); + static QWaylandAbstractDecoration *create(const QString &name, const QStringList &args); +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDDECORATIONFACTORY_H diff --git a/src/plugins/platforms/wayland/qwaylanddecorationplugin.cpp b/src/plugins/platforms/wayland/qwaylanddecorationplugin.cpp new file mode 100644 index 00000000000..f642119960e --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddecorationplugin.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2016 Robin Burchell +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylanddecorationplugin_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandDecorationPlugin::QWaylandDecorationPlugin(QObject *parent) + : QObject(parent) +{ +} +QWaylandDecorationPlugin::~QWaylandDecorationPlugin() +{ +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylanddecorationplugin_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylanddecorationplugin_p.h b/src/plugins/platforms/wayland/qwaylanddecorationplugin_p.h new file mode 100644 index 00000000000..e09d99702d6 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddecorationplugin_p.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 Robin Burchell +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDDECORATIONPLUGIN_H +#define QWAYLANDDECORATIONPLUGIN_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandAbstractDecoration; + +#define QWaylandDecorationFactoryInterface_iid "org.qt-project.Qt.WaylandClient.QWaylandDecorationFactoryInterface.5.4" + +class Q_WAYLANDCLIENT_EXPORT QWaylandDecorationPlugin : public QObject +{ + Q_OBJECT +public: + explicit QWaylandDecorationPlugin(QObject *parent = nullptr); + ~QWaylandDecorationPlugin() override; + + virtual QWaylandAbstractDecoration *create(const QString &key, const QStringList ¶mList) = 0; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDDECORATIONPLUGIN_H diff --git a/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/src/plugins/platforms/wayland/qwaylanddisplay.cpp new file mode 100644 index 00000000000..c4cdbecf6ae --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddisplay.cpp @@ -0,0 +1,1095 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2024 Jie Liu +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylanddisplay_p.h" + +#include "qwaylandappmenu_p.h" +#include "qwaylandintegration_p.h" +#include "qwaylandwindow_p.h" +#include "qwaylandsurface_p.h" +#include "qwaylandabstractdecoration_p.h" +#include "qwaylandscreen_p.h" +#include "qwaylandcursor_p.h" +#include "qwaylandinputdevice_p.h" +#if QT_CONFIG(clipboard) +#include "qwaylandclipboard_p.h" +#include "qwaylanddatacontrolv1_p.h" +#endif +#if QT_CONFIG(wayland_datadevice) +#include "qwaylanddatadevicemanager_p.h" +#include "qwaylanddatadevice_p.h" +#endif // QT_CONFIG(wayland_datadevice) +#if QT_CONFIG(wayland_client_primary_selection) +#include "qwaylandprimaryselectionv1_p.h" +#endif // QT_CONFIG(wayland_client_primary_selection) +#if QT_CONFIG(cursor) +#include +#endif +#include "qwaylandhardwareintegration_p.h" +#include "qwaylandtextinputv1_p.h" +#include "qwaylandtextinputv2_p.h" +#include "qwaylandtextinputv3_p.h" +#include "qwaylandinputcontext_p.h" +#include "qwaylandinputmethodcontext_p.h" + +#include "qwaylandwindowmanagerintegration_p.h" +#include "qwaylandshellintegration_p.h" +#include "qwaylandclientbufferintegration_p.h" + +#include "qwaylandpointergestures_p.h" +#include "qwaylandsubsurface_p.h" +#if QT_CONFIG(tabletevent) +#include "qwaylandtabletv2_p.h" +#endif + +#include "qwaylandcolormanagement_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include + +#include // for std::tie + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class EventThread : public QThread +{ + Q_OBJECT +public: + enum OperatingMode { + EmitToDispatch, // Emit the signal, allow dispatching in a differnt thread. + SelfDispatch, // Dispatch the events inside this thread. + }; + + EventThread(struct wl_display * wl, struct wl_event_queue * ev_queue, + OperatingMode mode) + : m_fd(wl_display_get_fd(wl)) + , m_pipefd{ -1, -1 } + , m_wldisplay(wl) + , m_wlevqueue(ev_queue) + , m_mode(mode) + , m_reading(true) + , m_quitting(false) + { + setObjectName(QStringLiteral("WaylandEventThread")); + } + + void readAndDispatchEvents() + { + /* + * Dispatch pending events and flush the requests at least once. If the event thread + * is not reading, try to call _prepare_read() to allow the event thread to poll(). + * If that fails, re-try dispatch & flush again until _prepare_read() is successful. + * + * This allow any call to readAndDispatchEvents() to start event thread's polling, + * not only the one issued from event thread's waitForReading(), which means functions + * called from dispatch_pending() can safely spin an event loop. + */ + if (m_quitting.loadRelaxed()) + return; + + for (;;) { + if (dispatchQueuePending() < 0) { + Q_EMIT waylandError(); + m_quitting.storeRelaxed(true); + return; + } + + wl_display_flush(m_wldisplay); + + // We have to check if event thread is reading every time we dispatch + // something, as that may recursively call this function. + if (m_reading.loadAcquire()) + break; + + if (prepareReadQueue() == 0) { + QMutexLocker l(&m_mutex); + m_reading.storeRelease(true); + m_cond.wakeOne(); + break; + } + } + } + + void stop() + { + // We have to both write to the pipe and set the flag, as the thread may be + // either in the poll() or waiting for _prepare_read(). + if (m_pipefd[1] != -1 && write(m_pipefd[1], "\0", 1) == -1) + qWarning("Failed to write to the pipe: %s.", strerror(errno)); + + m_quitting.storeRelaxed(true); + m_cond.wakeOne(); + + wait(); + } + +Q_SIGNALS: + void needReadAndDispatch(); + void waylandError(); + +protected: + void run() override + { + // we use this pipe to make the loop exit otherwise if we simply used a flag on the loop condition, if stop() gets + // called while poll() is blocking the thread will never quit since there are no wayland messages coming anymore. + struct Pipe + { + Pipe(int *fds) + : fds(fds) + { + if (qt_safe_pipe(fds) != 0) + qWarning("Pipe creation failed. Quitting may hang."); + } + ~Pipe() + { + if (fds[0] != -1) { + close(fds[0]); + close(fds[1]); + } + } + + int *fds; + } pipe(m_pipefd); + + // Make the main thread call wl_prepare_read(), dispatch the pending messages and flush the + // outbound ones. Wait until it's done before proceeding, unless we're told to quit. + while (waitForReading()) { + if (!m_reading.loadRelaxed()) + break; + + pollfd fds[2] = { { m_fd, POLLIN, 0 }, { m_pipefd[0], POLLIN, 0 } }; + poll(fds, 2, -1); + + if (fds[1].revents & POLLIN) { + // we don't really care to read the byte that was written here since we're closing down + wl_display_cancel_read(m_wldisplay); + break; + } + + if (fds[0].revents & POLLIN) + wl_display_read_events(m_wldisplay); + // The poll was succesfull and the event thread did the wl_display_read_events(). On the next iteration of the loop + // the event sent to the main thread will cause it to dispatch the messages just read, unless the loop exits in which + // case we don't care anymore about them. + else + wl_display_cancel_read(m_wldisplay); + } + } + +private: + bool waitForReading() + { + Q_ASSERT(QThread::currentThread() == this); + + m_reading.storeRelease(false); + + if (m_mode == SelfDispatch) { + readAndDispatchEvents(); + } else { + Q_EMIT needReadAndDispatch(); + + QMutexLocker lock(&m_mutex); + // m_reading might be set from our emit or some other invocation of + // readAndDispatchEvents(). + while (!m_reading.loadRelaxed() && !m_quitting.loadRelaxed()) + m_cond.wait(&m_mutex); + } + + return !m_quitting.loadRelaxed(); + } + + int dispatchQueuePending() + { + if (m_wlevqueue) + return wl_display_dispatch_queue_pending(m_wldisplay, m_wlevqueue); + else + return wl_display_dispatch_pending(m_wldisplay); + } + + int prepareReadQueue() + { + if (m_wlevqueue) + return wl_display_prepare_read_queue(m_wldisplay, m_wlevqueue); + else + return wl_display_prepare_read(m_wldisplay); + } + + int m_fd; + int m_pipefd[2]; + wl_display *m_wldisplay; + wl_event_queue *m_wlevqueue; + OperatingMode m_mode; + + /* Concurrency note when operating in EmitToDispatch mode: + * m_reading is set to false inside event thread's waitForReading(), and is + * set to true inside main thread's readAndDispatchEvents(). + * The lock is not taken when setting m_reading to false, as the main thread + * is not actively waiting for it to turn false. However, the lock is taken + * inside readAndDispatchEvents() before setting m_reading to true, + * as the event thread is actively waiting for it under the wait condition. + */ + + QAtomicInteger m_reading; + QAtomicInteger m_quitting; + QMutex m_mutex; + QWaitCondition m_cond; +}; + +Q_LOGGING_CATEGORY(lcQpaWayland, "qt.qpa.wayland"); // for general (uncategorized) Wayland platform logging + +struct wl_surface *QWaylandDisplay::createSurface(void *handle) +{ + struct wl_surface *surface = mGlobals.compositor->create_surface(); + wl_surface_set_user_data(surface, handle); + return surface; +} + +struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion) +{ + struct ::wl_region *region = mGlobals.compositor->create_region(); + + for (const QRect &rect : qregion) + wl_region_add(region, rect.x(), rect.y(), rect.width(), rect.height()); + + return region; +} + +::wl_subsurface *QWaylandDisplay::createSubSurface(QWaylandWindow *window, QWaylandWindow *parent) +{ + if (!mGlobals.subCompositor) { + qCWarning(lcQpaWayland) << "Can't create subsurface, not supported by the compositor."; + return nullptr; + } + + // Make sure we don't pass NULL surfaces to libwayland (crashes) + Q_ASSERT(parent->wlSurface()); + Q_ASSERT(window->wlSurface()); + + return mGlobals.subCompositor->get_subsurface(window->wlSurface(), parent->wlSurface()); +} + +::wp_viewport *QWaylandDisplay::createViewport(QWaylandWindow *window) +{ + if (!mGlobals.viewporter) { + qCWarning(lcQpaWayland) << "Can't create wp_viewport, not supported by the compositor."; + return nullptr; + } + + Q_ASSERT(window->wlSurface()); + return mGlobals.viewporter->get_viewport(window->wlSurface()); +} + +QWaylandShellIntegration *QWaylandDisplay::shellIntegration() const +{ + return mWaylandIntegration->shellIntegration(); +} + +QWaylandClientBufferIntegration * QWaylandDisplay::clientBufferIntegration() const +{ + return mWaylandIntegration->clientBufferIntegration(); +} + +QWaylandWindowManagerIntegration *QWaylandDisplay::windowManagerIntegration() const +{ + return mGlobals.windowManagerIntegration.get(); +} + +QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration) + : mWaylandIntegration(waylandIntegration) +{ + qRegisterMetaType("uint32_t"); + + mDisplay = wl_display_connect(nullptr); + if (mDisplay) { + setupConnection(); + } else { + qErrnoWarning(errno, "Failed to create wl_display"); + } + + mWaylandTryReconnect = qEnvironmentVariableIsSet("QT_WAYLAND_RECONNECT"); + mPreferWlrDataControl = qEnvironmentVariableIntValue("QT_WAYLAND_USE_DATA_CONTROL") > 0; +} + +void QWaylandDisplay::setupConnection() +{ + struct ::wl_registry *registry = wl_display_get_registry(mDisplay); + init(registry); + +#if QT_CONFIG(xkbcommon) + mXkbContext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS)); + if (!mXkbContext) + qCWarning(lcQpaWayland, "failed to create xkb context"); +#endif + if (mWaylandInputContextRequested) + checkTextInputProtocol(); +} + +QWaylandDisplay::~QWaylandDisplay(void) +{ + if (m_eventThread) + m_eventThread->stop(); + + if (m_frameEventQueueThread) + m_frameEventQueueThread->stop(); + + if (mSyncCallback) + wl_callback_destroy(mSyncCallback); + + qDeleteAll(std::exchange(mInputDevices, {})); + + for (QWaylandScreen *screen : std::exchange(mScreens, {})) { + QWindowSystemInterface::handleScreenRemoved(screen); + } + qDeleteAll(mWaitingScreens); + +#if QT_CONFIG(cursor) + mCursorThemes.clear(); +#endif + + if (m_frameEventQueue) + wl_event_queue_destroy(m_frameEventQueue); + + // Reset the globals manually since they need to be destroyed before the wl_display + mGlobals = {}; + + if (object()) + wl_registry_destroy(object()); + + if (mDisplay) + wl_display_disconnect(mDisplay); +} + +// Steps which is called just after constructor. This separates registry_global() out of the constructor +// so that factory functions in integration can be overridden. +bool QWaylandDisplay::initialize() +{ + if (!isInitialized()) + return false; + + forceRoundTrip(); + + emit connected(); + + if (!mWaitingScreens.isEmpty()) { + // Give wl_output.done and zxdg_output_v1.done events a chance to arrive + forceRoundTrip(); + } + if (mWaylandInputContextRequested) + mTextInputManagerIndex = INT_MAX; + + return qEnvironmentVariableIntValue("QT_WAYLAND_DONT_CHECK_SHELL_INTEGRATION") || shellIntegration(); +} + +void QWaylandDisplay::ensureScreen() +{ + if (!mScreens.empty() || mPlaceholderScreen) + return; // There are real screens or we already have a fake one + + qCInfo(lcQpaWayland) << "There are no outputs - creating placeholder screen"; + + mPlaceholderScreen = new QPlatformPlaceholderScreen(); + QWindowSystemInterface::handleScreenAdded(mPlaceholderScreen); + Q_ASSERT(!QGuiApplication::screens().empty()); +} + +void QWaylandDisplay::reconnect() +{ + qCWarning(lcQpaWayland) << "Attempting wayland reconnect"; + m_eventThread->stop(); + m_frameEventQueueThread->stop(); + m_eventThread->wait(); + m_frameEventQueueThread->wait(); + + qDeleteAll(mWaitingScreens); + mWaitingScreens.clear(); + + while (!mScreens.isEmpty()) { + auto screen = mScreens.takeLast(); + ensureScreen(); + QWindowSystemInterface::handleScreenRemoved(screen); + } + + mCursorThemes.clear(); + mCursor.reset(); + + mGlobals = GlobalHolder(); + + mWaylandIntegration->reset(); + + qDeleteAll(std::exchange(mInputDevices, {})); + mLastInputDevice = nullptr; + + for (const RegistryGlobal &global : mRegistryGlobals) { + emit globalRemoved(global); + } + mRegistryGlobals.clear(); + + mLastInputSerial = 0; + mLastInputWindow.clear(); + mLastKeyboardFocus.clear(); + mActiveWindows.clear(); + + const auto windows = QGuiApplication::allWindows(); + QList allPlatformWindows; + for (auto window : windows) { + if (auto waylandWindow = static_cast(window->handle())) { + waylandWindow->closeChildPopups(); + allPlatformWindows.push_back(waylandWindow); + } + } + + // Remove windows that do not need to be recreated and now closed popups + QList recreateWindows; + for (auto window : std::as_const(allPlatformWindows)) { + if (window->subSurfaceWindow() || window->shellSurface()) { + recreateWindows.push_back(window); + } + window->reset(); + } + + if (mSyncCallback) { + wl_callback_destroy(mSyncCallback); + mSyncCallback = nullptr; + } + + if (object()) + wl_registry_destroy(object()); + mDisplay = wl_display_connect(nullptr); + if (!mDisplay) + _exit(1); + + connect( + this, &QWaylandDisplay::connected, this, + [&allPlatformWindows] { + for (auto &window : std::as_const(allPlatformWindows)) { + window->initializeWlSurface(); + } + }, + Qt::SingleShotConnection); + + setupConnection(); + initialize(); + + if (m_frameEventQueue) + wl_event_queue_destroy(m_frameEventQueue); + initEventThread(); + + auto needsRecreate = [](QPlatformWindow *window) { + auto waylandWindow = static_cast(window); + return waylandWindow && !waylandWindow->subSurfaceWindow() && !waylandWindow->shellSurface(); + }; + auto window = recreateWindows.begin(); + while (!recreateWindows.isEmpty()) { + if (!needsRecreate((*window)->QPlatformWindow::parent()) && !needsRecreate((*window)->transientParent())) { + (*window)->reinit(); + window = recreateWindows.erase(window); + } else { + ++window; + } + if (window == recreateWindows.end()) + window = recreateWindows.begin(); + } + + mWaylandIntegration->reconfigureInputContext(); +} + +void QWaylandDisplay::flushRequests() +{ + m_eventThread->readAndDispatchEvents(); + QWindowSystemInterface::flushWindowSystemEvents(); +} + +// We have to wait until we have an eventDispatcher before creating the eventThread, +// otherwise forceRoundTrip() may block inside _events_read() because eventThread is +// polling. +void QWaylandDisplay::initEventThread() +{ + m_eventThread.reset( + new EventThread(mDisplay, /* default queue */ nullptr, EventThread::EmitToDispatch)); + connect(m_eventThread.get(), &EventThread::needReadAndDispatch, this, + &QWaylandDisplay::flushRequests, Qt::QueuedConnection); + connect(m_eventThread.get(), &EventThread::waylandError, this, + &QWaylandDisplay::checkWaylandError, Qt::QueuedConnection); + m_eventThread->start(); + + // wl_display_disconnect() free this. + m_frameEventQueue = wl_display_create_queue(mDisplay); + m_frameEventQueueThread.reset( + new EventThread(mDisplay, m_frameEventQueue, EventThread::SelfDispatch)); + m_frameEventQueueThread->start(); +} + +void QWaylandDisplay::checkWaylandError() +{ + int ecode = wl_display_get_error(mDisplay); + if ((ecode == EPIPE || ecode == ECONNRESET)) { + qWarning("The Wayland connection broke. Did the Wayland compositor die?"); + if (mWaylandTryReconnect) { + reconnect(); + return; + } + } else { + qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode)); + } + _exit(-1); +} + +void QWaylandDisplay::checkTextInputProtocol() +{ + QStringList tips, timps; // for text input protocols and text input manager protocols + // zwp_text_input_v2 is preferred over zwp_text_input_v3 because: + // - Currently, v3 is not as feature rich as v2. + // - While v2 is not upstreamed, it is well supported by KWin since Plasma 5 and Plasma + // Mobile uses some v2 only. + tips << QLatin1String(QtWayland::qt_text_input_method_v1::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_v2::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_v3::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_v1::interface()->name); + timps << QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_manager_v3::interface()->name) + << QLatin1String(QtWayland::zwp_text_input_manager_v1::interface()->name); + + QString tiProtocols = QString::fromLocal8Bit(qgetenv("QT_WAYLAND_TEXT_INPUT_PROTOCOL")); + qCDebug(lcQpaWayland) << "QT_WAYLAND_TEXT_INPUT_PROTOCOL=" << tiProtocols; + QStringList keys; + if (!tiProtocols.isEmpty()) { + keys = tiProtocols.split(QLatin1Char(';')); + QList::iterator it = keys.begin(); + while (it != keys.end()) { + if (tips.contains(*it)) + mTextInputManagerList.append(timps.at(tips.indexOf(*it))); + else + qCDebug(lcQpaWayland) << "text input: unknown protocol - " << *it; + ++it; + } + } + if (mTextInputManagerList.isEmpty()) // fallback + mTextInputManagerList = timps; +} + +QWaylandScreen *QWaylandDisplay::screenForOutput(struct wl_output *output) const +{ + for (auto screen : std::as_const(mScreens)) { + if (screen->output() == output) + return screen; + } + return nullptr; +} + +void QWaylandDisplay::handleScreenInitialized(QWaylandScreen *screen) +{ + if (!mWaitingScreens.removeOne(screen)) + return; + mScreens.append(screen); + QWindowSystemInterface::handleScreenAdded(screen); + if (mPlaceholderScreen) { + // handleScreenRemoved deletes the platform screen + QPlatformScreen *s = mPlaceholderScreen; + mPlaceholderScreen = nullptr; + QWindowSystemInterface::handleScreenRemoved(s); + + } +} + +template +struct WithDestructor : public T +{ + using T::T; + ~WithDestructor() + { + f(this->object()); + } +}; + +void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uint32_t version) +{ + struct ::wl_registry *registry = object(); + + static QStringList interfaceBlacklist = qEnvironmentVariable("QT_WAYLAND_DISABLED_INTERFACES").split(u','); + if (interfaceBlacklist.contains(interface)) { + return; + } + + if (interface == QLatin1String(QtWayland::wl_output::interface()->name)) { + mWaitingScreens << mWaylandIntegration->createPlatformScreen(this, version, id); + } else if (interface == QLatin1String(QtWayland::wl_compositor::interface()->name)) { + mGlobals.compositor.reset( + new WithDestructor( + registry, id, qMin((int)version, 6))); + } else if (interface == QLatin1String(QWaylandShm::interface()->name)) { + mGlobals.shm.reset(new QWaylandShm(this, version, id)); + } else if (interface == QLatin1String(QWaylandInputDevice::interface()->name)) { + QWaylandInputDevice *inputDevice = mWaylandIntegration->createInputDevice(this, version, id); + mInputDevices.append(inputDevice); +#if QT_CONFIG(wayland_datadevice) + } else if (interface == QLatin1String(QWaylandDataDeviceManager::interface()->name)) { + mGlobals.dndSelectionHandler.reset(new QWaylandDataDeviceManager(this, version, id)); +#endif + } else if (interface == QLatin1String(QtWayland::wl_subcompositor::interface()->name)) { + mGlobals.subCompositor.reset( + new WithDestructor(registry, + id, 1)); +#if QT_CONFIG(tabletevent) + } else if (interface == QLatin1String(QWaylandTabletManagerV2::interface()->name)) { + mGlobals.tabletManager.reset(new QWaylandTabletManagerV2(this, id, qMin(1, int(version)))); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTabletSeat( + new QWaylandTabletSeatV2(mGlobals.tabletManager.get(), inputDevice)); +#endif + } else if (interface == QLatin1String(QWaylandPointerGestures::interface()->name)) { + mGlobals.pointerGestures.reset(new QWaylandPointerGestures(this, id, 1)); +#if QT_CONFIG(wayland_client_primary_selection) + } else if (interface == QLatin1String(QWaylandPrimarySelectionDeviceManagerV1::interface()->name)) { + mGlobals.primarySelectionManager.reset( + new QWaylandPrimarySelectionDeviceManagerV1(this, id, 1)); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setPrimarySelectionDevice( + mGlobals.primarySelectionManager->createDevice(inputDevice)); +#endif + } else if (interface == QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name) + && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { + qCDebug(lcQpaWayland) << "text input: register qt_text_input_method_manager_v1"; + if (mTextInputManagerIndex < INT_MAX) { + mGlobals.textInputManagerv1.reset(); + mGlobals.textInputManagerv2.reset(); + mGlobals.textInputManagerv3.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInput(nullptr); + } + + mGlobals.textInputMethodManager.reset( + new WithDestructor(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInputMethod(new QWaylandTextInputMethod( + this, + mGlobals.textInputMethodManager->get_text_input_method( + inputDevice->wl_seat()))); + mWaylandIntegration->reconfigureInputContext(); + mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); + } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v1::interface()->name) + && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { + qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v1"; + if (mTextInputManagerIndex < INT_MAX) { + mGlobals.textInputMethodManager.reset(); + mGlobals.textInputManagerv2.reset(); + mGlobals.textInputManagerv3.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInputMethod(nullptr); + } + + mGlobals.textInputManagerv1.reset( + new WithDestructor(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) { + auto textInput = + new QWaylandTextInputv1(this, mGlobals.textInputManagerv1->create_text_input()); + textInput->setSeat(inputDevice->wl_seat()); + inputDevice->setTextInput(textInput); + } + + mWaylandIntegration->reconfigureInputContext(); + mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); + } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name) + && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { + qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v2"; + if (mTextInputManagerIndex < INT_MAX) { + mGlobals.textInputMethodManager.reset(); + mGlobals.textInputManagerv1.reset(); + mGlobals.textInputManagerv3.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInputMethod(nullptr); + } + + mGlobals.textInputManagerv2.reset( + new WithDestructor(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInput(new QWaylandTextInputv2( + this, mGlobals.textInputManagerv2->get_text_input(inputDevice->wl_seat()))); + mWaylandIntegration->reconfigureInputContext(); + mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); + } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v3::interface()->name) + && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { + qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v3"; + if (mTextInputManagerIndex < INT_MAX) { + mGlobals.textInputMethodManager.reset(); + mGlobals.textInputManagerv2.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInputMethod(nullptr); + } + mGlobals.textInputManagerv3.reset( + new WithDestructor(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInput(new QWaylandTextInputv3( + this, mGlobals.textInputManagerv3->get_text_input(inputDevice->wl_seat()))); + + mWaylandIntegration->reconfigureInputContext(); + mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); + }else if (interface == QLatin1String(QWaylandHardwareIntegration::interface()->name)) { + bool disableHardwareIntegration = qEnvironmentVariableIntValue("QT_WAYLAND_DISABLE_HW_INTEGRATION"); + if (!disableHardwareIntegration) { + mGlobals.hardwareIntegration.reset(new QWaylandHardwareIntegration(registry, id)); + // make a roundtrip here since we need to receive the events sent by + // qt_hardware_integration before creating windows + forceRoundTrip(); + } + } else if (interface == QLatin1String(QWaylandXdgOutputManagerV1::interface()->name)) { + mGlobals.xdgOutputManager.reset(new QWaylandXdgOutputManagerV1(this, id, version)); + for (auto *screen : std::as_const(mWaitingScreens)) + screen->initXdgOutput(xdgOutputManager()); + } else if (interface == QLatin1String(QtWayland::wp_fractional_scale_manager_v1::interface()->name)) { + mGlobals.fractionalScaleManager.reset( + new WithDestructor(registry, id, 1)); + } else if (interface == QLatin1String("wp_viewporter")) { + mGlobals.viewporter.reset( + new WithDestructor( + registry, id, qMin(1u, version))); + } else if (interface == QLatin1String(QtWayland::wp_cursor_shape_manager_v1::interface()->name)) { + mGlobals.cursorShapeManager.reset(new WithDestructor( + registry, id, std::min(1u, version))); + } else if ( + interface == QLatin1String(QtWayland::xdg_toplevel_drag_manager_v1::interface()->name)) { + mGlobals.xdgToplevelDragManager.reset( + new WithDestructor(registry, id, 1)); + } else if (interface == QLatin1String(QtWayland::qt_windowmanager::interface()->name)) { + mGlobals.windowManagerIntegration.reset( + new QWaylandWindowManagerIntegration(this, id, version)); + } else if (interface == QLatin1String(QtWayland::xdg_system_bell_v1::interface()->name)) { + mGlobals.systemBell.reset(new WithDestructor(registry, id, 1)); + } else if ( + interface == QLatin1String(QtWayland::org_kde_kwin_appmenu_manager::interface()->name)) { + mGlobals.appMenuManager.reset(new QWaylandAppMenuManager(registry, id, 1)); +#if QT_CONFIG(clipboard) + } else if (mPreferWlrDataControl && interface == QLatin1String(QWaylandDataControlManagerV1::interface()->name)) { + mGlobals.dataControlManager.reset(new QWaylandDataControlManagerV1(this, id, 2)); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) { + inputDevice->setDataControlDevice(mGlobals.dataControlManager->createDevice(inputDevice)); + } +#endif + } else if (interface == QLatin1String(QtWayland::xx_color_manager_v4::interface()->name)) { + mGlobals.colorManager = std::make_unique(registry, id, 1); + // we need a roundtrip to receive the features the compositor supports + forceRoundTrip(); + } + + mRegistryGlobals.append(RegistryGlobal(id, interface, version, registry)); + emit globalAdded(mRegistryGlobals.back()); + + const auto copy = mRegistryListeners; // be prepared for listeners unregistering on notification + for (Listener l : copy) + (*l.listener)(l.data, registry, id, interface, version); +} + +void QWaylandDisplay::registry_global_remove(uint32_t id) +{ + for (int i = 0, ie = mRegistryGlobals.size(); i != ie; ++i) { + RegistryGlobal &global = mRegistryGlobals[i]; + if (global.id == id) { + if (global.interface == QLatin1String(QtWayland::wl_output::interface()->name)) { + for (auto *screen : mWaitingScreens) { + if (screen->outputId() == id) { + mWaitingScreens.removeOne(screen); + delete screen; + break; + } + } + + for (QWaylandScreen *screen : std::as_const(mScreens)) { + if (screen->outputId() == id) { + mScreens.removeOne(screen); + // If this is the last screen, we have to add a fake screen, or Qt will break. + ensureScreen(); + QWindowSystemInterface::handleScreenRemoved(screen); + break; + } + } + } + if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v1::interface()->name)) { + mGlobals.textInputManagerv1.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInput(nullptr); + mWaylandIntegration->reconfigureInputContext(); + } + if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name)) { + mGlobals.textInputManagerv2.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInput(nullptr); + mWaylandIntegration->reconfigureInputContext(); + } + if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v3::interface()->name)) { + mGlobals.textInputManagerv3.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInput(nullptr); + mWaylandIntegration->reconfigureInputContext(); + } + if (global.interface == QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name)) { + mGlobals.textInputMethodManager.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setTextInputMethod(nullptr); + mWaylandIntegration->reconfigureInputContext(); + } +#if QT_CONFIG(wayland_client_primary_selection) + if (global.interface == QLatin1String(QtWayland::zwp_primary_selection_device_manager_v1::interface()->name)) { + mGlobals.primarySelectionManager.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setPrimarySelectionDevice(nullptr); + } +#endif +#if QT_CONFIG(clipboard) + if (global.interface == QLatin1String(QtWayland::zwlr_data_control_manager_v1::interface()->name)) { + mGlobals.dataControlManager.reset(); + for (QWaylandInputDevice *inputDevice : std::as_const(mInputDevices)) + inputDevice->setDataControlDevice(nullptr); + } +#endif + emit globalRemoved(mRegistryGlobals.takeAt(i)); + break; + } + } +} + +bool QWaylandDisplay::hasRegistryGlobal(QStringView interfaceName) const +{ + for (const RegistryGlobal &global : mRegistryGlobals) + if (global.interface == interfaceName) + return true; + + return false; +} + +void QWaylandDisplay::addRegistryListener(RegistryListener listener, void *data) +{ + Listener l = { listener, data }; + mRegistryListeners.append(l); + for (int i = 0, ie = mRegistryGlobals.size(); i != ie; ++i) + (*l.listener)(l.data, mRegistryGlobals[i].registry, mRegistryGlobals[i].id, + mRegistryGlobals[i].interface, mRegistryGlobals[i].version); +} + +void QWaylandDisplay::removeListener(RegistryListener listener, void *data) +{ + auto iter = std::remove_if(mRegistryListeners.begin(), mRegistryListeners.end(), [=](Listener l){ + return (l.listener == listener && l.data == data); + }); + mRegistryListeners.erase(iter, mRegistryListeners.end()); +} + +void QWaylandDisplay::forceRoundTrip() +{ + wl_display_roundtrip(mDisplay); +} + +bool QWaylandDisplay::supportsWindowDecoration() const +{ + static bool disabled = qgetenv("QT_WAYLAND_DISABLE_WINDOWDECORATION").toInt(); + // Stop early when disabled via the environment. Do not try to load the integration in + // order to play nice with SHM-only, buffer integration-less systems. + if (disabled) + return false; + + // Don't initialize client buffer integration just to check whether it can have a decoration. + if (!mWaylandIntegration->mClientBufferIntegrationInitialized) + return true; + + // We can do software-rendered decorations, only disable them if the integration explicitly says it can't. + static bool integrationSupport = !clientBufferIntegration() || clientBufferIntegration()->supportsWindowDecoration(); + return integrationSupport; +} + +QWaylandWindow *QWaylandDisplay::lastInputWindow() const +{ + return mLastInputWindow.data(); +} + +void QWaylandDisplay::setLastInputDevice(QWaylandInputDevice *device, uint32_t serial, QWaylandWindow *win) +{ + mLastInputDevice = device; + mLastInputSerial = serial; + mLastInputWindow = win; +} + +bool QWaylandDisplay::isWindowActivated(const QWaylandWindow *window) +{ + return mActiveWindows.contains(const_cast(window)); +} + +void QWaylandDisplay::handleWindowActivated(QWaylandWindow *window) +{ + if (mActiveWindows.contains(window)) + return; + + mActiveWindows.append(window); + requestWaylandSync(); + + if (auto *decoration = window->decoration()) + decoration->update(); +} + +void QWaylandDisplay::handleWindowDeactivated(QWaylandWindow *window) +{ + Q_ASSERT(!mActiveWindows.empty()); + + if (mActiveWindows.last() == window) + requestWaylandSync(); + + mActiveWindows.removeOne(window); + + if (QCoreApplication::closingDown()) + return; + + if (auto *decoration = window->decoration()) + decoration->update(); +} + +void QWaylandDisplay::handleKeyboardFocusChanged(QWaylandInputDevice *inputDevice) +{ + QWaylandWindow *keyboardFocus = inputDevice->keyboardFocus(); + + if (mLastKeyboardFocus == keyboardFocus) + return; + + if (keyboardFocus) + handleWindowActivated(keyboardFocus); + if (mLastKeyboardFocus) + handleWindowDeactivated(mLastKeyboardFocus); + + mLastKeyboardFocus = keyboardFocus; +} + +void QWaylandDisplay::handleWindowDestroyed(QWaylandWindow *window) +{ + if (mActiveWindows.contains(window)) + handleWindowDeactivated(window); +} + +void QWaylandDisplay::handleWaylandSync() +{ + // This callback is used to set the window activation because we may get an activate/deactivate + // pair, and the latter one would be lost in the QWindowSystemInterface queue, if we issue the + // handleWindowActivated() calls immediately. + QWindow *activeWindow = mActiveWindows.empty() ? nullptr : mActiveWindows.last()->window(); + if (activeWindow != QGuiApplication::focusWindow()) + QWindowSystemInterface::handleFocusWindowChanged(activeWindow); + + if (!activeWindow) { + if (lastInputDevice()) { +#if QT_CONFIG(clipboard) + if (auto *dataDevice = lastInputDevice()->dataDevice()) + dataDevice->invalidateSelectionOffer(); +#endif +#if QT_CONFIG(wayland_client_primary_selection) + if (auto *device = lastInputDevice()->primarySelectionDevice()) + device->invalidateSelectionOffer(); +#endif + } + } +} + +const wl_callback_listener QWaylandDisplay::syncCallbackListener = { + [](void *data, struct wl_callback *callback, uint32_t time){ + Q_UNUSED(time); + wl_callback_destroy(callback); + QWaylandDisplay *display = static_cast(data); + display->mSyncCallback = nullptr; + display->handleWaylandSync(); + } +}; + +void QWaylandDisplay::requestWaylandSync() +{ + if (mSyncCallback) + return; + + mSyncCallback = wl_display_sync(mDisplay); + wl_callback_add_listener(mSyncCallback, &syncCallbackListener, this); +} + +QWaylandInputDevice *QWaylandDisplay::defaultInputDevice() const +{ + return mInputDevices.isEmpty() ? nullptr : mInputDevices.first(); +} + +bool QWaylandDisplay::isKeyboardAvailable() const +{ + return std::any_of( + mInputDevices.constBegin(), mInputDevices.constEnd(), + [](const QWaylandInputDevice *device) { return device->keyboard() != nullptr; }); +} + +bool QWaylandDisplay::isWaylandInputContextRequested() const { + return mWaylandInputContextRequested; +} + +#if QT_CONFIG(cursor) + +QWaylandCursor *QWaylandDisplay::waylandCursor() +{ + if (!mCursor) + mCursor.reset(mWaylandIntegration->createPlatformCursor(this)); + return mCursor.data(); +} + +auto QWaylandDisplay::findExistingCursorTheme(const QString &name, int pixelSize) const noexcept + -> FindExistingCursorThemeResult +{ + const auto byNameAndSize = [](const WaylandCursorTheme &lhs, const WaylandCursorTheme &rhs) { + return std::tie(lhs.pixelSize, lhs.name) < std::tie(rhs.pixelSize, rhs.name); + }; + + const WaylandCursorTheme prototype = {name, pixelSize, nullptr}; + + const auto it = std::lower_bound(mCursorThemes.cbegin(), mCursorThemes.cend(), prototype, byNameAndSize); + if (it != mCursorThemes.cend() && it->name == name && it->pixelSize == pixelSize) + return {it, true}; + else + return {it, false}; +} + +QWaylandCursorTheme *QWaylandDisplay::loadCursorTheme(const QString &name, int pixelSize) +{ + const auto result = findExistingCursorTheme(name, pixelSize); + if (result.found) + return result.theme(); + + if (auto theme = QWaylandCursorTheme::create(shm(), pixelSize, name)) + return mCursorThemes.insert(result.position, {name, pixelSize, std::move(theme)})->theme.get(); + + return nullptr; +} + +#endif // QT_CONFIG(cursor) + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#include "qwaylanddisplay.moc" +#include "moc_qwaylanddisplay_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylanddisplay_p.h b/src/plugins/platforms/wayland/qwaylanddisplay_p.h new file mode 100644 index 00000000000..c8ba4935cf6 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddisplay_p.h @@ -0,0 +1,399 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2024 Jie Liu +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDDISPLAY_H +#define QWAYLANDDISPLAY_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 + +#include + +#if QT_CONFIG(xkbcommon) +#include +#endif + +struct wl_cursor_image; +struct wp_viewport; + +QT_BEGIN_NAMESPACE + +#define WAYLAND_IM_KEY "wayland" + +class QAbstractEventDispatcher; +class QSocketNotifier; +class QPlatformScreen; +class QPlatformPlaceholderScreen; + +namespace QtWayland { + class zwp_text_input_manager_v1; + class zwp_text_input_manager_v2; + class zwp_text_input_manager_v3; + class qt_text_input_method_manager_v1; + class wp_cursor_shape_manager_v1; + class wp_fractional_scale_manager_v1; + class wp_viewporter; + class xdg_system_bell_v1; + class xdg_toplevel_drag_manager_v1; +} + +namespace QtWaylandClient { + +QT_DECLARE_EXPORTED_QT_LOGGING_CATEGORY(lcQpaWayland, Q_WAYLANDCLIENT_EXPORT); + +class QWaylandAppMenuManager; +class QWaylandInputDevice; +class QWaylandBuffer; +class QWaylandScreen; +class QWaylandXdgOutputManagerV1; +class QWaylandClientBufferIntegration; +class QWaylandWindowManagerIntegration; +class QWaylandDataDeviceManager; +#if QT_CONFIG(clipboard) +class QWaylandDataControlManagerV1; +#endif +#if QT_CONFIG(wayland_client_primary_selection) +class QWaylandPrimarySelectionDeviceManagerV1; +#endif +#if QT_CONFIG(tabletevent) +class QWaylandTabletManagerV2; +#endif +class QWaylandPointerGestures; +class QWaylandWindow; +class QWaylandIntegration; +class QWaylandHardwareIntegration; +class QWaylandSurface; +class QWaylandShellIntegration; +class QWaylandCursor; +class QWaylandCursorTheme; +class EventThread; +class ColorManager; + +typedef void (*RegistryListener)(void *data, + struct wl_registry *registry, + uint32_t id, + const QString &interface, + uint32_t version); + +class Q_WAYLANDCLIENT_EXPORT QWaylandDisplay : public QObject, public QtWayland::wl_registry { + Q_OBJECT + +public: + QWaylandDisplay(QWaylandIntegration *waylandIntegration); + ~QWaylandDisplay(void) override; + + bool initialize(); + +#if QT_CONFIG(xkbcommon) + struct xkb_context *xkbContext() const { return mXkbContext.get(); } +#endif + + QList screens() const { return mScreens; } + QPlatformPlaceholderScreen *placeholderScreen() const { return mPlaceholderScreen; } + void ensureScreen(); + + QWaylandScreen *screenForOutput(struct wl_output *output) const; + void handleScreenInitialized(QWaylandScreen *screen); + + struct wl_surface *createSurface(void *handle); + struct ::wl_region *createRegion(const QRegion &qregion); + struct ::wl_subsurface *createSubSurface(QWaylandWindow *window, QWaylandWindow *parent); + struct ::wp_viewport *createViewport(QWaylandWindow *window); + + QWaylandShellIntegration *shellIntegration() const; + QWaylandClientBufferIntegration *clientBufferIntegration() const; + QWaylandWindowManagerIntegration *windowManagerIntegration() const; + +#if QT_CONFIG(cursor) + QWaylandCursor *waylandCursor(); + QWaylandCursorTheme *loadCursorTheme(const QString &name, int pixelSize); +#endif + struct wl_display *wl_display() const + { + return mDisplay; + } + struct ::wl_registry *wl_registry() { return object(); } + + QtWayland::wl_compositor *compositor() + { + return mGlobals.compositor.get(); + } + + QList inputDevices() const { return mInputDevices; } + QWaylandInputDevice *defaultInputDevice() const; + QWaylandInputDevice *currentInputDevice() const { return defaultInputDevice(); } +#if QT_CONFIG(wayland_datadevice) + QWaylandDataDeviceManager *dndSelectionHandler() const + { + return mGlobals.dndSelectionHandler.get(); + } +#endif +#if QT_CONFIG(clipboard) + QWaylandDataControlManagerV1 *dataControlManager() const + { + return mGlobals.dataControlManager.get(); + } +#endif +#if QT_CONFIG(wayland_client_primary_selection) + QWaylandPrimarySelectionDeviceManagerV1 *primarySelectionManager() const + { + return mGlobals.primarySelectionManager.get(); + } +#endif +#if QT_CONFIG(tabletevent) + QWaylandTabletManagerV2 *tabletManager() const + { + return mGlobals.tabletManager.get(); + } +#endif + QWaylandPointerGestures *pointerGestures() const + { + return mGlobals.pointerGestures.get(); + } + QtWayland::qt_text_input_method_manager_v1 *textInputMethodManager() const + { + return mGlobals.textInputMethodManager.get(); + } + QtWayland::zwp_text_input_manager_v1 *textInputManagerv1() const + { + return mGlobals.textInputManagerv1.get(); + } + QtWayland::zwp_text_input_manager_v2 *textInputManagerv2() const + { + return mGlobals.textInputManagerv2.get(); + } + QtWayland::zwp_text_input_manager_v3 *textInputManagerv3() const + { + return mGlobals.textInputManagerv3.get(); + } + QWaylandHardwareIntegration *hardwareIntegration() const + { + return mGlobals.hardwareIntegration.get(); + } + QWaylandXdgOutputManagerV1 *xdgOutputManager() const + { + return mGlobals.xdgOutputManager.get(); + } + QtWayland::wp_fractional_scale_manager_v1 *fractionalScaleManager() const + { + return mGlobals.fractionalScaleManager.get(); + } + QtWayland::wp_viewporter *viewporter() const + { + return mGlobals.viewporter.get(); + } + QtWayland::wp_cursor_shape_manager_v1 *cursorShapeManager() const + { + return mGlobals.cursorShapeManager.get(); + } + QtWayland::xdg_toplevel_drag_manager_v1 *xdgToplevelDragManager() const + { + return mGlobals.xdgToplevelDragManager.get(); + } + QtWayland::xdg_system_bell_v1 *systemBell() const + { + return mGlobals.systemBell.get(); + } + QWaylandAppMenuManager *appMenuManager() const + { + return mGlobals.appMenuManager.get(); + } + ColorManager *colorManager() const + { + return mGlobals.colorManager.get(); + } + + struct RegistryGlobal { + uint32_t id; + QString interface; + uint32_t version; + struct ::wl_registry *registry = nullptr; + RegistryGlobal(uint32_t id_, const QString &interface_, uint32_t version_, struct ::wl_registry *registry_) + : id(id_), interface(interface_), version(version_), registry(registry_) { } + }; + QList globals() const + { + return mRegistryGlobals; + } + bool hasRegistryGlobal(QStringView interfaceName) const; + + /* wl_registry_add_listener does not add but rather sets a listener, so this function is used + * to enable many listeners at once. */ + void addRegistryListener(RegistryListener listener, void *data); + void removeListener(RegistryListener listener, void *data); + + QWaylandShm *shm() const + { + return mGlobals.shm.get(); + } + + void forceRoundTrip(); + + bool supportsWindowDecoration() const; + + uint32_t lastInputSerial() const { return mLastInputSerial; } + QWaylandInputDevice *lastInputDevice() const { return mLastInputDevice; } + QWaylandWindow *lastInputWindow() const; + void setLastInputDevice(QWaylandInputDevice *device, uint32_t serial, QWaylandWindow *window); + + bool isWindowActivated(const QWaylandWindow *window); + void handleWindowActivated(QWaylandWindow *window); + void handleWindowDeactivated(QWaylandWindow *window); + void handleKeyboardFocusChanged(QWaylandInputDevice *inputDevice); + void handleWindowDestroyed(QWaylandWindow *window); + + wl_event_queue *frameEventQueue() { return m_frameEventQueue; }; + + bool isKeyboardAvailable() const; + bool isWaylandInputContextRequested() const; + + void initEventThread(); + +public Q_SLOTS: + void flushRequests(); + +Q_SIGNALS: + void connected(); + void globalAdded(const RegistryGlobal &global); + void globalRemoved(const RegistryGlobal &global); + +private: + void checkWaylandError(); + void reconnect(); + void setupConnection(); + void handleWaylandSync(); + void requestWaylandSync(); + + void checkTextInputProtocol(); + + struct Listener { + Listener() = default; + Listener(RegistryListener incomingListener, + void* incomingData) + : listener(incomingListener), data(incomingData) + {} + RegistryListener listener = nullptr; + void *data = nullptr; + }; + struct wl_display *mDisplay = nullptr; + std::unique_ptr m_eventThread; + wl_event_queue *m_frameEventQueue = nullptr; + QScopedPointer m_frameEventQueueThread; + QList mWaitingScreens; + QList mScreens; + QPlatformPlaceholderScreen *mPlaceholderScreen = nullptr; + QList mInputDevices; + QList mRegistryListeners; + QWaylandIntegration *mWaylandIntegration = nullptr; +#if QT_CONFIG(cursor) + struct WaylandCursorTheme { + QString name; + int pixelSize; + std::unique_ptr theme; + }; + std::vector mCursorThemes; + + struct FindExistingCursorThemeResult { + std::vector::const_iterator position; + bool found; + + QWaylandCursorTheme *theme() const noexcept + { return found ? position->theme.get() : nullptr; } + }; + FindExistingCursorThemeResult findExistingCursorTheme(const QString &name, + int pixelSize) const noexcept; + QScopedPointer mCursor; +#endif + + struct GlobalHolder + { + std::unique_ptr compositor; + std::unique_ptr shm; +#if QT_CONFIG(wayland_datadevice) + std::unique_ptr dndSelectionHandler; +#endif + std::unique_ptr subCompositor; +#if QT_CONFIG(tabletevent) + std::unique_ptr tabletManager; +#endif + std::unique_ptr pointerGestures; +#if QT_CONFIG(clipboard) + std::unique_ptr dataControlManager; +#endif +#if QT_CONFIG(wayland_client_primary_selection) + std::unique_ptr primarySelectionManager; +#endif + std::unique_ptr textInputMethodManager; + std::unique_ptr textInputManagerv1; + std::unique_ptr textInputManagerv2; + std::unique_ptr textInputManagerv3; + std::unique_ptr hardwareIntegration; + std::unique_ptr xdgOutputManager; + std::unique_ptr viewporter; + std::unique_ptr fractionalScaleManager; + std::unique_ptr cursorShapeManager; + std::unique_ptr systemBell; + std::unique_ptr xdgToplevelDragManager; + std::unique_ptr windowManagerIntegration; + std::unique_ptr appMenuManager; + std::unique_ptr colorManager; + } mGlobals; + + int mFd = -1; + int mWritableNotificationFd = -1; + QList mRegistryGlobals; + uint32_t mLastInputSerial = 0; + QWaylandInputDevice *mLastInputDevice = nullptr; + QPointer mLastInputWindow; + QPointer mLastKeyboardFocus; + QList mActiveWindows; + struct wl_callback *mSyncCallback = nullptr; + static const wl_callback_listener syncCallbackListener; + bool mWaylandTryReconnect = false; + bool mPreferWlrDataControl = false; + + bool mWaylandInputContextRequested = [] () { + const auto requested = QPlatformInputContextFactory::requested(); + return requested.isEmpty() || requested.contains(QLatin1String(WAYLAND_IM_KEY)); + }(); + QStringList mTextInputManagerList; + int mTextInputManagerIndex = INT_MAX; + + void registry_global(uint32_t id, const QString &interface, uint32_t version) override; + void registry_global_remove(uint32_t id) override; + +#if QT_CONFIG(xkbcommon) + QXkbCommon::ScopedXKBContext mXkbContext; +#endif + + friend class QWaylandIntegration; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDDISPLAY_H diff --git a/src/plugins/platforms/wayland/qwaylanddnd.cpp b/src/plugins/platforms/wayland/qwaylanddnd.cpp new file mode 100644 index 00000000000..096d7e5b586 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddnd.cpp @@ -0,0 +1,114 @@ +// Copyright (C) 2016 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 + +#include "qwaylanddnd_p.h" + +#include "qwaylanddatadevice_p.h" +#include "qwaylanddatadevicemanager_p.h" +#include "qwaylanddataoffer_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylanddisplay_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE +#if QT_CONFIG(draganddrop) +namespace QtWaylandClient { + +QWaylandDrag::QWaylandDrag(QWaylandDisplay *display) + : m_display(display) +{ +} + +QWaylandDrag::~QWaylandDrag() +{ +} + +void QWaylandDrag::startDrag() +{ + // Some compositors do not send a pointer leave before starting a drag, some do. + // This is discussed upstream at: https://gitlab.freedesktop.org/wayland/wayland/-/issues/444 + // For consistency between compositors we emit the leave event here, upon drag start. + m_display->currentInputDevice()->handleStartDrag(); + + QBasicDrag::startDrag(); + QWaylandWindow *icon = static_cast(shapedPixmapWindow()->handle()); + if (m_display->currentInputDevice()->dataDevice()->startDrag(drag()->mimeData(), drag()->supportedActions(), icon)) { + icon->addAttachOffset(-drag()->hotSpot()); + } else { + // Cancelling immediately does not work, since the event loop for QDrag::exec is started + // after this function returns. + QMetaObject::invokeMethod(this, [this](){ cancelDrag(); }, Qt::QueuedConnection); + } +} + +void QWaylandDrag::cancel() +{ + QBasicDrag::cancel(); + + m_display->currentInputDevice()->dataDevice()->cancelDrag(); + + if (drag()) + drag()->deleteLater(); +} + +void QWaylandDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) +{ + Q_UNUSED(globalPos); + Q_UNUSED(b); + Q_UNUSED(mods); + // Do nothing +} + +void QWaylandDrag::drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) +{ + QBasicDrag::drop(globalPos, b, mods); +} + +void QWaylandDrag::endDrag() +{ + m_display->currentInputDevice()->handleEndDrag(); +} + +void QWaylandDrag::setResponse(bool accepted) +{ + // This method is used for old DataDevices where the drag action is not communicated + Qt::DropAction action = defaultAction(drag()->supportedActions(), m_display->currentInputDevice()->modifiers()); + setResponse(QPlatformDropQtResponse(accepted, action)); +} + +void QWaylandDrag::setResponse(const QPlatformDropQtResponse &response) +{ + setCanDrop(response.isAccepted()); + + if (canDrop()) { + updateCursor(response.acceptedAction()); + } else { + updateCursor(Qt::IgnoreAction); + } +} + +void QWaylandDrag::setDropResponse(const QPlatformDropQtResponse &response) +{ + setExecutedDropAction(response.acceptedAction()); +} + +void QWaylandDrag::finishDrag() +{ + QKeyEvent event(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); + eventFilter(shapedPixmapWindow(), &event); + + if (drag()) + drag()->deleteLater(); +} + +bool QWaylandDrag::ownsDragObject() const +{ + return true; +} + +} +#endif // draganddrop +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylanddnd_p.h b/src/plugins/platforms/wayland/qwaylanddnd_p.h new file mode 100644 index 00000000000..4952c6d3d39 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddnd_p.h @@ -0,0 +1,60 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDDND_H +#define QWAYLANDDND_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; +#if QT_CONFIG(draganddrop) +class Q_WAYLANDCLIENT_EXPORT QWaylandDrag : public QBasicDrag +{ +public: + QWaylandDrag(QWaylandDisplay *display); + ~QWaylandDrag() override; + + void setResponse(bool accepted); + void setResponse(const QPlatformDropQtResponse &response); + void setDropResponse(const QPlatformDropQtResponse &response); + void finishDrag(); + +protected: + void startDrag() override; + void cancel() override; + void move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) override; + void drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) override; + void endDrag() override; + + bool ownsDragObject() const override; + +private: + QWaylandDisplay *m_display = nullptr; +}; +#endif +} + +QT_END_NAMESPACE + +#endif // QWAYLANDDND_H diff --git a/src/plugins/platforms/wayland/qwaylandfractionalscale.cpp b/src/plugins/platforms/wayland/qwaylandfractionalscale.cpp new file mode 100644 index 00000000000..6cd933f4707 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandfractionalscale.cpp @@ -0,0 +1,31 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandfractionalscale_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandFractionalScale::QWaylandFractionalScale(struct ::wp_fractional_scale_v1 *object) + : QtWayland::wp_fractional_scale_v1(object) +{} + + +QWaylandFractionalScale::~QWaylandFractionalScale() +{ + destroy(); +} + +void QWaylandFractionalScale::wp_fractional_scale_v1_preferred_scale(uint scale) +{ + qreal preferredScale = scale / 120.0; // hardcoded denominator determined in the spec + if (preferredScale != mPreferredScale) { + mPreferredScale = preferredScale; + Q_EMIT preferredScaleChanged(); + } +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandfractionalscale_p.h b/src/plugins/platforms/wayland/qwaylandfractionalscale_p.h new file mode 100644 index 00000000000..48e1fb4877e --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandfractionalscale_p.h @@ -0,0 +1,52 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDFRACTIONALSCALE_P_H +#define QWAYLANDFRACTIONALSCALE_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandFractionalScale : public QObject, public QtWayland::wp_fractional_scale_v1 +{ + Q_OBJECT +public: + explicit QWaylandFractionalScale(struct ::wp_fractional_scale_v1 *object); + ~QWaylandFractionalScale(); + + std::optional preferredScale() const { return mPreferredScale; } + +Q_SIGNALS: + void preferredScaleChanged(); + +protected: + void wp_fractional_scale_v1_preferred_scale(uint scale) override; + +private: + std::optional mPreferredScale; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/qwaylandinputcontext.cpp b/src/plugins/platforms/wayland/qwaylandinputcontext.cpp new file mode 100644 index 00000000000..0ccc4dba57a --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandinputcontext.cpp @@ -0,0 +1,310 @@ +// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + +#include "qwaylandinputcontext_p.h" + +#include +#include +#include +#include + +#include "qwaylanddisplay_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylandwindow_p.h" + +#if QT_CONFIG(xkbcommon) +#include +#endif + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcQpaInputMethods, "qt.qpa.input.methods") + +namespace QtWaylandClient { + +QWaylandInputContext::QWaylandInputContext(QWaylandDisplay *display) + : mDisplay(display) +{ +} + +QWaylandInputContext::~QWaylandInputContext() +{ +} + +bool QWaylandInputContext::isValid() const +{ + return mDisplay->textInputManagerv2() != nullptr || mDisplay->textInputManagerv1() != nullptr || mDisplay->textInputManagerv3() != nullptr; +} + +void QWaylandInputContext::reset() +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; +#if QT_CONFIG(xkbcommon) + if (m_composeState) + xkb_compose_state_reset(m_composeState); +#endif + + QPlatformInputContext::reset(); + + QWaylandTextInputInterface *inputInterface = textInput(); + if (!inputInterface) + return; + + inputInterface->reset(); +} + +void QWaylandInputContext::commit() +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + QWaylandTextInputInterface *inputInterface = textInput(); + if (!inputInterface) + return; + + inputInterface->commit(); +} + +static ::wl_surface *surfaceForWindow(QWindow *window) +{ + if (!window || !window->handle()) + return nullptr; + + auto *waylandWindow = static_cast(window->handle()); + return waylandWindow->wlSurface(); +} + +void QWaylandInputContext::update(Qt::InputMethodQueries queries) +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO << queries; + + QWaylandTextInputInterface *inputInterface = textInput(); + if (!QGuiApplication::focusObject() || !inputInterface) + return; + + auto *currentSurface = surfaceForWindow(mCurrentWindow); + + if (currentSurface && !inputMethodAccepted()) { + inputInterface->disableSurface(currentSurface); + mCurrentWindow.clear(); + } else if (!currentSurface && inputMethodAccepted()) { + QWindow *window = QGuiApplication::focusWindow(); + if (auto *focusSurface = surfaceForWindow(window)) { + inputInterface->enableSurface(focusSurface); + mCurrentWindow = window; + } + } + + inputInterface->updateState(queries, QWaylandTextInputInterface::update_state_change); +} + +void QWaylandInputContext::invokeAction(QInputMethod::Action action, int cursorPostion) +{ + QWaylandTextInputInterface *inputInterface = textInput(); + if (!inputInterface) + return; + + if (action == QInputMethod::Click) + inputInterface->setCursorInsidePreedit(cursorPostion); +} + +void QWaylandInputContext::showInputPanel() +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + QWaylandTextInputInterface *inputInterface = textInput(); + if (!inputInterface) + return; + + inputInterface->showInputPanel(); +} + +void QWaylandInputContext::hideInputPanel() +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + QWaylandTextInputInterface *inputInterface = textInput(); + if (!inputInterface) + return; + + inputInterface->hideInputPanel(); +} + +bool QWaylandInputContext::isInputPanelVisible() const +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + QWaylandTextInputInterface *inputInterface = textInput(); + if (!inputInterface) + return QPlatformInputContext::isInputPanelVisible(); + + return inputInterface->isInputPanelVisible(); +} + +QRectF QWaylandInputContext::keyboardRect() const +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + QWaylandTextInputInterface *inputInterface = textInput(); + if (!inputInterface) + return QPlatformInputContext::keyboardRect(); + + return inputInterface->keyboardRect(); +} + +QLocale QWaylandInputContext::locale() const +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + QWaylandTextInputInterface *inputInterface = textInput(); + if (!inputInterface) + return QPlatformInputContext::locale(); + + return inputInterface->locale(); +} + +Qt::LayoutDirection QWaylandInputContext::inputDirection() const +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + + QWaylandTextInputInterface *inputInterface = textInput(); + if (!inputInterface) + return QPlatformInputContext::inputDirection(); + + return inputInterface->inputDirection(); +} + +void QWaylandInputContext::setFocusObject(QObject *object) +{ + qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; +#if QT_CONFIG(xkbcommon) + m_focusObject = object; +#else + Q_UNUSED(object); +#endif + + QWaylandTextInputInterface *inputInterface = textInput(); + if (!inputInterface) + return; + + QWindow *window = QGuiApplication::focusWindow(); + + if (window && window->handle()) { + if (mCurrentWindow.data() != window) { + if (!inputMethodAccepted()) { + auto *surface = static_cast(window->handle())->wlSurface(); + if (surface) + inputInterface->disableSurface(surface); + mCurrentWindow.clear(); + } else { + auto *surface = static_cast(window->handle())->wlSurface(); + if (surface) { + inputInterface->enableSurface(surface); + mCurrentWindow = window; + } else { + mCurrentWindow.clear(); + } + } + } + if (mCurrentWindow) + inputInterface->updateState(Qt::ImQueryAll, QWaylandTextInputInterface::update_state_enter); + return; + } + + if (mCurrentWindow) + mCurrentWindow.clear(); +} + +QWaylandTextInputInterface *QWaylandInputContext::textInput() const +{ + return mDisplay->defaultInputDevice() ? mDisplay->defaultInputDevice()->textInput() : nullptr; +} + +#if QT_CONFIG(xkbcommon) + +void QWaylandInputContext::ensureInitialized() +{ + if (m_initialized) + return; + + if (!m_XkbContext) { + qCWarning(qLcQpaInputMethods) << "error: xkb context has not been set on" << metaObject()->className(); + return; + } + + m_initialized = true; + const char *const locale = setlocale(LC_CTYPE, nullptr); + qCDebug(qLcQpaInputMethods) << "detected locale (LC_CTYPE):" << locale; + + m_composeTable = xkb_compose_table_new_from_locale(m_XkbContext, locale, XKB_COMPOSE_COMPILE_NO_FLAGS); + if (m_composeTable) + m_composeState = xkb_compose_state_new(m_composeTable, XKB_COMPOSE_STATE_NO_FLAGS); + + if (!m_composeTable) { + qCWarning(qLcQpaInputMethods, "failed to create compose table"); + return; + } + if (!m_composeState) { + qCWarning(qLcQpaInputMethods, "failed to create compose state"); + return; + } +} + +bool QWaylandInputContext::filterEvent(const QEvent *event) +{ + auto keyEvent = static_cast(event); + if (keyEvent->type() != QEvent::KeyPress) + return false; + + if (!inputMethodAccepted()) + return false; + + // lazy initialization - we don't want to do this on an app startup + ensureInitialized(); + + if (!m_composeTable || !m_composeState) + return false; + + xkb_compose_state_feed(m_composeState, keyEvent->nativeVirtualKey()); + + switch (xkb_compose_state_get_status(m_composeState)) { + case XKB_COMPOSE_COMPOSING: + return true; + case XKB_COMPOSE_CANCELLED: + reset(); + return false; + case XKB_COMPOSE_COMPOSED: + { + const int size = xkb_compose_state_get_utf8(m_composeState, nullptr, 0); + QVarLengthArray buffer(size + 1); + xkb_compose_state_get_utf8(m_composeState, buffer.data(), buffer.size()); + QString composedText = QString::fromUtf8(buffer.constData()); + + QInputMethodEvent event; + event.setCommitString(composedText); + + if (!m_focusObject && qApp) + m_focusObject = qApp->focusObject(); + + if (m_focusObject) + QCoreApplication::sendEvent(m_focusObject, &event); + else + qCWarning(qLcQpaInputMethods, "no focus object"); + + reset(); + return true; + } + case XKB_COMPOSE_NOTHING: + return false; + default: + Q_UNREACHABLE_RETURN(false); + } +} + +#endif + +} + +QT_END_NAMESPACE + +#include "moc_qwaylandinputcontext_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylandinputcontext_p.h b/src/plugins/platforms/wayland/qwaylandinputcontext_p.h new file mode 100644 index 00000000000..0076f20cd82 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandinputcontext_p.h @@ -0,0 +1,94 @@ +// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + +#ifndef QWAYLANDINPUTCONTEXT_H +#define QWAYLANDINPUTCONTEXT_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 "qwaylandtextinputinterface_p.h" +#include +#if QT_CONFIG(xkbcommon) +#include +#endif + +struct wl_callback; +struct wl_callback_listener; + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcQpaInputMethods) + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class QWaylandInputContext : public QPlatformInputContext +{ + Q_OBJECT +public: + explicit QWaylandInputContext(QWaylandDisplay *display); + ~QWaylandInputContext() override; + + bool isValid() const override; + + void reset() override; + void commit() override; + void update(Qt::InputMethodQueries) override; + + void invokeAction(QInputMethod::Action, int cursorPosition) override; + + void showInputPanel() override; + void hideInputPanel() override; + bool isInputPanelVisible() const override; + QRectF keyboardRect() const override; + + QLocale locale() const override; + Qt::LayoutDirection inputDirection() const override; + + void setFocusObject(QObject *object) override; + +#if QT_CONFIG(xkbcommon) + bool filterEvent(const QEvent *event) override; + + // This invokable is called from QXkbCommon::setXkbContext(). + Q_INVOKABLE void setXkbContext(struct xkb_context *context) { m_XkbContext = context; } +#endif + +private: + QWaylandTextInputInterface *textInput() const; + + QWaylandDisplay *mDisplay = nullptr; + QPointer mCurrentWindow; + +#if QT_CONFIG(xkbcommon) + void ensureInitialized(); + + bool m_initialized = false; + QObject *m_focusObject = nullptr; + xkb_compose_table *m_composeTable = nullptr; + xkb_compose_state *m_composeState = nullptr; + struct xkb_context *m_XkbContext = nullptr; +#endif +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDINPUTCONTEXT_H diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp new file mode 100644 index 00000000000..fea7e77414c --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp @@ -0,0 +1,1574 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2024 Jie Liu +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandinputdevice_p.h" + +#include "qwaylandintegration_p.h" +#include "qwaylandtextinputv3_p.h" +#include "qwaylandwindow_p.h" +#include "qwaylandsurface_p.h" +#include "qwaylandbuffer_p.h" +#if QT_CONFIG(wayland_datadevice) +#include "qwaylanddatadevice_p.h" +#include "qwaylanddatadevicemanager_p.h" +#endif +#if QT_CONFIG(clipboard) +#include "qwaylanddatacontrolv1_p.h" +#endif +#if QT_CONFIG(wayland_client_primary_selection) +#include "qwaylandprimaryselectionv1_p.h" +#endif +#if QT_CONFIG(tabletevent) +#include "qwaylandtabletv2_p.h" +#endif +#include "qwaylandpointergestures_p.h" +#include "qwaylandscreen_p.h" +#include "qwaylandcursor_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandshmbackingstore_p.h" +#include "qwaylandtextinputv1_p.h" +#include "qwaylandtextinputv2_p.h" +#include "qwaylandtextinputinterface_p.h" +#include "qwaylandinputcontext_p.h" +#include "qwaylandinputmethodcontext_p.h" +#include "qwaylandcallback_p.h" +#include "qwaylandcursorsurface_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if QT_CONFIG(cursor) +#include +#endif + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +Q_LOGGING_CATEGORY(lcQpaWaylandInput, "qt.qpa.wayland.input"); + +// The maximum number of concurrent touchpoints is not exposed in wayland, so we assume a +// reasonable number of them. As of 2021 most touchscreen panels support 10 concurrent touchpoints. +static const int MaxTouchPoints = 10; + +QWaylandInputDevice::Keyboard::Keyboard(QWaylandInputDevice *p) + : mParent(p) +{ + init(p->get_keyboard()); + mRepeatTimer.callOnTimeout(this, [&]() { + if (!focusWindow()) { + // We destroyed the keyboard focus surface, but the server didn't get the message yet... + // or the server didn't send an enter event first. + return; + } + mRepeatTimer.setInterval(1000 / mRepeatRate); + Qt::KeyboardModifiers modifiers = this->modifiers(); + handleKey(mRepeatKey.time, QEvent::KeyRelease, mRepeatKey.key, modifiers, + mRepeatKey.code, mRepeatKey.nativeVirtualKey, this->mNativeModifiers, + mRepeatKey.text, true); + handleKey(mRepeatKey.time, QEvent::KeyPress, mRepeatKey.key, modifiers, + mRepeatKey.code, mRepeatKey.nativeVirtualKey, this->mNativeModifiers, + mRepeatKey.text, true); + }); +} + +#if QT_CONFIG(xkbcommon) +bool QWaylandInputDevice::Keyboard::createDefaultKeymap() +{ + struct xkb_context *ctx = mParent->mQDisplay->xkbContext(); + if (!ctx) + return false; + + struct xkb_rule_names names; + names.rules = "evdev"; + names.model = "pc105"; + names.layout = "us"; + names.variant = ""; + names.options = ""; + + mXkbKeymap.reset(xkb_keymap_new_from_names(ctx, &names, XKB_KEYMAP_COMPILE_NO_FLAGS)); + if (mXkbKeymap) + mXkbState.reset(xkb_state_new(mXkbKeymap.get())); + + if (!mXkbKeymap || !mXkbState) { + qCWarning(lcQpaWayland, "failed to create default keymap"); + return false; + } + + return true; +} +#endif + +QWaylandInputDevice::Keyboard::~Keyboard() +{ + if (mFocus) + QWindowSystemInterface::handleFocusWindowChanged(nullptr); + if (version() >= 3) + wl_keyboard_release(object()); + else + wl_keyboard_destroy(object()); +} + +QWaylandWindow *QWaylandInputDevice::Keyboard::focusWindow() const +{ + return mFocus ? mFocus->waylandWindow() : nullptr; +} + +QWaylandInputDevice::Pointer::Pointer(QWaylandInputDevice *seat) + : mParent(seat) +{ + init(seat->get_pointer()); +#if QT_CONFIG(cursor) + if (auto cursorShapeManager = seat->mQDisplay->cursorShapeManager()) { + mCursor.shape.reset(new QWaylandCursorShape(cursorShapeManager->get_pointer(object()))); + } + + mCursor.frameTimer.setSingleShot(true); + mCursor.frameTimer.callOnTimeout(this, [&]() { + cursorTimerCallback(); + }); +#endif +} + +QWaylandInputDevice::Pointer::~Pointer() +{ + if (version() >= 3) + wl_pointer_release(object()); + else + wl_pointer_destroy(object()); +} + +QWaylandWindow *QWaylandInputDevice::Pointer::focusWindow() const +{ + return mFocus ? mFocus->waylandWindow() : nullptr; +} + +#if QT_CONFIG(cursor) + +int QWaylandInputDevice::Pointer::idealCursorScale() const +{ + if (seat()->mQDisplay->compositor()->version() < 3) { + return 1; + } + + if (auto *s = mCursor.surface.data()) { + if (s->outputScale() > 0) + return s->outputScale(); + } + + return seat()->mCursor.fallbackOutputScale; +} + +void QWaylandInputDevice::Pointer::updateCursorTheme() +{ + QString cursorThemeName; + QSize cursorSize; + + if (const QPlatformTheme *platformTheme = QGuiApplicationPrivate::platformTheme()) { + cursorThemeName = platformTheme->themeHint(QPlatformTheme::MouseCursorTheme).toString(); + cursorSize = platformTheme->themeHint(QPlatformTheme::MouseCursorSize).toSize(); + } + + if (cursorThemeName.isEmpty()) + cursorThemeName = QStringLiteral("default"); + if (cursorSize.isEmpty()) + cursorSize = QSize(24, 24); + + int scale = idealCursorScale(); + int pixelSize = cursorSize.width() * scale; + auto *display = seat()->mQDisplay; + mCursor.theme = display->loadCursorTheme(cursorThemeName, pixelSize); + + if (!mCursor.theme) + return; // A warning has already been printed in loadCursorTheme + + if (auto *arrow = mCursor.theme->cursor(Qt::ArrowCursor)) { + int arrowPixelSize = qMax(arrow->images[0]->width, arrow->images[0]->height); // Not all cursor themes are square + while (scale > 1 && arrowPixelSize / scale < cursorSize.width()) + --scale; + } else { + qCWarning(lcQpaWayland) << "Cursor theme does not support the arrow cursor"; + } + mCursor.themeBufferScale = scale; +} + +void QWaylandInputDevice::Pointer::updateCursor() +{ + if (mEnterSerial == 0) + return; + + auto shape = seat()->mCursor.shape; + + if (shape == Qt::BlankCursor) { + if (mCursor.surface) + mCursor.surface->reset(); + set_cursor(mEnterSerial, nullptr, 0, 0); + return; + } + + if (shape == Qt::BitmapCursor) { + auto buffer = seat()->mCursor.bitmapBuffer; + if (!buffer) { + qCWarning(lcQpaWayland) << "No buffer for bitmap cursor, can't set cursor"; + return; + } + auto hotspot = seat()->mCursor.hotspot; + int bufferScale = seat()->mCursor.bitmapScale; + getOrCreateCursorSurface()->update(buffer->buffer(), hotspot, buffer->size(), bufferScale); + return; + } + + if (mCursor.shape) { + if (mCursor.surface) { + mCursor.surface->reset(); + } + mCursor.shape->setShape(mEnterSerial, shape); + return; + } + + if (!mCursor.theme || idealCursorScale() != mCursor.themeBufferScale) + updateCursorTheme(); + + if (!mCursor.theme) + return; + + // Set from shape using theme + const QElapsedTimer &timer = seat()->mCursor.animationTimer; + const uint time = timer.isValid() ? timer.elapsed() : 0; + + if (struct ::wl_cursor *waylandCursor = mCursor.theme->cursor(shape)) { + uint duration = 0; + int frame = wl_cursor_frame_and_duration(waylandCursor, time, &duration); + ::wl_cursor_image *image = waylandCursor->images[frame]; + + struct wl_buffer *buffer = wl_cursor_image_get_buffer(image); + if (!buffer) { + qCWarning(lcQpaWayland) << "Could not find buffer for cursor" << shape; + return; + } + int bufferScale = mCursor.themeBufferScale; + QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale; + QSize size = QSize(image->width, image->height) / bufferScale; + bool animated = duration > 0; + if (animated) { + mCursor.gotFrameCallback = false; + mCursor.gotTimerCallback = false; + mCursor.frameTimer.start(duration); + } + getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale, animated); + return; + } + + qCWarning(lcQpaWayland) << "Unable to change to cursor" << shape; +} + +CursorSurface * +QWaylandInputDevice::Pointer::getOrCreateCursorSurface() +{ + if (!mCursor.surface) + mCursor.surface.reset(new CursorSurface(this, seat()->mQDisplay)); + return mCursor.surface.get(); +} + +void QWaylandInputDevice::Pointer::cursorTimerCallback() +{ + mCursor.gotTimerCallback = true; + if (mCursor.gotFrameCallback) { + updateCursor(); + } +} + +void QWaylandInputDevice::Pointer::cursorFrameCallback() +{ + mCursor.gotFrameCallback = true; + if (mCursor.gotTimerCallback) { + updateCursor(); + } +} + +#endif // QT_CONFIG(cursor) + +QWaylandInputDevice::Touch::Touch(QWaylandInputDevice *p) + : mParent(p) +{ + init(p->get_touch()); +} + +QWaylandInputDevice::Touch::~Touch() +{ + if (version() >= 3) + wl_touch_release(object()); + else + wl_touch_destroy(object()); +} + +QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, uint32_t id) + : QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 9)) + , mQDisplay(display) + , mDisplay(display->wl_display()) + , mId(id) +{ + +#if QT_CONFIG(clipboard) + if (auto *dataControlManager = mQDisplay->dataControlManager()) { + setDataControlDevice(dataControlManager->createDevice(this)); + } +#endif + +#if QT_CONFIG(wayland_datadevice) + if (mQDisplay->dndSelectionHandler()) { + mDataDevice = mQDisplay->dndSelectionHandler()->getDataDevice(this); + } +#endif + +#if QT_CONFIG(wayland_client_primary_selection) + // TODO: Could probably decouple this more if there was a signal for new seat added + if (auto *psm = mQDisplay->primarySelectionManager()) + setPrimarySelectionDevice(psm->createDevice(this)); +#endif + + if (mQDisplay->textInputManagerv1()) { + auto textInput = new QWaylandTextInputv1(mQDisplay, mQDisplay->textInputManagerv1()->create_text_input()); + textInput->setSeat(wl_seat()); + mTextInput.reset(textInput); + } + + if (mQDisplay->textInputManagerv2()) + mTextInput.reset(new QWaylandTextInputv2(mQDisplay, mQDisplay->textInputManagerv2()->get_text_input(wl_seat()))); + + if (mQDisplay->textInputManagerv3()) + mTextInput.reset(new QWaylandTextInputv3(mQDisplay, mQDisplay->textInputManagerv3()->get_text_input(wl_seat()))); + + if (mQDisplay->textInputMethodManager()) + mTextInputMethod.reset(new QWaylandTextInputMethod(mQDisplay, mQDisplay->textInputMethodManager()->get_text_input_method(wl_seat()))); + +#if QT_CONFIG(tabletevent) + if (auto *tm = mQDisplay->tabletManager()) + mTabletSeat.reset(new QWaylandTabletSeatV2(tm, this)); +#endif +} + +QWaylandInputDevice::~QWaylandInputDevice() +{ + if (version() >= WL_SEAT_RELEASE_SINCE_VERSION) + release(); + else + wl_seat_destroy(object()); +} + +void QWaylandInputDevice::seat_capabilities(uint32_t caps) +{ + mCaps = caps; + maybeRegisterInputDevices(); +} + +void QWaylandInputDevice::seat_name(const QString &name) +{ + mSeatName = name; + mSeatNameKnown = true; + maybeRegisterInputDevices(); +} + +void QWaylandInputDevice::maybeRegisterInputDevices() +{ + if (!mSeatNameKnown) + return; // too early + + if (mCaps & WL_SEAT_CAPABILITY_KEYBOARD && !mKeyboard) { + mKeyboard.reset(createKeyboard(this)); + } else if (!(mCaps & WL_SEAT_CAPABILITY_KEYBOARD) && mKeyboard) { + mKeyboard.reset(); + } + + if (mCaps & WL_SEAT_CAPABILITY_POINTER && !mPointer) { + mPointer.reset(createPointer(this)); + + auto *pointerGestures = mQDisplay->pointerGestures(); + if (pointerGestures) { + // NOTE: The name of the device and its system ID are not exposed on Wayland. + mTouchPadDevice = new QPointingDevice( + QLatin1StringView("touchpad"), 0, QInputDevice::DeviceType::TouchPad, + QPointingDevice::PointerType::Finger, QInputDevice::Capability::Position, + MaxTouchPoints, 0, mSeatName, QPointingDeviceUniqueId(), this); + QWindowSystemInterface::registerInputDevice(mTouchPadDevice); + mPointerGesturePinch.reset(pointerGestures->createPointerGesturePinch(this)); + mPointerGesturePinch->init(pointerGestures->get_pinch_gesture(mPointer->object())); + mPointerGestureSwipe.reset(pointerGestures->createPointerGestureSwipe(this)); + mPointerGestureSwipe->init(pointerGestures->get_swipe_gesture(mPointer->object())); + } + } else if (!(mCaps & WL_SEAT_CAPABILITY_POINTER) && mPointer) { + mPointer.reset(); + mPointerGesturePinch.reset(); + mPointerGestureSwipe.reset(); + } + + if (mCaps & WL_SEAT_CAPABILITY_TOUCH && !mTouch) { + mTouch.reset(createTouch(this)); + + if (!mTouchDevice) { + // TODO number of touchpoints, actual name and ID + mTouchDevice = new QPointingDevice( + QLatin1StringView("touchscreen"), 0, QInputDevice::DeviceType::TouchScreen, + QPointingDevice::PointerType::Finger, QInputDevice::Capability::Position, + MaxTouchPoints, 0, mSeatName, QPointingDeviceUniqueId(), this); + QWindowSystemInterface::registerInputDevice(mTouchDevice); + } + } else if (!(mCaps & WL_SEAT_CAPABILITY_TOUCH) && mTouch) { + mTouch.reset(); + } +} + +QWaylandInputDevice::Keyboard *QWaylandInputDevice::createKeyboard(QWaylandInputDevice *device) +{ + return new Keyboard(device); +} + +QWaylandInputDevice::Pointer *QWaylandInputDevice::createPointer(QWaylandInputDevice *device) +{ + return new Pointer(device); +} + +QWaylandInputDevice::Touch *QWaylandInputDevice::createTouch(QWaylandInputDevice *device) +{ + return new Touch(device); +} + +QWaylandInputDevice::Keyboard *QWaylandInputDevice::keyboard() const +{ + return mKeyboard.data(); +} + +QWaylandInputDevice::Pointer *QWaylandInputDevice::pointer() const +{ + return mPointer.data(); +} + +QWaylandPointerGestureSwipe *QWaylandInputDevice::pointerGestureSwipe() const +{ + return mPointerGestureSwipe.data(); +} + +QWaylandPointerGesturePinch *QWaylandInputDevice::pointerGesturePinch() const +{ + return mPointerGesturePinch.data(); +} + +QWaylandInputDevice::Touch *QWaylandInputDevice::touch() const +{ + return mTouch.data(); +} + +void QWaylandInputDevice::handleEndDrag() +{ + if (mTouch) + mTouch->releasePoints(); + if (mPointer) + mPointer->releaseButtons(); +} + +void QWaylandInputDevice::handleStartDrag() +{ + if (mPointer) + mPointer->leavePointers(); +} + +#if QT_CONFIG(wayland_datadevice) +void QWaylandInputDevice::setDataDevice(QWaylandDataDevice *device) +{ + mDataDevice = device; +} + +QWaylandDataDevice *QWaylandInputDevice::dataDevice() const +{ + return mDataDevice; +} +#endif + +#if QT_CONFIG(clipboard) +void QWaylandInputDevice::setDataControlDevice(QWaylandDataControlDeviceV1 *dataControlDevice) +{ + mDataControlDevice.reset(dataControlDevice); +} + +QWaylandDataControlDeviceV1 *QWaylandInputDevice::dataControlDevice() const +{ + return mDataControlDevice.data(); +} +#endif + +#if QT_CONFIG(wayland_client_primary_selection) +void QWaylandInputDevice::setPrimarySelectionDevice(QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice) +{ + mPrimarySelectionDevice.reset(primarySelectionDevice); +} + +QWaylandPrimarySelectionDeviceV1 *QWaylandInputDevice::primarySelectionDevice() const +{ + return mPrimarySelectionDevice.data(); +} +#endif + +void QWaylandInputDevice::setTextInput(QWaylandTextInputInterface *textInput) +{ + mTextInput.reset(textInput); +} + +#if QT_CONFIG(tabletevent) +void QWaylandInputDevice::setTabletSeat(QWaylandTabletSeatV2 *tabletSeat) +{ + mTabletSeat.reset(tabletSeat); +} + +QWaylandTabletSeatV2 *QWaylandInputDevice::tabletSeat() const +{ + return mTabletSeat.get(); +} +#endif + +void QWaylandInputDevice::setTextInputMethod(QWaylandTextInputMethod *textInputMethod) +{ + mTextInputMethod.reset(textInputMethod); +} + +QWaylandTextInputInterface *QWaylandInputDevice::textInput() const +{ + return mTextInput.data(); +} + +QWaylandTextInputMethod *QWaylandInputDevice::textInputMethod() const +{ + return mTextInputMethod.data(); +} + +void QWaylandInputDevice::removeMouseButtonFromState(Qt::MouseButton button) +{ + if (mPointer) + mPointer->mButtons = mPointer->mButtons & !button; +} + +QWaylandWindow *QWaylandInputDevice::pointerFocus() const +{ + return mPointer ? mPointer->focusWindow() : nullptr; +} + +QWaylandWindow *QWaylandInputDevice::keyboardFocus() const +{ + return mKeyboard ? mKeyboard->focusWindow() : nullptr; +} + +QWaylandWindow *QWaylandInputDevice::touchFocus() const +{ + return mTouch ? mTouch->mFocus : nullptr; +} + +QPointF QWaylandInputDevice::pointerSurfacePosition() const +{ + return mPointer ? mPointer->mSurfacePos : QPointF(); +} + +QList QWaylandInputDevice::possibleKeys(const QKeyEvent *event) const +{ +#if QT_CONFIG(xkbcommon) + if (mKeyboard && mKeyboard->mXkbState) + return QXkbCommon::possibleKeys(mKeyboard->mXkbState.get(), event); +#else + Q_UNUSED(event); +#endif + return {}; +} + +Qt::KeyboardModifiers QWaylandInputDevice::modifiers() const +{ + if (!mKeyboard) + return Qt::NoModifier; + + return mKeyboard->modifiers(); +} + +Qt::KeyboardModifiers QWaylandInputDevice::Keyboard::modifiers() const +{ + Qt::KeyboardModifiers ret = Qt::NoModifier; + +#if QT_CONFIG(xkbcommon) + if (!mXkbState) + return ret; + + ret = QXkbCommon::modifiers(mXkbState.get()); +#endif + + return ret; +} + +#if QT_CONFIG(cursor) +void QWaylandInputDevice::setCursor(const QCursor *cursor, const QSharedPointer &cachedBuffer, int fallbackOutputScale) +{ + CursorState oldCursor = mCursor; + mCursor = CursorState(); // Clear any previous state + mCursor.shape = cursor ? cursor->shape() : Qt::ArrowCursor; + mCursor.hotspot = cursor ? cursor->hotSpot() : QPoint(); + mCursor.fallbackOutputScale = fallbackOutputScale; + mCursor.animationTimer.start(); + + if (mCursor.shape == Qt::BitmapCursor) { + mCursor.bitmapBuffer = cachedBuffer ? cachedBuffer : QWaylandCursor::cursorBitmapBuffer(mQDisplay, cursor); + qreal dpr = cursor->pixmap().devicePixelRatio(); + mCursor.bitmapScale = int(dpr); // Wayland doesn't support fractional buffer scale + // If there was a fractional part of the dpr, we need to scale the hotspot accordingly + if (mCursor.bitmapScale < dpr) + mCursor.hotspot *= dpr / mCursor.bitmapScale; + } + + // Return early if setCursor was called redundantly (mostly happens from decorations) + if (mCursor.shape != Qt::BitmapCursor + && mCursor.shape == oldCursor.shape + && mCursor.hotspot == oldCursor.hotspot + && mCursor.fallbackOutputScale == oldCursor.fallbackOutputScale) { + return; + } + + if (mPointer) + mPointer->updateCursor(); + +#if QT_CONFIG(tabletevent) + if (mTabletSeat) + mTabletSeat->updateCursor(); +#endif +} +#endif + +class EnterEvent : public QWaylandPointerEvent +{ +public: + EnterEvent(QWaylandWindow *surface, const QPointF &local, const QPointF &global) + : QWaylandPointerEvent(QEvent::Enter, Qt::NoScrollPhase, surface, 0, + local, global, Qt::NoButton, Qt::NoButton, Qt::NoModifier) + {} +}; + +void QWaylandInputDevice::Pointer::pointer_enter(uint32_t serial, struct wl_surface *surface, + wl_fixed_t sx, wl_fixed_t sy) +{ + if (!surface) + return; + + QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface); + + if (!window) + return; // Ignore foreign surfaces + + if (mFocus) { + qCWarning(lcQpaWayland) << "The compositor sent a wl_pointer.enter event before sending a" + << "leave event first, this is not allowed by the wayland protocol" + << "attempting to work around it by invalidating the current focus"; + invalidateFocus(); + } + mFocus = window->waylandSurface(); + connect(mFocus.data(), &QObject::destroyed, this, &Pointer::handleFocusDestroyed); + + mSurfacePos = QPointF(wl_fixed_to_double(sx), wl_fixed_to_double(sy)); + mGlobalPos = window->mapToGlobalF(mSurfacePos); + + mParent->mSerial = serial; + mEnterSerial = serial; + + if (!mParent->mQDisplay->lastInputDevice()) { + mParent->mQDisplay->setLastInputDevice(mParent, serial, window); + } + +#if QT_CONFIG(cursor) + // Depends on mEnterSerial being updated + updateCursor(); +#endif + + QWaylandWindow *grab = QWaylandWindow::mouseGrab(); + if (!grab) + setFrameEvent(new EnterEvent(window, mSurfacePos, mGlobalPos)); +} + +class LeaveEvent : public QWaylandPointerEvent +{ +public: + LeaveEvent(QWaylandWindow *surface, const QPointF &localPos, const QPointF &globalPos) + : QWaylandPointerEvent(QEvent::Leave, Qt::NoScrollPhase, surface, 0, + localPos, globalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier) + {} +}; + +void QWaylandInputDevice::Pointer::pointer_leave(uint32_t serial, struct wl_surface *surface) +{ + invalidateFocus(); + mButtons = Qt::NoButton; + + mParent->mSerial = serial; + + // The event may arrive after destroying the window, indicated by + // a null surface. + if (!surface) + return; + + auto *window = QWaylandWindow::fromWlSurface(surface); + if (!window) + return; // Ignore foreign surfaces + + if (!QWaylandWindow::mouseGrab()) + setFrameEvent(new LeaveEvent(window, mSurfacePos, mGlobalPos)); +} + +class MotionEvent : public QWaylandPointerEvent +{ +public: + MotionEvent(QWaylandWindow *surface, ulong timestamp, const QPointF &localPos, + const QPointF &globalPos, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) + : QWaylandPointerEvent(QEvent::MouseMove, Qt::NoScrollPhase, surface, + timestamp, localPos, globalPos, buttons, Qt::NoButton, modifiers) + { + } +}; + +void QWaylandInputDevice::Pointer::pointer_motion(uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + QWaylandWindow *window = focusWindow(); + if (!window) { + // We destroyed the pointer focus surface, but the server didn't get the message yet... + // or the server didn't send an enter event first. In either case, ignore the event. + return; + } + + QPointF pos(wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)); + QPointF global = window->mapToGlobalF(pos); + + mSurfacePos = pos; + mGlobalPos = global; + mParent->mTime = time; + + QWaylandWindow *grab = QWaylandWindow::mouseGrab(); + if (grab && grab != window) { + // We can't know the true position since we're getting events for another surface, + // so we just set it outside of the window boundaries. + pos = QPointF(-1, -1); + global = grab->mapToGlobalF(pos); + window = grab; + } + setFrameEvent(new MotionEvent(window, time, pos, global, mButtons, mParent->modifiers())); +} + +class PressEvent : public QWaylandPointerEvent +{ +public: + PressEvent(QWaylandWindow *surface, ulong timestamp, const QPointF &localPos, + const QPointF &globalPos, Qt::MouseButtons buttons, Qt::MouseButton button, + Qt::KeyboardModifiers modifiers) + : QWaylandPointerEvent(QEvent::MouseButtonPress, Qt::NoScrollPhase, surface, + timestamp, localPos, globalPos, buttons, button, modifiers) + { + } +}; + +class ReleaseEvent : public QWaylandPointerEvent +{ +public: + ReleaseEvent(QWaylandWindow *surface, ulong timestamp, const QPointF &localPos, + const QPointF &globalPos, Qt::MouseButtons buttons, Qt::MouseButton button, + Qt::KeyboardModifiers modifiers) + : QWaylandPointerEvent(QEvent::MouseButtonRelease, Qt::NoScrollPhase, surface, + timestamp, localPos, globalPos, buttons, button, modifiers) + { + } +}; + +void QWaylandInputDevice::Pointer::pointer_button(uint32_t serial, uint32_t time, + uint32_t button, uint32_t state) +{ + QWaylandWindow *window = focusWindow(); + if (!window) { + // We destroyed the pointer focus surface, but the server didn't get the message yet... + // or the server didn't send an enter event first. In either case, ignore the event. + return; + } + + Qt::MouseButton qt_button; + + // translate from kernel (input.h) 'button' to corresponding Qt:MouseButton. + // The range of mouse values is 0x110 <= mouse_button < 0x120, the first Joystick button. + switch (button) { + case 0x110: qt_button = Qt::LeftButton; break; // kernel BTN_LEFT + case 0x111: qt_button = Qt::RightButton; break; + case 0x112: qt_button = Qt::MiddleButton; break; + case 0x113: qt_button = Qt::ExtraButton1; break; // AKA Qt::BackButton + case 0x114: qt_button = Qt::ExtraButton2; break; // AKA Qt::ForwardButton + case 0x115: qt_button = Qt::ExtraButton3; break; // AKA Qt::TaskButton + case 0x116: qt_button = Qt::ExtraButton4; break; + case 0x117: qt_button = Qt::ExtraButton5; break; + case 0x118: qt_button = Qt::ExtraButton6; break; + case 0x119: qt_button = Qt::ExtraButton7; break; + case 0x11a: qt_button = Qt::ExtraButton8; break; + case 0x11b: qt_button = Qt::ExtraButton9; break; + case 0x11c: qt_button = Qt::ExtraButton10; break; + case 0x11d: qt_button = Qt::ExtraButton11; break; + case 0x11e: qt_button = Qt::ExtraButton12; break; + case 0x11f: qt_button = Qt::ExtraButton13; break; + default: return; // invalid button number (as far as Qt is concerned) + } + + mLastButton = qt_button; + + if (state) + mButtons |= qt_button; + else + mButtons &= ~qt_button; + + mParent->mTime = time; + mParent->mSerial = serial; + if (state) + mParent->mQDisplay->setLastInputDevice(mParent, serial, window); + + QWaylandWindow *grab = QWaylandWindow::mouseGrab(); + + QPointF pos = mSurfacePos; + QPointF global = mGlobalPos; + if (grab && grab != focusWindow()) { + pos = QPointF(-1, -1); + global = grab->mapToGlobalF(pos); + + window = grab; + } + + if (state) + setFrameEvent(new PressEvent(window, time, pos, global, mButtons, qt_button, mParent->modifiers())); + else + setFrameEvent(new ReleaseEvent(window, time, pos, global, mButtons, qt_button, mParent->modifiers())); +} + +void QWaylandInputDevice::Pointer::invalidateFocus() +{ + if (mFocus) { + disconnect(mFocus.data(), &QObject::destroyed, this, &Pointer::handleFocusDestroyed); + mFocus = nullptr; + } + mEnterSerial = 0; +} + +void QWaylandInputDevice::Pointer::releaseButtons() +{ + setFrameEvent(new ReleaseEvent(nullptr, mParent->mTime, mSurfacePos, mGlobalPos, Qt::NoButton, Qt::NoButton, mParent->modifiers())); +} + +void QWaylandInputDevice::Pointer::leavePointers() +{ + if (auto *window = focusWindow()) { + LeaveEvent e(focusWindow(), mSurfacePos, mGlobalPos); + window->handleMouse(mParent, e); + } +} + +class WheelEvent : public QWaylandPointerEvent +{ +public: + WheelEvent(QWaylandWindow *surface, Qt::ScrollPhase phase, ulong timestamp, const QPointF &local, + const QPointF &global, const QPoint &pixelDelta, const QPoint &angleDelta, + Qt::MouseEventSource source, Qt::KeyboardModifiers modifiers, bool inverted) + : QWaylandPointerEvent(QEvent::Wheel, phase, surface, timestamp, local, global, + modifiers & Qt::AltModifier ? pixelDelta.transposed() : pixelDelta, + modifiers & Qt::AltModifier ? angleDelta.transposed() : angleDelta, + source, modifiers, inverted) + { + } +}; + +void QWaylandInputDevice::Pointer::pointer_axis(uint32_t time, uint32_t axis, int32_t value) +{ + if (!focusWindow()) { + // We destroyed the pointer focus surface, but the server didn't get the message yet... + // or the server didn't send an enter event first. In either case, ignore the event. + return; + } + + // Get the delta and convert it into the expected range + switch (axis) { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + mFrameData.delta.ry() += wl_fixed_to_double(value); + qCDebug(lcQpaWaylandInput) << "wl_pointer.axis vertical:" << mFrameData.delta.y(); + break; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + mFrameData.delta.rx() += wl_fixed_to_double(value); + qCDebug(lcQpaWaylandInput) << "wl_pointer.axis horizontal:" << mFrameData.delta.x(); + break; + default: + //TODO: is this really needed? + qCWarning(lcQpaWaylandInput) << "wl_pointer.axis: Unknown axis:" << axis; + return; + } + + mParent->mTime = time; + + if (version() < WL_POINTER_FRAME_SINCE_VERSION) { + qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; + flushFrameEvent(); + } +} + +void QWaylandInputDevice::Pointer::pointer_frame() +{ + flushFrameEvent(); +} + +void QWaylandInputDevice::Pointer::pointer_axis_source(uint32_t source) +{ + switch (source) { + case axis_source_wheel: + qCDebug(lcQpaWaylandInput) << "Axis source wheel"; + break; + case axis_source_finger: + qCDebug(lcQpaWaylandInput) << "Axis source finger"; + break; + case axis_source_continuous: + qCDebug(lcQpaWaylandInput) << "Axis source continuous"; + break; + case axis_source_wheel_tilt: + qCDebug(lcQpaWaylandInput) << "Axis source wheel tilt"; + } + mFrameData.axisSource = axis_source(source); +} + +void QWaylandInputDevice::Pointer::pointer_axis_stop(uint32_t time, uint32_t axis) +{ + if (!focusWindow()) + return; + + mParent->mTime = time; + switch (axis) { + case axis_vertical_scroll: + qCDebug(lcQpaWaylandInput) << "Received vertical wl_pointer.axis_stop"; + mFrameData.delta.setY(0); //TODO: what's the point of doing this? + break; + case axis_horizontal_scroll: + qCDebug(lcQpaWaylandInput) << "Received horizontal wl_pointer.axis_stop"; + mFrameData.delta.setX(0); + break; + default: + qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_stop: Unknown axis: " << axis + << "This is most likely a compositor bug"; + return; + } + + // May receive axis_stop for events we haven't sent a ScrollBegin for because + // most axis_sources do not mandate an axis_stop event to be sent. + if (!mScrollBeginSent) { + // TODO: For now, we just ignore these events, but we could perhaps take this as an + // indication that this compositor will in fact send axis_stop events for these sources + // and send a ScrollBegin the next time an axis_source event with this type is encountered. + return; + } + + QWaylandWindow *target = QWaylandWindow::mouseGrab(); + if (!target) + target = focusWindow(); + Qt::KeyboardModifiers mods = mParent->modifiers(); + const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; + WheelEvent wheelEvent(focusWindow(), Qt::ScrollEnd, mParent->mTime, mSurfacePos, mGlobalPos, + QPoint(), QPoint(), Qt::MouseEventNotSynthesized, mods, inverted); + target->handleMouse(mParent, wheelEvent); + mScrollBeginSent = false; + mScrollDeltaRemainder = QPointF(); +} + +void QWaylandInputDevice::Pointer::pointer_axis_discrete(uint32_t axis, int32_t value) +{ + if (!focusWindow()) + return; + + const int32_t delta120 = value * 15 * 8; + + switch (axis) { + case axis_vertical_scroll: + qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete vertical:" << value; + mFrameData.delta120.ry() += delta120; + break; + case axis_horizontal_scroll: + qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete horizontal:" << value; + mFrameData.delta120.rx() += delta120; + break; + default: + //TODO: is this really needed? + qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_discrete: Unknown axis:" << axis; + return; + } +} + +void QWaylandInputDevice::Pointer::pointer_axis_value120(uint32_t axis, int32_t value) +{ + if (!focusWindow()) + return; + + switch (axis) { + case axis_vertical_scroll: + qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_value120 vertical:" << value; + mFrameData.delta120.ry() += value; + break; + case axis_horizontal_scroll: + qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_value120 horizontal:" << value; + mFrameData.delta120.rx() += value; + break; + default: + qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_value120: Unknown axis:" << axis; + return; + } +} + +void QWaylandInputDevice::Pointer::pointer_axis_relative_direction(uint32_t axis, uint32_t direction) +{ + const bool inverted = direction == axis_relative_direction_inverted; + switch (axis) { + case axis_vertical_scroll: + mFrameData.verticalAxisInverted = inverted; + break; + case axis_horizontal_scroll: + mFrameData.horizontalAxisInverted = inverted; + break; + default: + qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_relative_direction: Unknown axis:" << axis; + } +} + +void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) +{ + qCDebug(lcQpaWaylandInput) << "Setting frame event " << event->type; + if (mFrameData.event && mFrameData.event->type != event->type) { + qCDebug(lcQpaWaylandInput) << "Flushing; previous was " << mFrameData.event->type; + flushFrameEvent(); + } + + mFrameData.event = event; + + if (version() < WL_POINTER_FRAME_SINCE_VERSION) { + qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; + flushFrameEvent(); + } +} + +void QWaylandInputDevice::Pointer::FrameData::resetScrollData() +{ + delta120 = QPoint(); + delta = QPointF(); + axisSource = axis_source_wheel; + horizontalAxisInverted = false; + verticalAxisInverted = false; +} + +bool QWaylandInputDevice::Pointer::FrameData::hasPixelDelta() const +{ + switch (axisSource) { + case axis_source_wheel_tilt: // sideways tilt of the wheel + case axis_source_wheel: + // In the case of wheel events, a pixel delta doesn't really make sense, + // and will make Qt think this is a continuous scroll event when it isn't, + // so just ignore it. + return false; + case axis_source_finger: + case axis_source_continuous: + return !delta.isNull(); + default: + return false; + } +} + +QPoint QWaylandInputDevice::Pointer::FrameData::pixelDeltaAndError(QPointF *accumulatedError) const +{ + if (!hasPixelDelta()) + return QPoint(); + + Q_ASSERT(accumulatedError); + // Add accumulated rounding error before rounding again + QPoint pixelDelta = (delta + *accumulatedError).toPoint(); + *accumulatedError += delta - pixelDelta; + Q_ASSERT(qAbs(accumulatedError->x()) < 1.0); + Q_ASSERT(qAbs(accumulatedError->y()) < 1.0); + + // for continuous scroll events things should be + // in the same direction + // i.e converted so downwards surface co-ordinates (positive axis_value) + // goes to downwards in wheel event (negative value) + pixelDelta *= -1; + return pixelDelta; +} + +QPoint QWaylandInputDevice::Pointer::FrameData::angleDelta() const +{ + if (delta120.isNull()) { + // If we didn't get any discrete events, then we need to fall back to + // the continuous information. + return (delta * -12).toPoint(); //TODO: why multiply by 12? + } + + // The angle delta is in eights of degrees, and our docs says most mice have + // 1 click = 15 degrees, i.e. 120 is one click. It's also in the opposite + // direction of surface space. + return -delta120; +} + +Qt::MouseEventSource QWaylandInputDevice::Pointer::FrameData::wheelEventSource() const +{ + switch (axisSource) { + case axis_source_wheel_tilt: // sideways tilt of the wheel + case axis_source_wheel: + return Qt::MouseEventNotSynthesized; + case axis_source_finger: + case axis_source_continuous: + default: // Whatever other sources might be added are probably not mouse wheels + return Qt::MouseEventSynthesizedBySystem; + } +} + +void QWaylandInputDevice::Pointer::flushScrollEvent() +{ + QPoint angleDelta = mFrameData.angleDelta(); + + // Angle delta is required for Qt wheel events, so don't try to send events if it's zero + if (!angleDelta.isNull()) { + QWaylandWindow *target = QWaylandWindow::mouseGrab(); + if (!target) + target = focusWindow(); + + if (isDefinitelyTerminated(mFrameData.axisSource) && !mScrollBeginSent) { + qCDebug(lcQpaWaylandInput) << "Flushing scroll event sending ScrollBegin"; + target->handleMouse(mParent, WheelEvent(focusWindow(), Qt::ScrollBegin, mParent->mTime, + mSurfacePos, mGlobalPos, QPoint(), QPoint(), + Qt::MouseEventNotSynthesized, + mParent->modifiers(), false)); + mScrollBeginSent = true; + mScrollDeltaRemainder = QPointF(); + } + + Qt::ScrollPhase phase = mScrollBeginSent ? Qt::ScrollUpdate : Qt::NoScrollPhase; + QPoint pixelDelta = mFrameData.pixelDeltaAndError(&mScrollDeltaRemainder); + Qt::MouseEventSource source = mFrameData.wheelEventSource(); + + + // The wayland protocol has separate horizontal and vertical axes, Qt has just the one inverted flag + // Pragmatically it should't come up + const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; + + qCDebug(lcQpaWaylandInput) << "Flushing scroll event" << phase << pixelDelta << angleDelta; + target->handleMouse(mParent, WheelEvent(focusWindow(), phase, mParent->mTime, mSurfacePos, mGlobalPos, + pixelDelta, angleDelta, source, mParent->modifiers(), inverted)); + } + mFrameData.resetScrollData(); +} + +void QWaylandInputDevice::Pointer::flushFrameEvent() +{ + if (auto *event = mFrameData.event) { + if (auto window = event->surface) { + window->handleMouse(mParent, *event); + } else if (mFrameData.event->type == QEvent::MouseButtonRelease) { + // If the window has been destroyed, we still need to report an up event, but it can't + // be handled by the destroyed window (obviously), so send the event here instead. + QWindowSystemInterface::handleMouseEvent( + nullptr, event->timestamp, + QPointingDevice::primaryPointingDevice(mParent->seatname()), event->local, + event->global, event->buttons, event->button, event->type, + event->modifiers); // , Qt::MouseEventSource source = + // Qt::MouseEventNotSynthesized); + } + delete mFrameData.event; + mFrameData.event = nullptr; + } + + //TODO: do modifiers get passed correctly here? + flushScrollEvent(); +} + +bool QWaylandInputDevice::Pointer::isDefinitelyTerminated(QtWayland::wl_pointer::axis_source source) const +{ + return source == axis_source_finger; +} + +void QWaylandInputDevice::Keyboard::keyboard_keymap(uint32_t format, int32_t fd, uint32_t size) +{ + mKeymapFormat = format; +#if QT_CONFIG(xkbcommon) + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + qCWarning(lcQpaWayland) << "unknown keymap format:" << format; + close(fd); + return; + } + + char *map_str = static_cast(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0)); + if (map_str == MAP_FAILED) { + close(fd); + return; + } + + mXkbKeymap.reset(xkb_keymap_new_from_string(mParent->mQDisplay->xkbContext(), map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS)); + QXkbCommon::verifyHasLatinLayout(mXkbKeymap.get()); + + munmap(map_str, size); + close(fd); + + if (mXkbKeymap) + mXkbState.reset(xkb_state_new(mXkbKeymap.get())); + else + mXkbState.reset(nullptr); +#else + Q_UNUSED(fd); + Q_UNUSED(size); +#endif +} + +void QWaylandInputDevice::Keyboard::keyboard_enter(uint32_t serial, struct wl_surface *surface, struct wl_array *keys) +{ + Q_UNUSED(serial); + Q_UNUSED(keys); + + if (!surface) { + // Ignoring wl_keyboard.enter event with null surface. This is either a compositor bug, + // or it's a race with a wl_surface.destroy request. In either case, ignore the event. + return; + } + + QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface); + if (!window) + return; + + if (mFocus) { + qCWarning(lcQpaWayland()) << "Unexpected wl_keyboard.enter event. Keyboard already has focus"; + disconnect(mFocus, &QWaylandSurface::destroyed, this, &Keyboard::handleFocusDestroyed); + } + + mFocus = window->waylandSurface(); + connect(mFocus, &QWaylandSurface::destroyed, this, &Keyboard::handleFocusDestroyed); + + mParent->mQDisplay->handleKeyboardFocusChanged(mParent); +} + +void QWaylandInputDevice::Keyboard::keyboard_leave(uint32_t serial, struct wl_surface *surface) +{ + Q_UNUSED(serial); + + if (!surface) { + // Either a compositor bug, or a race condition with wl_surface.destroy, ignore the event. + return; + } + + QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface); + if (!window) + return; + + if (window->waylandSurface() != mFocus) { + qCWarning(lcQpaWayland) << "Ignoring unexpected wl_keyboard.leave event." + << "wl_surface argument does not match the current focus" + << "This is most likely a compositor bug"; + return; + } + disconnect(mFocus, &QWaylandSurface::destroyed, this, &Keyboard::handleFocusDestroyed); + handleFocusLost(); +} + +void QWaylandInputDevice::Keyboard::handleKey(ulong timestamp, QEvent::Type type, int key, + Qt::KeyboardModifiers modifiers, quint32 nativeScanCode, + quint32 nativeVirtualKey, quint32 nativeModifiers, + const QString &text, bool autorepeat, ushort count) +{ + QPlatformInputContext *inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); + bool filtered = false; + + if (inputContext) { + QKeyEvent event(type, key, modifiers, nativeScanCode, nativeVirtualKey, + nativeModifiers, text, autorepeat, count); + event.setTimestamp(timestamp); + filtered = inputContext->filterEvent(&event); + } + + if (!filtered) { + auto window = focusWindow()->window(); + + if (type == QEvent::KeyPress && key == Qt::Key_Menu) { + auto cursor = window->screen()->handle()->cursor(); + if (cursor) { + const QPoint globalPos = cursor->pos(); + const QPoint pos = window->mapFromGlobal(globalPos); + QWindowSystemInterface::handleContextMenuEvent(window, false, pos, globalPos, modifiers); + } + } + + QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, type, key, modifiers, + nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorepeat, count); + } +} + +void QWaylandInputDevice::Keyboard::keyboard_key(uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +{ + if (mKeymapFormat != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 && mKeymapFormat != WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { + qCWarning(lcQpaWayland) << Q_FUNC_INFO << "unknown keymap format:" << mKeymapFormat; + return; + } + + auto *window = focusWindow(); + if (!window) { + // We destroyed the keyboard focus surface, but the server didn't get the message yet... + // or the server didn't send an enter event first. In either case, ignore the event. + return; + } + + mParent->mSerial = serial; + + const bool isDown = state != WL_KEYBOARD_KEY_STATE_RELEASED; + if (isDown) + mParent->mQDisplay->setLastInputDevice(mParent, serial, window); + + if (mKeymapFormat == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { +#if QT_CONFIG(xkbcommon) + if ((!mXkbKeymap || !mXkbState) && !createDefaultKeymap()) + return; + + auto code = key + 8; // map to wl_keyboard::keymap_format::keymap_format_xkb_v1 + + xkb_keysym_t sym = xkb_state_key_get_one_sym(mXkbState.get(), code); + Qt::KeyboardModifiers modifiers = QXkbCommon::modifiers(mXkbState.get(), sym); + + int qtkey = keysymToQtKey(sym, modifiers, mXkbState.get(), code); + QString text = QXkbCommon::lookupString(mXkbState.get(), code); + + QEvent::Type type = isDown ? QEvent::KeyPress : QEvent::KeyRelease; + handleKey(time, type, qtkey, modifiers, code, sym, mNativeModifiers, text); + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED && xkb_keymap_key_repeats(mXkbKeymap.get(), code) && mRepeatRate > 0) { + mRepeatKey.key = qtkey; + mRepeatKey.code = code; + mRepeatKey.time = time; + mRepeatKey.text = text; + mRepeatKey.nativeVirtualKey = sym; + mRepeatTimer.setInterval(mRepeatDelay); + mRepeatTimer.start(); + } else if (mRepeatKey.code == code) { + mRepeatTimer.stop(); + } +#else + Q_UNUSED(time); + Q_UNUSED(key); + qCWarning(lcQpaWayland, "xkbcommon not available on this build, not performing key mapping"); + return; +#endif + } else if (mKeymapFormat == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { + // raw scan code + return; + } +} + +void QWaylandInputDevice::Keyboard::handleFocusDestroyed() +{ + handleFocusLost(); +} + +void QWaylandInputDevice::Keyboard::handleFocusLost() +{ + mFocus = nullptr; + mParent->mQDisplay->handleKeyboardFocusChanged(mParent); + mRepeatTimer.stop(); +} + +void QWaylandInputDevice::Keyboard::keyboard_modifiers(uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) +{ + Q_UNUSED(serial); +#if QT_CONFIG(xkbcommon) + if (mXkbState) + xkb_state_update_mask(mXkbState.get(), + mods_depressed, mods_latched, mods_locked, + 0, 0, group); + mNativeModifiers = mods_depressed | mods_latched | mods_locked; +#else + Q_UNUSED(mods_depressed); + Q_UNUSED(mods_latched); + Q_UNUSED(mods_locked); + Q_UNUSED(group); +#endif +} + +void QWaylandInputDevice::Keyboard::keyboard_repeat_info(int32_t rate, int32_t delay) +{ + mRepeatRate = rate; + mRepeatDelay = delay; +} + +void QWaylandInputDevice::Touch::touch_down(uint32_t serial, + uint32_t time, + struct wl_surface *surface, + int32_t id, + wl_fixed_t x, + wl_fixed_t y) +{ + if (!surface) + return; + + auto *window = QWaylandWindow::fromWlSurface(surface); + if (!window) + return; // Ignore foreign surfaces + + mParent->mTime = time; + mParent->mSerial = serial; + mFocus = window; + mParent->mQDisplay->setLastInputDevice(mParent, serial, mFocus); + QPointF position(wl_fixed_to_double(x), wl_fixed_to_double(y)); + mParent->handleTouchPoint(id, QEventPoint::Pressed, position); +} + +void QWaylandInputDevice::Touch::touch_up(uint32_t serial, uint32_t time, int32_t id) +{ + Q_UNUSED(serial); + mParent->mTime = time; + mParent->handleTouchPoint(id, QEventPoint::Released); + + if (allTouchPointsReleased()) { + mFocus = nullptr; + + // As of Weston 7.0.0 there is no touch_frame after the last touch_up + // (i.e. when the last finger is released). To accommodate for this, issue a + // touch_frame. This cannot hurt since it is safe to call the touch_frame + // handler multiple times when there are no points left. + // See: https://gitlab.freedesktop.org/wayland/weston/issues/44 + // TODO: change logging category to lcQpaWaylandInput in newer versions. + qCDebug(lcQpaWayland, "Generating fake frame event to work around Weston bug"); + touch_frame(); + } +} + +void QWaylandInputDevice::Touch::touch_motion(uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) +{ + QPointF position(wl_fixed_to_double(x), wl_fixed_to_double(y)); + mParent->mTime = time; + mParent->handleTouchPoint(id, QEventPoint::Updated, position); +} + +void QWaylandInputDevice::Touch::touch_cancel() +{ + mPendingTouchPoints.clear(); + + mFocus = nullptr; + QWindowSystemInterface::handleTouchCancelEvent(nullptr, mParent->mTouchDevice); +} + +void QWaylandInputDevice::handleTouchPoint(int id, QEventPoint::State state, const QPointF &surfacePosition) +{ + auto end = mTouch->mPendingTouchPoints.end(); + auto it = std::find_if(mTouch->mPendingTouchPoints.begin(), end, [id](const QWindowSystemInterface::TouchPoint &tp){ return tp.id == id; }); + if (it == end) { + it = mTouch->mPendingTouchPoints.insert(end, QWindowSystemInterface::TouchPoint()); + it->id = id; + } + // If the touch points were up and down in same frame, send out frame right away + else if ((it->state == QEventPoint::Pressed && state == QEventPoint::Released) + || (it->state == QEventPoint::Released && state == QEventPoint::Pressed)) { + mTouch->touch_frame(); + it = mTouch->mPendingTouchPoints.insert(mTouch->mPendingTouchPoints.end(), QWindowSystemInterface::TouchPoint()); + it->id = id; + } + + QWindowSystemInterface::TouchPoint &tp = *it; + + // Only moved and pressed needs to update/set position + if (state == QEventPoint::Updated || state == QEventPoint::Pressed) { + // We need a global (screen) position. + QWaylandWindow *win = mTouch->mFocus; + + //is it possible that mTouchFocus is null; + if (!win && mPointer) + win = mPointer->focusWindow(); + if (!win && mKeyboard) + win = mKeyboard->focusWindow(); + if (!win || !win->window()) + return; + + tp.area = QRectF(0, 0, 8, 8); + const QPointF localPosition = win->mapFromWlSurface(surfacePosition); + const QPointF globalPosition = win->mapToGlobalF(localPosition); + tp.area.moveCenter(globalPosition); + } + + // If the touch point was pressed earlier this frame, we don't want to overwrite its state. + if (tp.state != QEventPoint::Pressed) + tp.state = QEventPoint::State(state); + + tp.pressure = tp.state == QEventPoint::Released ? 0 : 1; +} + +bool QWaylandInputDevice::Touch::allTouchPointsReleased() +{ + for (const auto &tp : std::as_const(mPendingTouchPoints)) { + if (tp.state != QEventPoint::Released) + return false; + } + return true; +} + +void QWaylandInputDevice::Touch::releasePoints() +{ + if (mPendingTouchPoints.empty()) + return; + + for (QWindowSystemInterface::TouchPoint &tp : mPendingTouchPoints) + tp.state = QEventPoint::Released; + + touch_frame(); +} + +void QWaylandInputDevice::Touch::touch_frame() +{ + // TODO: early return if no events? + + QWindow *window = mFocus ? mFocus->window() : nullptr; + + if (mFocus) { + // Returns a reference to the last item in the list. The list must not be empty. + // If the list can be empty, call isEmpty() before calling this function. + // See: https://doc.qt.io/qt-5.15/qlist.html#last + if (mPendingTouchPoints.empty()) + return; + const QWindowSystemInterface::TouchPoint &tp = mPendingTouchPoints.constLast(); + // When the touch event is received, the global pos is calculated with the margins + // in mind. Now we need to adjust again to get the correct local pos back. + QMargins margins = mFocus->clientSideMargins(); + QPoint p = tp.area.center().toPoint(); + QPointF localPos(mFocus->mapFromGlobal(p) + QPoint(margins.left(), margins.top())); + if (mFocus->touchDragDecoration(mParent, localPos, tp.area.center(), tp.state, mParent->modifiers())) + return; + } + + QWindowSystemInterface::handleTouchEvent(window, mParent->mTime, mParent->mTouchDevice, mPendingTouchPoints, mParent->modifiers()); + + // Prepare state for next frame + const auto prevTouchPoints = mPendingTouchPoints; + mPendingTouchPoints.clear(); + for (const auto &prevPoint: prevTouchPoints) { + // All non-released touch points should be part of the next touch event + if (prevPoint.state != QEventPoint::Released) { + QWindowSystemInterface::TouchPoint tp = prevPoint; + tp.state = QEventPoint::Stationary; // ... as stationary (unless proven otherwise) + mPendingTouchPoints.append(tp); + } + } + +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylandinputdevice_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h new file mode 100644 index 00000000000..bcaf025840d --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h @@ -0,0 +1,535 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2024 Jie Liu +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDINPUTDEVICE_H +#define QWAYLANDINPUTDEVICE_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 + +#if QT_CONFIG(xkbcommon) +#include +#endif + +#include +#include +#include + +#if QT_CONFIG(cursor) +struct wl_cursor_image; +#endif + +QT_BEGIN_NAMESPACE + +namespace QtWayland { +class zwp_primary_selection_device_v1; +} //namespace QtWayland + +namespace QtWaylandClient { + +class QWaylandDataDevice; +class QWaylandDisplay; +#if QT_CONFIG(clipboard) +class QWaylandDataControlDeviceV1; +#endif +#if QT_CONFIG(wayland_client_primary_selection) +class QWaylandPrimarySelectionDeviceV1; +#endif +#if QT_CONFIG(tabletevent) +class QWaylandTabletSeatV2; +#endif +class QWaylandPointerGestures; +class QWaylandPointerGestureSwipe; +class QWaylandPointerGesturePinch; +class QWaylandTextInputInterface; +class QWaylandTextInputMethod; +#if QT_CONFIG(cursor) +class QWaylandCursorTheme; +class QWaylandCursorShape; +template +class CursorSurface; +#endif + +Q_DECLARE_LOGGING_CATEGORY(lcQpaWaylandInput); + +class Q_WAYLANDCLIENT_EXPORT QWaylandInputDevice + : public QObject + , public QtWayland::wl_seat +{ + Q_OBJECT +public: + class Keyboard; + class Pointer; + class Touch; + + QWaylandInputDevice(QWaylandDisplay *display, int version, uint32_t id); + ~QWaylandInputDevice() override; + + uint32_t id() const { return mId; } + uint32_t capabilities() const { return mCaps; } + QString seatname() const { return mSeatName; } + + QWaylandDisplay *display() const { return mQDisplay; } + struct ::wl_seat *wl_seat() { return QtWayland::wl_seat::object(); } + +#if QT_CONFIG(cursor) + void setCursor(const QCursor *cursor, const QSharedPointer &cachedBuffer = {}, int fallbackOutputScale = 1); +#endif + void handleStartDrag(); + void handleEndDrag(); + +#if QT_CONFIG(wayland_datadevice) + void setDataDevice(QWaylandDataDevice *device); + QWaylandDataDevice *dataDevice() const; +#endif + +#if QT_CONFIG(clipboard) + void setDataControlDevice(QWaylandDataControlDeviceV1 *dataControlDevice); + QWaylandDataControlDeviceV1 *dataControlDevice() const; +#endif + +#if QT_CONFIG(wayland_client_primary_selection) + void setPrimarySelectionDevice(QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice); + QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice() const; +#endif + +#if QT_CONFIG(tabletevent) + void setTabletSeat(QWaylandTabletSeatV2 *tabletSeat); + QWaylandTabletSeatV2* tabletSeat() const; +#endif + + void setTextInput(QWaylandTextInputInterface *textInput); + QWaylandTextInputInterface *textInput() const; + + void setTextInputMethod(QWaylandTextInputMethod *textInputMethod); + QWaylandTextInputMethod *textInputMethod() const; + + void removeMouseButtonFromState(Qt::MouseButton button); + + QWaylandWindow *pointerFocus() const; + QWaylandWindow *keyboardFocus() const; + QWaylandWindow *touchFocus() const; + + QList possibleKeys(const QKeyEvent *event) const; + + QPointF pointerSurfacePosition() const; + + Qt::KeyboardModifiers modifiers() const; + + uint32_t serial() const; + + virtual Keyboard *createKeyboard(QWaylandInputDevice *device); + virtual Pointer *createPointer(QWaylandInputDevice *device); + virtual Touch *createTouch(QWaylandInputDevice *device); + + Keyboard *keyboard() const; + Pointer *pointer() const; + QWaylandPointerGestureSwipe *pointerGestureSwipe() const; + QWaylandPointerGesturePinch *pointerGesturePinch() const; + Touch *touch() const; + +protected: + QWaylandDisplay *mQDisplay = nullptr; + struct wl_display *mDisplay = nullptr; + + uint32_t mId = -1; + uint32_t mCaps = 0; + QString mSeatName; + bool mSeatNameKnown = false; + +#if QT_CONFIG(cursor) + struct CursorState { + QSharedPointer bitmapBuffer; // not used with shape cursors + int bitmapScale = 1; + Qt::CursorShape shape = Qt::ArrowCursor; + int fallbackOutputScale = 1; + QPoint hotspot; + QElapsedTimer animationTimer; + } mCursor; +#endif + +#if QT_CONFIG(wayland_datadevice) + QWaylandDataDevice *mDataDevice = nullptr; +#endif + +#if QT_CONFIG(clipboard) + QScopedPointer mDataControlDevice; +#endif + +#if QT_CONFIG(wayland_client_primary_selection) + QScopedPointer mPrimarySelectionDevice; +#endif + + QScopedPointer mKeyboard; + QScopedPointer mPointer; + QScopedPointer mPointerGestureSwipe; + QScopedPointer mPointerGesturePinch; + QScopedPointer mTouch; + + QScopedPointer mTextInput; + QScopedPointer mTextInputMethod; +#if QT_CONFIG(tabletevent) + QScopedPointer mTabletSeat; +#endif + + uint32_t mTime = 0; + uint32_t mSerial = 0; + + void seat_capabilities(uint32_t caps) override; + void seat_name(const QString &name) override; + void maybeRegisterInputDevices(); + void handleTouchPoint(int id, QEventPoint::State state, const QPointF &surfacePosition = QPoint()); + + QPointingDevice *mTouchDevice = nullptr; + QPointingDevice *mTouchPadDevice = nullptr; + + friend class QWaylandTouchExtension; + friend class QWaylandQtKeyExtension; + friend class QWaylandPointerGestureSwipe; + friend class QWaylandPointerGesturePinch; + friend class QWaylandWindow; + friend class QWaylandTabletToolV2; +}; + +inline uint32_t QWaylandInputDevice::serial() const +{ + return mSerial; +} + + +class Q_WAYLANDCLIENT_EXPORT QWaylandInputDevice::Keyboard : public QObject, public QtWayland::wl_keyboard +{ + Q_OBJECT + +public: + Keyboard(QWaylandInputDevice *p); + ~Keyboard() override; + + QWaylandWindow *focusWindow() const; + + void keyboard_keymap(uint32_t format, + int32_t fd, + uint32_t size) override; + void keyboard_enter(uint32_t time, + struct wl_surface *surface, + struct wl_array *keys) override; + void keyboard_leave(uint32_t time, + struct wl_surface *surface) override; + void keyboard_key(uint32_t serial, uint32_t time, + uint32_t key, uint32_t state) override; + void keyboard_modifiers(uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) override; + void keyboard_repeat_info(int32_t rate, int32_t delay) override; + + QWaylandInputDevice *mParent = nullptr; + QPointer mFocus; + + uint32_t mNativeModifiers = 0; + + struct repeatKey { + int key = 0; + uint32_t code = 0; + uint32_t time = 0 ; + QString text; + uint32_t nativeVirtualKey = 0; + } mRepeatKey; + + QTimer mRepeatTimer; + int mRepeatRate = 25; + int mRepeatDelay = 400; + + uint32_t mKeymapFormat = WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1; + + Qt::KeyboardModifiers modifiers() const; + + struct ::wl_keyboard *wl_keyboard() { return QtWayland::wl_keyboard::object(); } + +#if QT_CONFIG(xkbcommon) + virtual int keysymToQtKey(xkb_keysym_t keysym, Qt::KeyboardModifiers modifiers, xkb_state *state, xkb_keycode_t code) { + return QXkbCommon::keysymToQtKey(keysym, modifiers, state, code); + } +#endif + +private Q_SLOTS: + void handleFocusDestroyed(); + void handleFocusLost(); + +private: +#if QT_CONFIG(xkbcommon) + bool createDefaultKeymap(); +#endif + void handleKey(ulong timestamp, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers, + quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers, + const QString &text, bool autorepeat = false, ushort count = 1); + +#if QT_CONFIG(xkbcommon) + QXkbCommon::ScopedXKBKeymap mXkbKeymap; + QXkbCommon::ScopedXKBState mXkbState; +#endif + friend class QWaylandInputDevice; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandInputDevice::Pointer : public QObject, public QtWayland::wl_pointer +{ + Q_OBJECT +public: + explicit Pointer(QWaylandInputDevice *seat); + ~Pointer() override; + QWaylandWindow *focusWindow() const; +#if QT_CONFIG(cursor) + int idealCursorScale() const; + void updateCursorTheme(); + void updateCursor(); + void cursorTimerCallback(); + void cursorFrameCallback(); + CursorSurface *getOrCreateCursorSurface(); +#endif + QWaylandInputDevice *seat() const { return mParent; } + + struct ::wl_pointer *wl_pointer() { return QtWayland::wl_pointer::object(); } + +protected: + void pointer_enter(uint32_t serial, struct wl_surface *surface, + wl_fixed_t sx, wl_fixed_t sy) override; + void pointer_leave(uint32_t time, struct wl_surface *surface) override; + void pointer_motion(uint32_t time, + wl_fixed_t sx, wl_fixed_t sy) override; + void pointer_button(uint32_t serial, uint32_t time, + uint32_t button, uint32_t state) override; + void pointer_axis(uint32_t time, + uint32_t axis, + wl_fixed_t value) override; + void pointer_axis_source(uint32_t source) override; + void pointer_axis_stop(uint32_t time, uint32_t axis) override; + void pointer_axis_discrete(uint32_t axis, int32_t value) override; + void pointer_frame() override; + void pointer_axis_value120(uint32_t axis, int32_t value120) override; + void pointer_axis_relative_direction(uint32_t axis, uint32_t direction) override; + +private Q_SLOTS: + void handleFocusDestroyed() { invalidateFocus(); } + +private: + void invalidateFocus(); + +public: + void releaseButtons(); + void leavePointers(); + + QWaylandInputDevice *mParent = nullptr; + QPointer mFocus; + uint32_t mEnterSerial = 0; +#if QT_CONFIG(cursor) + struct { + QScopedPointer shape; + QWaylandCursorTheme *theme = nullptr; + int themeBufferScale = 0; + QScopedPointer> surface; + QTimer frameTimer; + bool gotFrameCallback = false; + bool gotTimerCallback = false; + } mCursor; +#endif + QPointF mSurfacePos; + QPointF mGlobalPos; + Qt::MouseButtons mButtons = Qt::NoButton; + Qt::MouseButton mLastButton = Qt::NoButton; + + struct FrameData { + QWaylandPointerEvent *event = nullptr; + + QPointF delta; + QPoint delta120; + axis_source axisSource = axis_source_wheel; + bool verticalAxisInverted = false; + bool horizontalAxisInverted = false; + + void resetScrollData(); + bool hasPixelDelta() const; + QPoint pixelDeltaAndError(QPointF *accumulatedError) const; + QPoint pixelDelta() const { return hasPixelDelta() ? delta.toPoint() : QPoint(); } + QPoint angleDelta() const; + Qt::MouseEventSource wheelEventSource() const; + } mFrameData; + + bool mScrollBeginSent = false; + QPointF mScrollDeltaRemainder; + + void setFrameEvent(QWaylandPointerEvent *event); + void flushScrollEvent(); + void flushFrameEvent(); +private: //TODO: should other methods be private as well? + bool isDefinitelyTerminated(axis_source source) const; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandInputDevice::Touch : public QtWayland::wl_touch +{ +public: + Touch(QWaylandInputDevice *p); + ~Touch() override; + + void touch_down(uint32_t serial, + uint32_t time, + struct wl_surface *surface, + int32_t id, + wl_fixed_t x, + wl_fixed_t y) override; + void touch_up(uint32_t serial, + uint32_t time, + int32_t id) override; + void touch_motion(uint32_t time, + int32_t id, + wl_fixed_t x, + wl_fixed_t y) override; + void touch_frame() override; + void touch_cancel() override; + + bool allTouchPointsReleased(); + void releasePoints(); + + struct ::wl_touch *wl_touch() { return QtWayland::wl_touch::object(); } + + QWaylandInputDevice *mParent = nullptr; + QPointer mFocus; + QList mPendingTouchPoints; +}; + +class QWaylandPointerEvent +{ + Q_GADGET +public: + inline QWaylandPointerEvent(QEvent::Type type, Qt::ScrollPhase phase, QWaylandWindow *surface, + ulong timestamp, const QPointF &localPos, const QPointF &globalPos, + Qt::MouseButtons buttons, Qt::MouseButton button, + Qt::KeyboardModifiers modifiers) + : type(type) + , phase(phase) + , timestamp(timestamp) + , local(localPos) + , global(globalPos) + , buttons(buttons) + , button(button) + , modifiers(modifiers) + , surface(surface) + {} + inline QWaylandPointerEvent(QEvent::Type type, Qt::ScrollPhase phase, QWaylandWindow *surface, + ulong timestamp, const QPointF &local, const QPointF &global, + const QPoint &pixelDelta, const QPoint &angleDelta, + Qt::MouseEventSource source, + Qt::KeyboardModifiers modifiers, bool inverted) + : type(type) + , phase(phase) + , timestamp(timestamp) + , local(local) + , global(global) + , modifiers(modifiers) + , pixelDelta(pixelDelta) + , angleDelta(angleDelta) + , source(source) + , surface(surface) + , inverted(inverted) + {} + + QEvent::Type type = QEvent::None; + Qt::ScrollPhase phase = Qt::NoScrollPhase; + ulong timestamp = 0; + QPointF local; + QPointF global; + Qt::MouseButtons buttons; + Qt::MouseButton button = Qt::NoButton; // Button that caused the event (QMouseEvent::button) + Qt::KeyboardModifiers modifiers; + QPoint pixelDelta; + QPoint angleDelta; + Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; + QPointer surface; + bool inverted = false; +}; + +#ifndef QT_NO_GESTURES +class QWaylandPointerGestureSwipeEvent +{ + Q_GADGET +public: + inline QWaylandPointerGestureSwipeEvent(QWaylandWindow *surface, Qt::GestureState state, + ulong timestamp, const QPointF &local, + const QPointF &global, uint fingers, const QPointF& delta) + : surface(surface) + , state(state) + , timestamp(timestamp) + , local(local) + , global(global) + , fingers(fingers) + , delta(delta) + {} + + QPointer surface; + Qt::GestureState state = Qt::GestureState::NoGesture; + ulong timestamp = 0; + QPointF local; + QPointF global; + uint fingers = 0; + QPointF delta; +}; + +class QWaylandPointerGesturePinchEvent +{ + Q_GADGET +public: + inline QWaylandPointerGesturePinchEvent(QWaylandWindow *surface, Qt::GestureState state, + ulong timestamp, const QPointF &local, + const QPointF &global, uint fingers, const QPointF& delta, + qreal scale_delta, qreal rotation_delta) + : surface(surface) + , state(state) + , timestamp(timestamp) + , local(local) + , global(global) + , fingers(fingers) + , delta(delta) + , scale_delta(scale_delta) + , rotation_delta(rotation_delta) + {} + + QPointer surface; + Qt::GestureState state = Qt::GestureState::NoGesture; + ulong timestamp = 0; + QPointF local; + QPointF global; + uint fingers = 0; + QPointF delta; + qreal scale_delta = 0; + qreal rotation_delta = 0; +}; +#endif // #ifndef QT_NO_GESTURES + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/qwaylandinputmethodcontext.cpp b/src/plugins/platforms/wayland/qwaylandinputmethodcontext.cpp new file mode 100644 index 00000000000..b0f55ef570c --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandinputmethodcontext.cpp @@ -0,0 +1,400 @@ +// Copyright (C) 2020 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 + +#include "qwaylandinputmethodcontext_p.h" +#include "qwaylandinputcontext_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandinputdevice_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +static constexpr int maxStringSize = 1000; // actual max is 4096/3 + +QWaylandTextInputMethod::QWaylandTextInputMethod(QWaylandDisplay *display, struct ::qt_text_input_method_v1 *textInputMethod) + : QtWayland::qt_text_input_method_v1(textInputMethod) +{ + Q_UNUSED(display); +} + +QWaylandTextInputMethod::~QWaylandTextInputMethod() +{ + qt_text_input_method_v1_destroy(object()); +} + +void QWaylandTextInputMethod::text_input_method_v1_visible_changed(int32_t visible) +{ + if (m_isVisible != visible) { + m_isVisible = visible; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputPanelVisibleChanged(); + } +} + +void QWaylandTextInputMethod::text_input_method_v1_locale_changed(const QString &localeName) +{ + m_locale = QLocale(localeName); +} + +void QWaylandTextInputMethod::text_input_method_v1_input_direction_changed(int32_t inputDirection) +{ + m_layoutDirection = Qt::LayoutDirection(inputDirection); +} + +void QWaylandTextInputMethod::text_input_method_v1_keyboard_rectangle_changed(wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) +{ + const QRectF keyboardRectangle(wl_fixed_to_double(x), + wl_fixed_to_double(y), + wl_fixed_to_double(width), + wl_fixed_to_double(height)); + if (m_keyboardRect != keyboardRectangle) { + m_keyboardRect = keyboardRectangle; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitKeyboardRectChanged(); + } +} + +void QWaylandTextInputMethod::text_input_method_v1_start_input_method_event(uint32_t serial, int32_t surrounding_text_offset) +{ + if (m_pendingInputMethodEvents.contains(serial)) { + qCWarning(qLcQpaInputMethods) << "Input method event with serial" << serial << "already started"; + return; + } + + m_pendingInputMethodEvents[serial] = QList{}; + m_offsetFromCompositor[serial] = surrounding_text_offset; +} + +// We need to keep surrounding text below maxStringSize characters, with cursorPos centered in that substring + +static int calculateOffset(const QString &text, int cursorPos) +{ + int size = text.size(); + int halfSize = maxStringSize/2; + if (size <= maxStringSize || cursorPos < halfSize) + return 0; + if (cursorPos > size - halfSize) + return size - maxStringSize; + return cursorPos - halfSize; +} + +static QString mapSurroundingTextToCompositor(const QString &s, int offset) +{ + return s.mid(offset, maxStringSize); +} + +static int mapPositionToCompositor(int pos, int offset) +{ + return pos - offset; +} + +static int mapPositionFromCompositor(int pos, int offset) +{ + return pos + offset; +} + +void QWaylandTextInputMethod::text_input_method_v1_input_method_event_attribute(uint32_t serial, int32_t type, int32_t start, int32_t length, const QString &value) +{ + if (!m_pendingInputMethodEvents.contains(serial)) { + qCWarning(qLcQpaInputMethods) << "Input method event with serial" << serial << "does not exist"; + return; + } + + int startMapped = mapPositionFromCompositor(start, m_offsetFromCompositor[serial]); + QList &attributes = m_pendingInputMethodEvents[serial]; + switch (type) { + case QInputMethodEvent::Selection: + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), startMapped, length)); + break; + case QInputMethodEvent::Cursor: + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), start, length, QColor::fromString(value))); + break; + case QInputMethodEvent::TextFormat: + { + QTextCharFormat textFormat; + textFormat.setProperty(QTextFormat::FontUnderline, true); + textFormat.setProperty(QTextFormat::TextUnderlineStyle, QTextCharFormat::SingleUnderline); + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), start, length, textFormat)); + break; + } + case QInputMethodEvent::Language: + case QInputMethodEvent::Ruby: + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), start, length, value)); + break; + }; +} + +void QWaylandTextInputMethod::sendInputState(QInputMethodQueryEvent *event, Qt::InputMethodQueries queries) +{ + int cursorPosition = event->value(Qt::ImCursorPosition).toInt(); + int anchorPosition = event->value(Qt::ImAnchorPosition).toInt(); + QString surroundingText = event->value(Qt::ImSurroundingText).toString(); + int offset = calculateOffset(surroundingText, cursorPosition); + + if (queries & Qt::ImCursorPosition) + update_cursor_position(mapPositionToCompositor(cursorPosition, offset)); + if (queries & Qt::ImSurroundingText) + update_surrounding_text(mapSurroundingTextToCompositor(surroundingText, offset), offset); + if (queries & Qt::ImAnchorPosition) + update_anchor_position(mapPositionToCompositor(anchorPosition, offset)); + if (queries & Qt::ImAbsolutePosition) + update_absolute_position(event->value(Qt::ImAbsolutePosition).toInt()); // do not map: this is the position in the whole document +} + + +void QWaylandTextInputMethod::text_input_method_v1_end_input_method_event(uint32_t serial, const QString &commitString, const QString &preeditString, int32_t replacementStart, int32_t replacementLength) +{ + if (!m_pendingInputMethodEvents.contains(serial)) { + qCWarning(qLcQpaInputMethods) << "Input method event with serial" << serial << "does not exist"; + return; + } + + QList attributes = m_pendingInputMethodEvents.take(serial); + m_offsetFromCompositor.remove(serial); + if (QGuiApplication::focusObject() != nullptr) { + QInputMethodEvent event(preeditString, attributes); + event.setCommitString(commitString, replacementStart, replacementLength); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + } + + // Send current state to make sure it matches + if (QGuiApplication::focusObject() != nullptr) { + QInputMethodQueryEvent event(Qt::ImCursorPosition | Qt::ImSurroundingText | Qt::ImAnchorPosition | Qt::ImAbsolutePosition); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + sendInputState(&event); + } + + acknowledge_input_method(); +} + +void QWaylandTextInputMethod::text_input_method_v1_key(int32_t type, + int32_t key, + int32_t modifiers, + int32_t autoRepeat, + int32_t count, + int32_t nativeScanCode, + int32_t nativeVirtualKey, + int32_t nativeModifiers, + const QString &text) +{ + if (QGuiApplication::focusObject() != nullptr) { + QKeyEvent event(QKeyEvent::Type(type), + key, + Qt::KeyboardModifiers(modifiers), + nativeScanCode, + nativeVirtualKey, + nativeModifiers, + text, + autoRepeat, + count); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + } +} + +void QWaylandTextInputMethod::text_input_method_v1_enter(struct ::wl_surface *surface) +{ + m_surface = surface; +} + +void QWaylandTextInputMethod::text_input_method_v1_leave(struct ::wl_surface *surface) +{ + if (surface != m_surface) { + qCWarning(qLcQpaInputMethods) << "Got leave event for surface without corresponding enter"; + } else { + m_surface = nullptr; + } +} + +QWaylandInputMethodContext::QWaylandInputMethodContext(QWaylandDisplay *display) + : m_display(display) +{ +} + +QWaylandInputMethodContext::~QWaylandInputMethodContext() +{ +} + +bool QWaylandInputMethodContext::isValid() const +{ + return m_display->textInputMethodManager() != nullptr; +} + +void QWaylandInputMethodContext::reset() +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + inputMethod->reset(); +} + +void QWaylandInputMethodContext::commit() +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + inputMethod->commit(); + + m_display->forceRoundTrip(); +} + +void QWaylandInputMethodContext::update(Qt::InputMethodQueries queries) +{ + wl_surface *currentSurface = m_currentWindow != nullptr && m_currentWindow->handle() != nullptr + ? static_cast(m_currentWindow->handle())->wlSurface() + : nullptr; + if (currentSurface != nullptr && !inputMethodAccepted()) { + textInputMethod()->disable(currentSurface); + m_currentWindow.clear(); + } else if (currentSurface == nullptr && inputMethodAccepted()) { + QWindow *window = QGuiApplication::focusWindow(); + currentSurface = window != nullptr && window->handle() != nullptr + ? static_cast(window->handle())->wlSurface() + : nullptr; + if (currentSurface != nullptr) { + textInputMethod()->disable(currentSurface); + m_currentWindow = window; + } + } + + queries &= (Qt::ImEnabled + | Qt::ImHints + | Qt::ImCursorRectangle + | Qt::ImCursorPosition + | Qt::ImSurroundingText + | Qt::ImCurrentSelection + | Qt::ImAnchorPosition + | Qt::ImTextAfterCursor + | Qt::ImTextBeforeCursor + | Qt::ImPreferredLanguage); + + const Qt::InputMethodQueries queriesNeedingOffset = Qt::ImCursorPosition | Qt::ImSurroundingText | Qt::ImAnchorPosition; + if (queries & queriesNeedingOffset) + queries |= queriesNeedingOffset; + + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr && QGuiApplication::focusObject() != nullptr) { + QInputMethodQueryEvent event(queries); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + + inputMethod->start_update(int(queries)); + + if (queries & Qt::ImHints) + inputMethod->update_hints(event.value(Qt::ImHints).toInt()); + + if (queries & Qt::ImCursorRectangle) { + QRect rect = event.value(Qt::ImCursorRectangle).toRect(); + inputMethod->update_cursor_rectangle(rect.x(), rect.y(), rect.width(), rect.height()); + } + + inputMethod->sendInputState(&event, queries); + + if (queries & Qt::ImPreferredLanguage) + inputMethod->update_preferred_language(event.value(Qt::ImPreferredLanguage).toString()); + + inputMethod->end_update(); + + // ### Should we do a display sync here and ignore all events until it is received? + } +} + +void QWaylandInputMethodContext::invokeAction(QInputMethod::Action action, int cursorPosition) +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + inputMethod->invoke_action(int(action), cursorPosition); +} + +void QWaylandInputMethodContext::showInputPanel() +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + inputMethod->show_input_panel(); +} + +void QWaylandInputMethodContext::hideInputPanel() +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + inputMethod->hide_input_panel(); +} + +bool QWaylandInputMethodContext::isInputPanelVisible() const +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + return inputMethod->isVisible(); + else + return false; +} + +QRectF QWaylandInputMethodContext::keyboardRect() const +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + return inputMethod->keyboardRect(); + else + return QRectF(); +} + +QLocale QWaylandInputMethodContext::locale() const +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + return inputMethod->locale(); + else + return QLocale(); +} + +Qt::LayoutDirection QWaylandInputMethodContext::inputDirection() const +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + return inputMethod->inputDirection(); + else + return Qt::LeftToRight; +} + +void QWaylandInputMethodContext::setFocusObject(QObject *) +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod == nullptr) + return; + + if (inputMethod->isVisible() && !inputMethodAccepted()) + inputMethod->hide_input_panel(); + + QWindow *window = QGuiApplication::focusWindow(); + + if (m_currentWindow != nullptr && m_currentWindow->handle() != nullptr) { + if (m_currentWindow.data() != window || !inputMethodAccepted()) { + auto *surface = static_cast(m_currentWindow->handle())->wlSurface(); + if (surface) + inputMethod->disable(surface); + m_currentWindow.clear(); + } + } + + if (window != nullptr && window->handle() != nullptr && inputMethodAccepted()) { + if (m_currentWindow.data() != window) { + auto *surface = static_cast(window->handle())->wlSurface(); + if (surface != nullptr) { + inputMethod->enable(surface); + m_currentWindow = window; + } + } + + update(Qt::ImQueryAll); + } +} + +QWaylandTextInputMethod *QWaylandInputMethodContext::textInputMethod() const +{ + return m_display->defaultInputDevice() ? m_display->defaultInputDevice()->textInputMethod() : nullptr; +} + +} // QtWaylandClient + +QT_END_NAMESPACE + +#include "moc_qwaylandinputmethodcontext_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylandinputmethodcontext_p.h b/src/plugins/platforms/wayland/qwaylandinputmethodcontext_p.h new file mode 100644 index 00000000000..85ef656018c --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandinputmethodcontext_p.h @@ -0,0 +1,118 @@ +// Copyright (C) 2022 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 + +#ifndef QWAYLANDINPUTMETHODCONTEXT_P_H +#define QWAYLANDINPUTMETHODCONTEXT_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + class QWaylandDisplay; + +class QWaylandTextInputMethod : public QtWayland::qt_text_input_method_v1 +{ +public: + QWaylandTextInputMethod(QWaylandDisplay *display, struct ::qt_text_input_method_v1 *textInputMethod); + ~QWaylandTextInputMethod() override; + + void text_input_method_v1_visible_changed(int32_t visible) override; + void text_input_method_v1_enter(struct ::wl_surface *surface) override; + void text_input_method_v1_leave(struct ::wl_surface *surface) override; + void text_input_method_v1_locale_changed(const QString &localeName) override; + void text_input_method_v1_input_direction_changed(int32_t inputDirection) override; + void text_input_method_v1_keyboard_rectangle_changed(wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) override; + void text_input_method_v1_key(int32_t type, int32_t key, int32_t modifiers, int32_t autoRepeat, int32_t count, int32_t nativeScanCode, int32_t nativeVirtualKey, int32_t nativeModifiers, const QString &text) override; + void text_input_method_v1_start_input_method_event(uint32_t serial, int32_t surrounding_text_offset) override; + void text_input_method_v1_end_input_method_event(uint32_t serial, const QString &commitString, const QString &preeditString, int32_t replacementStart, int32_t replacementLength) override; + void text_input_method_v1_input_method_event_attribute(uint32_t serial, int32_t type, int32_t start, int32_t length, const QString &value) override; + + inline bool isVisible() const + { + return m_isVisible; + } + + inline QRectF keyboardRect() const + { + return m_keyboardRect; + } + + inline QLocale locale() const + { + return m_locale; + } + + inline Qt::LayoutDirection inputDirection() const + { + return m_layoutDirection; + } + + void sendInputState(QInputMethodQueryEvent *state, Qt::InputMethodQueries queries = Qt::ImQueryInput); + +private: + QHash > m_pendingInputMethodEvents; + QHash m_offsetFromCompositor; + + struct ::wl_surface *m_surface; + + // Cached state + bool m_isVisible = false; + QRectF m_keyboardRect; + QLocale m_locale; + Qt::LayoutDirection m_layoutDirection; +}; + +class QWaylandInputMethodContext : public QPlatformInputContext +{ + Q_OBJECT +public: + QWaylandInputMethodContext(QWaylandDisplay *display); + ~QWaylandInputMethodContext() override; + + bool isValid() const override; + void reset() override; + void commit() override; + void update(Qt::InputMethodQueries) override; + void invokeAction(QInputMethod::Action, int cursorPosition) override; + void showInputPanel() override; + void hideInputPanel() override; + + bool isInputPanelVisible() const override; + QRectF keyboardRect() const override; + QLocale locale() const override; + Qt::LayoutDirection inputDirection() const override; + + void setFocusObject(QObject *object) override; + +private: + QWaylandTextInputMethod *textInputMethod() const; + + QWaylandDisplay *m_display; + QPointer m_currentWindow; +}; + +} // QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDINPUTMETHODCONTEXT_P_H diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp new file mode 100644 index 00000000000..b0033876bba --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp @@ -0,0 +1,556 @@ +// Copyright (C) 2016 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 + +#include "qwaylandintegration_p.h" + +#include "qwaylanddisplay_p.h" +#include "qwaylandshmwindow_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylandinputcontext_p.h" +#include "qwaylandinputmethodcontext_p.h" +#include "qwaylandshmbackingstore_p.h" +#include "qwaylandnativeinterface_p.h" +#if QT_CONFIG(clipboard) +#include "qwaylandclipboard_p.h" +#endif +#include "qwaylanddnd_p.h" +#include "qwaylandwindowmanagerintegration_p.h" +#include "qwaylandplatformservices_p.h" +#include "qwaylandscreen_p.h" +#include "qwaylandcursor_p.h" + +#if defined(Q_OS_MACOS) +# include +# include +#else +# include +#endif +#include +#if __has_include() +#include +#else +#include +#endif + +#include + +#include +#include +#include +#if QT_CONFIG(opengl) +#include +#endif // QT_CONFIG(opengl) +#include + +#include +#include +#include + +#include "qwaylandhardwareintegration_p.h" +#include "qwaylandclientbufferintegration_p.h" +#include "qwaylandclientbufferintegrationfactory_p.h" + +#include "qwaylandserverbufferintegration_p.h" +#include "qwaylandserverbufferintegrationfactory_p.h" +#include "qwaylandshellsurface_p.h" + +#include "qwaylandshellintegration_p.h" +#include "qwaylandshellintegrationfactory_p.h" + +#include "qwaylandinputdeviceintegration_p.h" +#include "qwaylandinputdeviceintegrationfactory_p.h" +#include "qwaylandwindow_p.h" + +#include + +#if QT_CONFIG(accessibility_atspi_bridge) +#include +#endif + +#if QT_CONFIG(xkbcommon) +#include +#endif + +#if QT_CONFIG(vulkan) +#include "qwaylandvulkaninstance_p.h" +#include "qwaylandvulkanwindow_p.h" +#endif + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace QtWaylandClient { + +QWaylandIntegration *QWaylandIntegration::sInstance = nullptr; + +QWaylandIntegration::QWaylandIntegration(const QString &platformName) +#if defined(Q_OS_MACOS) + : mFontDb(new QCoreTextFontDatabaseEngineFactory) +#else + : mPlatformName(platformName), mFontDb(new QGenericUnixFontDatabase()) +#endif +{ + mDisplay.reset(new QWaylandDisplay(this)); + mPlatformServices.reset(new QWaylandPlatformServices(mDisplay.data())); + + QWaylandWindow::fixedToplevelPositions = + !qEnvironmentVariableIsSet("QT_WAYLAND_DISABLE_FIXED_POSITIONS"); + + sInstance = this; + if (platformName != "wayland"_L1) + initializeClientBufferIntegration(); +} + +QWaylandIntegration::~QWaylandIntegration() +{ + sInstance = nullptr; +} + +bool QWaylandIntegration::init() +{ + return mDisplay->initialize(); +} + +QPlatformNativeInterface * QWaylandIntegration::nativeInterface() const +{ + return mNativeInterface.data(); +} + +bool QWaylandIntegration::hasCapability(QPlatformIntegration::Capability cap) const +{ + switch (cap) { + case ThreadedPixmaps: return true; + case OpenGL: + return mDisplay->clientBufferIntegration(); + case ThreadedOpenGL: + return mDisplay->clientBufferIntegration() && mDisplay->clientBufferIntegration()->supportsThreadedOpenGL(); + case BufferQueueingOpenGL: + return true; + case MultipleWindows: + case NonFullScreenWindows: + return true; + case RasterGLSurface: + return true; + case WindowActivation: + return true; + case ScreenWindowGrabbing: // whether QScreen::grabWindow() is supported + return false; + default: return QPlatformIntegration::hasCapability(cap); + } +} + +QPlatformWindow *QWaylandIntegration::createPlatformWindow(QWindow *window) const +{ + if ((window->surfaceType() == QWindow::OpenGLSurface || window->surfaceType() == QWindow::RasterGLSurface) + && mDisplay->clientBufferIntegration()) + return mDisplay->clientBufferIntegration()->createEglWindow(window); + +#if QT_CONFIG(vulkan) + if (window->surfaceType() == QSurface::VulkanSurface) + return new QWaylandVulkanWindow(window, mDisplay.data()); +#endif // QT_CONFIG(vulkan) + + return new QWaylandShmWindow(window, mDisplay.data()); +} + +#if QT_CONFIG(opengl) +QPlatformOpenGLContext *QWaylandIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const +{ + if (mDisplay->clientBufferIntegration()) + return mDisplay->clientBufferIntegration()->createPlatformOpenGLContext(context->format(), context->shareHandle()); + return nullptr; +} +#endif // opengl + +QPlatformBackingStore *QWaylandIntegration::createPlatformBackingStore(QWindow *window) const +{ + return new QWaylandShmBackingStore(window, mDisplay.data()); +} + +QAbstractEventDispatcher *QWaylandIntegration::createEventDispatcher() const +{ + return createUnixEventDispatcher(); +} + +QPlatformNativeInterface *QWaylandIntegration::createPlatformNativeInterface() +{ + return new QWaylandNativeInterface(this); +} + +// Support platform specific initialization +void QWaylandIntegration::initializePlatform() +{ + mDisplay->initEventThread(); + + mNativeInterface.reset(createPlatformNativeInterface()); + initializeInputDeviceIntegration(); +#if QT_CONFIG(clipboard) + mClipboard.reset(new QWaylandClipboard(mDisplay.data())); +#endif +#if QT_CONFIG(draganddrop) + mDrag.reset(new QWaylandDrag(mDisplay.data())); +#endif + + reconfigureInputContext(); +} + +void QWaylandIntegration::initialize() +{ + initializePlatform(); + + // Call this after initializing event thread for QWaylandDisplay::flushRequests() + QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::eventDispatcher; + QObject::connect(dispatcher, SIGNAL(aboutToBlock()), mDisplay.data(), SLOT(flushRequests())); + QObject::connect(dispatcher, SIGNAL(awake()), mDisplay.data(), SLOT(flushRequests())); + + // Qt does not support running with no screens + mDisplay->ensureScreen(); +} + +QPlatformFontDatabase *QWaylandIntegration::fontDatabase() const +{ + return mFontDb.data(); +} + +#if QT_CONFIG(clipboard) +QPlatformClipboard *QWaylandIntegration::clipboard() const +{ + return mClipboard.data(); +} +#endif + +#if QT_CONFIG(draganddrop) +QPlatformDrag *QWaylandIntegration::drag() const +{ + return mDrag.data(); +} +#endif // draganddrop + +QPlatformInputContext *QWaylandIntegration::inputContext() const +{ + return mInputContext.data(); +} + +QVariant QWaylandIntegration::styleHint(StyleHint hint) const +{ + if (hint == ShowIsFullScreen && mDisplay->windowManagerIntegration()) + return mDisplay->windowManagerIntegration()->showIsFullScreen(); + + return QPlatformIntegration::styleHint(hint); +} + +#if QT_CONFIG(accessibility) +QPlatformAccessibility *QWaylandIntegration::accessibility() const +{ + if (!mAccessibility) { +#if QT_CONFIG(accessibility_atspi_bridge) + Q_ASSERT_X(QCoreApplication::eventDispatcher(), "QWaylandIntegration", + "Initializing accessibility without event-dispatcher!"); + mAccessibility.reset(new QSpiAccessibleBridge()); +#else + mAccessibility.reset(new QPlatformAccessibility()); +#endif + } + return mAccessibility.data(); +} +#endif + +QPlatformServices *QWaylandIntegration::services() const +{ + return mPlatformServices.data(); +} + +QWaylandDisplay *QWaylandIntegration::display() const +{ + return mDisplay.data(); +} + +Qt::KeyboardModifiers QWaylandIntegration::queryKeyboardModifiers() const +{ + if (auto *seat = mDisplay->currentInputDevice(); seat && seat->keyboardFocus()) { + return seat->modifiers(); + } + return Qt::NoModifier; +} + +QList QWaylandIntegration::possibleKeys(const QKeyEvent *event) const +{ + if (auto *seat = mDisplay->currentInputDevice()) + return seat->possibleKeys(event); + return {}; +} + +QStringList QWaylandIntegration::themeNames() const +{ + return QGenericUnixTheme::themeNames(); +} + +QPlatformTheme *QWaylandIntegration::createPlatformTheme(const QString &name) const +{ + return QGenericUnixTheme::createUnixTheme(name); +} + +QWaylandScreen *QWaylandIntegration::createPlatformScreen(QWaylandDisplay *waylandDisplay, int version, uint32_t id) const +{ + return new QWaylandScreen(waylandDisplay, version, id); +} + +QWaylandCursor *QWaylandIntegration::createPlatformCursor(QWaylandDisplay *display) const +{ + return new QWaylandCursor(display); +} + +#if QT_CONFIG(vulkan) +QPlatformVulkanInstance *QWaylandIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const +{ + return new QWaylandVulkanInstance(instance); +} +#endif // QT_CONFIG(vulkan) + +// May be called from non-GUI threads +QWaylandClientBufferIntegration *QWaylandIntegration::clientBufferIntegration() const +{ + // Do an inexpensive check first to avoid locking whenever possible + if (Q_UNLIKELY(!mClientBufferIntegrationInitialized)) + const_cast(this)->initializeClientBufferIntegration(); + + Q_ASSERT(mClientBufferIntegrationInitialized); + return mClientBufferIntegration && mClientBufferIntegration->isValid() ? mClientBufferIntegration.data() : nullptr; +} + +QWaylandServerBufferIntegration *QWaylandIntegration::serverBufferIntegration() const +{ + if (!mServerBufferIntegrationInitialized) + const_cast(this)->initializeServerBufferIntegration(); + + return mServerBufferIntegration.data(); +} + +QWaylandShellIntegration *QWaylandIntegration::shellIntegration() const +{ + if (!mShellIntegrationInitialized) + const_cast(this)->initializeShellIntegration(); + + return mShellIntegration.data(); +} + +// May be called from non-GUI threads +void QWaylandIntegration::initializeClientBufferIntegration() +{ + QMutexLocker lock(&mClientBufferInitLock); + if (mClientBufferIntegrationInitialized) + return; + + QString targetKey = QString::fromLocal8Bit(qgetenv("QT_WAYLAND_CLIENT_BUFFER_INTEGRATION")); + if (mPlatformName == "wayland-egl"_L1) + targetKey = "wayland-egl"_L1; + else if (mPlatformName == "wayland-brcm"_L1) + targetKey = "brcm"_L1; + + if (targetKey.isEmpty()) { + if (mDisplay->hardwareIntegration() + && mDisplay->hardwareIntegration()->clientBufferIntegration() != QLatin1String("wayland-eglstream-controller") + && mDisplay->hardwareIntegration()->clientBufferIntegration() != QLatin1String("linux-dmabuf-unstable-v1")) { + targetKey = mDisplay->hardwareIntegration()->clientBufferIntegration(); + } else { + targetKey = QLatin1String("wayland-egl"); + } + } + + if (targetKey.isEmpty()) { + qWarning("Failed to determine what client buffer integration to use"); + } else { + QStringList keys = QWaylandClientBufferIntegrationFactory::keys(); + qCDebug(lcQpaWayland) << "Available client buffer integrations:" << keys; + + if (keys.contains(targetKey)) + mClientBufferIntegration.reset(QWaylandClientBufferIntegrationFactory::create(targetKey, QStringList())); + + if (mClientBufferIntegration) { + qCDebug(lcQpaWayland) << "Initializing client buffer integration" << targetKey; + mClientBufferIntegration->initialize(mDisplay.data()); + } else { + qCWarning(lcQpaWayland) << "Failed to load client buffer integration:" << targetKey; + qCWarning(lcQpaWayland) << "Available client buffer integrations:" << keys; + } + } + + // This must be set last to make sure other threads don't use the + // integration before initialization is complete. + mClientBufferIntegrationInitialized = true; +} + +void QWaylandIntegration::initializeServerBufferIntegration() +{ + mServerBufferIntegrationInitialized = true; + + QString targetKey = QString::fromLocal8Bit(qgetenv("QT_WAYLAND_SERVER_BUFFER_INTEGRATION")); + + if (targetKey.isEmpty() && mDisplay->hardwareIntegration()) + targetKey = mDisplay->hardwareIntegration()->serverBufferIntegration(); + + if (targetKey.isEmpty()) { + qWarning("Failed to determine what server buffer integration to use"); + return; + } + + QStringList keys = QWaylandServerBufferIntegrationFactory::keys(); + qCDebug(lcQpaWayland) << "Available server buffer integrations:" << keys; + + if (keys.contains(targetKey)) + mServerBufferIntegration.reset(QWaylandServerBufferIntegrationFactory::create(targetKey, QStringList())); + + if (mServerBufferIntegration) { + qCDebug(lcQpaWayland) << "Initializing server buffer integration" << targetKey; + mServerBufferIntegration->initialize(mDisplay.data()); + } else { + qCWarning(lcQpaWayland) << "Failed to load server buffer integration: " << targetKey; + qCWarning(lcQpaWayland) << "Available server buffer integrations:" << keys; + } +} + +void QWaylandIntegration::initializeShellIntegration() +{ + mShellIntegrationInitialized = true; + + QByteArray integrationNames = qgetenv("QT_WAYLAND_SHELL_INTEGRATION"); + QString targetKeys = QString::fromLocal8Bit(integrationNames); + + QStringList preferredShells; + if (!targetKeys.isEmpty()) { + preferredShells = targetKeys.split(QLatin1Char(';')); + } else { + preferredShells << QLatin1String("xdg-shell"); + preferredShells << QLatin1String("wl-shell") << QLatin1String("ivi-shell"); + preferredShells << QLatin1String("qt-shell"); + } + + for (const QString &preferredShell : std::as_const(preferredShells)) { + mShellIntegration.reset(createShellIntegration(preferredShell)); + if (mShellIntegration) { + qCDebug(lcQpaWayland, "Using the '%s' shell integration", qPrintable(preferredShell)); + break; + } + } + + if (!mShellIntegration) { + qCWarning(lcQpaWayland) << "Loading shell integration failed."; + qCWarning(lcQpaWayland) << "Attempted to load the following shells" << preferredShells; + } + + QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); +} + +QWaylandInputDevice *QWaylandIntegration::createInputDevice(QWaylandDisplay *display, int version, uint32_t id) const +{ + if (mInputDeviceIntegration) { + return mInputDeviceIntegration->createInputDevice(display, version, id); + } + return new QWaylandInputDevice(display, version, id); +} + +void QWaylandIntegration::initializeInputDeviceIntegration() +{ + QByteArray integrationName = qgetenv("QT_WAYLAND_INPUTDEVICE_INTEGRATION"); + QString targetKey = QString::fromLocal8Bit(integrationName); + + if (targetKey.isEmpty()) { + return; + } + + QStringList keys = QWaylandInputDeviceIntegrationFactory::keys(); + if (keys.contains(targetKey)) { + mInputDeviceIntegration.reset(QWaylandInputDeviceIntegrationFactory::create(targetKey, QStringList())); + qDebug("Using the '%s' input device integration", qPrintable(targetKey)); + } else { + qWarning("Wayland inputdevice integration '%s' not found, using default", qPrintable(targetKey)); + } +} + +void QWaylandIntegration::reconfigureInputContext() +{ + if (!mDisplay) { + // This function can be called from QWaylandDisplay::registry_global() when we + // are in process of constructing QWaylandDisplay. Configuring input context + // in that case is done by calling reconfigureInputContext() from QWaylandIntegration + // constructor, after QWaylandDisplay has been constructed. + return; + } + + auto requested = QPlatformInputContextFactory::requested(); + if (requested.contains(QLatin1String("qtvirtualkeyboard"))) + qCWarning(lcQpaWayland) << "qtvirtualkeyboard currently is not supported at client-side," + " use QT_IM_MODULES=qtvirtualkeyboard at compositor-side."; + + if (mDisplay->isWaylandInputContextRequested() + && !requested.contains(QLatin1String(WAYLAND_IM_KEY))) + requested.append(QLatin1String(WAYLAND_IM_KEY)); + + const QString defaultInputContext(QStringLiteral("compose")); + if (!requested.contains(defaultInputContext)) + requested.append(defaultInputContext); + + for (const QString &imKey : requested) { + if (imKey == QLatin1String(WAYLAND_IM_KEY)) { + Q_ASSERT(mDisplay->isWaylandInputContextRequested()); + if (mDisplay->textInputMethodManager() != nullptr) + mInputContext.reset(new QWaylandInputMethodContext(mDisplay.data())); + else if (mDisplay->textInputManagerv1() != nullptr + || mDisplay->textInputManagerv2() != nullptr + || mDisplay->textInputManagerv3() != nullptr) + mInputContext.reset(new QWaylandInputContext(mDisplay.data())); + } else { + mInputContext.reset(QPlatformInputContextFactory::create(imKey)); + } + + if (mInputContext && mInputContext->isValid()) + break; + } + +#if QT_CONFIG(xkbcommon) + QXkbCommon::setXkbContext(mInputContext.data(), mDisplay->xkbContext()); + if (QWaylandInputContext* waylandInput = qobject_cast(mInputContext.get())) { + waylandInput->setXkbContext(mDisplay->xkbContext()); + } +#endif + + qCDebug(lcQpaWayland) << "using input method:" << (inputContext() ? inputContext()->metaObject()->className() : ""); +} + +QWaylandShellIntegration *QWaylandIntegration::createShellIntegration(const QString &integrationName) +{ + if (QWaylandShellIntegrationFactory::keys().contains(integrationName)) { + return QWaylandShellIntegrationFactory::create(integrationName, mDisplay.data()); + } else { + qCWarning(lcQpaWayland) << "No shell integration named" << integrationName << "found"; + return nullptr; + } +} + +void QWaylandIntegration::reset() +{ + mServerBufferIntegration.reset(); + mServerBufferIntegrationInitialized = false; + + mInputDeviceIntegration.reset(); + + mClientBufferIntegration.reset(); + mClientBufferIntegrationInitialized = false; +} + +void QWaylandIntegration::setApplicationBadge(qint64 number) +{ + mPlatformServices->setApplicationBadge(number); +} + +void QWaylandIntegration::beep() const +{ + if (auto bell = mDisplay->systemBell()) { + bell->ring(nullptr); + } +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandintegration_p.h b/src/plugins/platforms/wayland/qwaylandintegration_p.h new file mode 100644 index 00000000000..e379668adb4 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandintegration_p.h @@ -0,0 +1,157 @@ +// 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 + +#ifndef QPLATFORMINTEGRATION_WAYLAND_H +#define QPLATFORMINTEGRATION_WAYLAND_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandBuffer; +class QWaylandDisplay; +class QWaylandClientBufferIntegration; +class QWaylandServerBufferIntegration; +class QWaylandShellIntegration; +class QWaylandInputDeviceIntegration; +class QWaylandInputDevice; +class QWaylandScreen; +class QWaylandCursor; +class QWaylandPlatformServices; + +class Q_WAYLANDCLIENT_EXPORT QWaylandIntegration : public QPlatformIntegration +{ +public: + QWaylandIntegration(const QString &platformName); + ~QWaylandIntegration() override; + + static QWaylandIntegration *instance() { return sInstance; } + + bool init(); + + bool hasCapability(QPlatformIntegration::Capability cap) const override; + QPlatformWindow *createPlatformWindow(QWindow *window) const override; +#if QT_CONFIG(opengl) + QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override; +#endif + QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; + + QAbstractEventDispatcher *createEventDispatcher() const override; + void initialize() override; + + QPlatformFontDatabase *fontDatabase() const override; + + QPlatformNativeInterface *nativeInterface() const override; +#if QT_CONFIG(clipboard) + QPlatformClipboard *clipboard() const override; +#endif +#if QT_CONFIG(draganddrop) + QPlatformDrag *drag() const override; +#endif + QPlatformInputContext *inputContext() const override; + + QVariant styleHint(StyleHint hint) const override; + +#if QT_CONFIG(accessibility) + QPlatformAccessibility *accessibility() const override; +#endif + + QPlatformServices *services() const override; + + QWaylandDisplay *display() const; + + Qt::KeyboardModifiers queryKeyboardModifiers() const override; + + QList possibleKeys(const QKeyEvent *event) const override; + + QStringList themeNames() const override; + + QPlatformTheme *createPlatformTheme(const QString &name) const override; + +#if QT_CONFIG(vulkan) + QPlatformVulkanInstance *createPlatformVulkanInstance(QVulkanInstance *instance) const override; +#endif + + void setApplicationBadge(qint64 number) override; + void beep() const override; + + virtual QWaylandInputDevice *createInputDevice(QWaylandDisplay *display, int version, uint32_t id) const; + virtual QWaylandScreen *createPlatformScreen(QWaylandDisplay *waylandDisplay, int version, uint32_t id) const; + virtual QWaylandCursor *createPlatformCursor(QWaylandDisplay *display) const; + + virtual QWaylandClientBufferIntegration *clientBufferIntegration() const; + virtual QWaylandServerBufferIntegration *serverBufferIntegration() const; + virtual QWaylandShellIntegration *shellIntegration() const; + + void reconfigureInputContext(); + +protected: + // NOTE: mDisplay *must* be destructed after mDrag and mClientBufferIntegration + // and mShellIntegration. + // Do not move this definition into the private section at the bottom. + QScopedPointer mDisplay; + +protected: + void reset(); + virtual QPlatformNativeInterface *createPlatformNativeInterface(); + + QScopedPointer mClientBufferIntegration; + QScopedPointer mServerBufferIntegration; + QScopedPointer mShellIntegration; + QScopedPointer mInputDeviceIntegration; + + QScopedPointer mInputContext; + +private: + void initializePlatform(); + void initializeClientBufferIntegration(); + void initializeServerBufferIntegration(); + void initializeShellIntegration(); + void initializeInputDeviceIntegration(); + QWaylandShellIntegration *createShellIntegration(const QString& interfaceName); + + const QString mPlatformName; + QScopedPointer mFontDb; +#if QT_CONFIG(clipboard) + QScopedPointer mClipboard; +#endif +#if QT_CONFIG(draganddrop) + QScopedPointer mDrag; +#endif + QScopedPointer mNativeInterface; +#if QT_CONFIG(accessibility) + mutable QScopedPointer mAccessibility; +#endif + QScopedPointer mPlatformServices; + QMutex mClientBufferInitLock; + bool mClientBufferIntegrationInitialized = false; + bool mServerBufferIntegrationInitialized = false; + bool mShellIntegrationInitialized = false; + + static QWaylandIntegration *sInstance; + + friend class QWaylandDisplay; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/qwaylandnativeinterface.cpp b/src/plugins/platforms/wayland/qwaylandnativeinterface.cpp new file mode 100644 index 00000000000..e1586d2446f --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandnativeinterface.cpp @@ -0,0 +1,241 @@ +// Copyright (C) 2016 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 + +#include "qwaylandnativeinterface_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandwindow_p.h" +#include "qwaylandshellintegration_p.h" +#include "qwaylandsubsurface_p.h" +#include "qwaylandintegration_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandwindowmanagerintegration_p.h" +#include "qwaylandscreen_p.h" +#include "qwaylandinputdevice_p.h" +#include +#include +#include +#include +#if QT_CONFIG(vulkan) +#include +#endif + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandNativeInterface::QWaylandNativeInterface(QWaylandIntegration *integration) + : m_integration(integration) +{ +} + +void *QWaylandNativeInterface::nativeResourceForIntegration(const QByteArray &resourceString) +{ + QByteArray lowerCaseResource = resourceString.toLower(); + + if (lowerCaseResource == "display" || lowerCaseResource == "wl_display" || lowerCaseResource == "nativedisplay") + return m_integration->display()->wl_display(); + if (lowerCaseResource == "compositor") { + if (auto compositor = m_integration->display()->compositor()) + return compositor->object(); + } + if (lowerCaseResource == "server_buffer_integration") + return m_integration->serverBufferIntegration(); + + if (lowerCaseResource == "egldisplay" && m_integration->clientBufferIntegration()) + return m_integration->clientBufferIntegration()->nativeResource(QWaylandClientBufferIntegration::EglDisplay); + + if (lowerCaseResource == "wl_seat") + return m_integration->display()->defaultInputDevice()->wl_seat(); + if (lowerCaseResource == "wl_keyboard") { + auto *keyboard = m_integration->display()->defaultInputDevice()->keyboard(); + if (keyboard) + return keyboard->wl_keyboard(); + return nullptr; + } + if (lowerCaseResource == "wl_pointer") { + auto *pointer = m_integration->display()->defaultInputDevice()->pointer(); + if (pointer) + return pointer->wl_pointer(); + return nullptr; + } + if (lowerCaseResource == "wl_touch") { + auto *touch = m_integration->display()->defaultInputDevice()->touch(); + if (touch) + return touch->wl_touch(); + return nullptr; + } + if (lowerCaseResource == "serial") + return reinterpret_cast(quintptr(m_integration->display()->defaultInputDevice()->serial())); + + return nullptr; +} + +wl_display *QtWaylandClient::QWaylandNativeInterface::display() const +{ + return m_integration->display()->wl_display(); +} + +wl_compositor *QtWaylandClient::QWaylandNativeInterface::compositor() const +{ + if (auto compositor = m_integration->display()->compositor()) + return compositor->object(); + return nullptr; +} + +wl_seat *QtWaylandClient::QWaylandNativeInterface::seat() const +{ + if (auto inputDevice = m_integration->display()->defaultInputDevice()) { + return inputDevice->wl_seat(); + } + return nullptr; +} + +wl_keyboard *QtWaylandClient::QWaylandNativeInterface::keyboard() const +{ + if (auto inputDevice = m_integration->display()->defaultInputDevice()) + if (auto keyboard = inputDevice->keyboard()) + return keyboard->wl_keyboard(); + return nullptr; +} + +wl_pointer *QtWaylandClient::QWaylandNativeInterface::pointer() const +{ + if (auto inputDevice = m_integration->display()->defaultInputDevice()) + if (auto pointer = inputDevice->pointer()) + return pointer->wl_pointer(); + return nullptr; +} + +wl_touch *QtWaylandClient::QWaylandNativeInterface::touch() const +{ + if (auto inputDevice = m_integration->display()->defaultInputDevice()) + if (auto touch = inputDevice->touch()) + return touch->wl_touch(); + return nullptr; +} + +uint QtWaylandClient::QWaylandNativeInterface::lastInputSerial() const +{ + return m_integration->display()->lastInputSerial(); +} + +wl_seat *QtWaylandClient::QWaylandNativeInterface::lastInputSeat() const +{ + if (auto inputDevice = m_integration->display()->lastInputDevice()) + return inputDevice->wl_seat(); + return nullptr; +} + +void *QWaylandNativeInterface::nativeResourceForWindow(const QByteArray &resourceString, QWindow *window) +{ + QByteArray lowerCaseResource = resourceString.toLower(); + + if (lowerCaseResource == "display") + return m_integration->display()->wl_display(); + if (lowerCaseResource == "compositor") { + if (auto compositor = m_integration->display()->compositor()) + return compositor->object(); + } + if (lowerCaseResource == "surface") { + QWaylandWindow *w = static_cast(window->handle()); + return w ? w->wlSurface() : nullptr; + } + + if (lowerCaseResource == "egldisplay" && m_integration->clientBufferIntegration()) + return m_integration->clientBufferIntegration()->nativeResource(QWaylandClientBufferIntegration::EglDisplay); + +#if QT_CONFIG(vulkan) + if (lowerCaseResource == "vksurface") { + if (window->surfaceType() == QSurface::VulkanSurface && window->handle()) { + // return a pointer to the VkSurfaceKHR value, not the value itself + return static_cast(window->handle())->vkSurface(); + } + } +#endif + + QWaylandWindow *platformWindow = static_cast(window->handle()); + if (platformWindow && platformWindow->shellIntegration()) + return platformWindow->shellIntegration()->nativeResourceForWindow(resourceString, window); + + return nullptr; +} + +void *QWaylandNativeInterface::nativeResourceForScreen(const QByteArray &resourceString, QScreen *screen) +{ + QByteArray lowerCaseResource = resourceString.toLower(); + + if (lowerCaseResource == "output" && !screen->handle()->isPlaceholder()) + return ((QWaylandScreen *) screen->handle())->output(); + + return nullptr; +} + +#if QT_CONFIG(opengl) +void *QWaylandNativeInterface::nativeResourceForContext(const QByteArray &resource, QOpenGLContext *context) +{ +#if QT_CONFIG(opengl) + QByteArray lowerCaseResource = resource.toLower(); + + if (lowerCaseResource == "eglconfig" && m_integration->clientBufferIntegration()) + return m_integration->clientBufferIntegration()->nativeResourceForContext(QWaylandClientBufferIntegration::EglConfig, context->handle()); + + if (lowerCaseResource == "eglcontext" && m_integration->clientBufferIntegration()) + return m_integration->clientBufferIntegration()->nativeResourceForContext(QWaylandClientBufferIntegration::EglContext, context->handle()); + + if (lowerCaseResource == "egldisplay" && m_integration->clientBufferIntegration()) + return m_integration->clientBufferIntegration()->nativeResourceForContext(QWaylandClientBufferIntegration::EglDisplay, context->handle()); +#endif + + return nullptr; +} +#endif // opengl + +QPlatformNativeInterface::NativeResourceForWindowFunction QWaylandNativeInterface::nativeResourceFunctionForWindow(const QByteArray &resource) +{ + QByteArray lowerCaseResource = resource.toLower(); + + if (lowerCaseResource == "setmargins") { + return NativeResourceForWindowFunction(reinterpret_cast(setWindowMargins)); + } + + return nullptr; +} + +QVariantMap QWaylandNativeInterface::windowProperties(QPlatformWindow *window) const +{ + QWaylandWindow *waylandWindow = static_cast(window); + return waylandWindow->properties(); +} + +QVariant QWaylandNativeInterface::windowProperty(QPlatformWindow *window, const QString &name) const +{ + QWaylandWindow *waylandWindow = static_cast(window); + return waylandWindow->property(name); +} + +QVariant QWaylandNativeInterface::windowProperty(QPlatformWindow *window, const QString &name, const QVariant &defaultValue) const +{ + QWaylandWindow *waylandWindow = static_cast(window); + return waylandWindow->property(name, defaultValue); +} + +void QWaylandNativeInterface::setWindowProperty(QPlatformWindow *window, const QString &name, const QVariant &value) +{ + QWaylandWindow *wlWindow = static_cast(window); + wlWindow->sendProperty(name, value); +} + +void QWaylandNativeInterface::emitWindowPropertyChanged(QPlatformWindow *window, const QString &name) +{ + emit windowPropertyChanged(window,name); +} + +void QWaylandNativeInterface::setWindowMargins(QWindow *window, const QMargins &margins) +{ + QWaylandWindow *wlWindow = static_cast(window->handle()); + wlWindow->setCustomMargins(margins); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandnativeinterface_p.h b/src/plugins/platforms/wayland/qwaylandnativeinterface_p.h new file mode 100644 index 00000000000..ce8c6bec3fd --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandnativeinterface_p.h @@ -0,0 +1,77 @@ +// Copyright (C) 2020 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 + +#ifndef QWAYLANDNATIVEINTERFACE_P_H +#define QWAYLANDNATIVEINTERFACE_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 + +QT_BEGIN_NAMESPACE + +class QMargins; + +namespace QtWaylandClient { + +class QWaylandIntegration; +class QWaylandScreen; + +class Q_WAYLANDCLIENT_EXPORT QWaylandNativeInterface : public QPlatformNativeInterface, + public QNativeInterface::QWaylandApplication +{ +public: + QWaylandNativeInterface(QWaylandIntegration *integration); + void *nativeResourceForIntegration(const QByteArray &resource) override; + void *nativeResourceForWindow(const QByteArray &resourceString, + QWindow *window) override; + void *nativeResourceForScreen(const QByteArray &resourceString, + QScreen *screen) override; +#if QT_CONFIG(opengl) + void *nativeResourceForContext(const QByteArray &resource, QOpenGLContext *context) override; +#endif + NativeResourceForWindowFunction nativeResourceFunctionForWindow(const QByteArray &resource) override; + QVariantMap windowProperties(QPlatformWindow *window) const override; + QVariant windowProperty(QPlatformWindow *window, const QString &name) const override; + QVariant windowProperty(QPlatformWindow *window, const QString &name, const QVariant &defaultValue) const override; + void setWindowProperty(QPlatformWindow *window, const QString &name, const QVariant &value) override; + + void emitWindowPropertyChanged(QPlatformWindow *window, const QString &name); + + // QWaylandApplication interface + wl_display *display() const override; + wl_compositor *compositor() const override; + wl_seat *seat() const override; + wl_keyboard *keyboard() const override; + wl_pointer *pointer() const override; + wl_touch *touch() const override; + uint lastInputSerial() const override; + wl_seat *lastInputSeat() const override; + +private: + static void setWindowMargins(QWindow *window, const QMargins &margins); + + QWaylandIntegration *m_integration = nullptr; + QHash m_windowProperties; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDNATIVEINTERFACE_P_H diff --git a/src/plugins/platforms/wayland/qwaylandplatformservices.cpp b/src/plugins/platforms/wayland/qwaylandplatformservices.cpp new file mode 100644 index 00000000000..74d8c2e2e3b --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandplatformservices.cpp @@ -0,0 +1,84 @@ +// Copyright (C) 2016 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 + +#include "qwaylandappmenu_p.h" +#include "qwaylandplatformservices_p.h" +#include "qwaylandwindow_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandshellsurface_p.h" +#include "qwaylandwindowmanagerintegration_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandPlatformServices::QWaylandPlatformServices(QWaylandDisplay *display) + : m_display(display) { } + +QWaylandPlatformServices::~QWaylandPlatformServices() +{ + qDeleteAll(m_appMenus); +} + +bool QWaylandPlatformServices::openUrl(const QUrl &url) +{ + if (auto windowManagerIntegration = m_display->windowManagerIntegration()) { + windowManagerIntegration->openUrl(url); + return true; + } + return QDesktopUnixServices::openUrl(url); +} + +bool QWaylandPlatformServices::openDocument(const QUrl &url) +{ + if (auto windowManagerIntegration = m_display->windowManagerIntegration()) { + windowManagerIntegration->openUrl(url); + return true; + } + return QDesktopUnixServices::openDocument(url); +} + +QString QWaylandPlatformServices::portalWindowIdentifier(QWindow *window) +{ + if (window && window->handle()) { + auto shellSurface = static_cast(window->handle())->shellSurface(); + if (shellSurface) { + const QString handle = shellSurface->externWindowHandle(); + return QLatin1String("wayland:") + handle; + } + } + return QString(); +} + +void QWaylandPlatformServices::registerDBusMenuForWindow(QWindow *window, const QString &service, + const QString &path) +{ + if (!m_display->appMenuManager()) + return; + if (!window) + return; + if (!window->handle()) + window->create(); + auto waylandWindow = static_cast(window->handle()); + auto menu = *m_appMenus.insert(window, new QWaylandAppMenu); + + auto createAppMenu = [waylandWindow, menu, service, path] { + menu->init(waylandWindow->display()->appMenuManager()->create(waylandWindow->wlSurface())); + menu->set_address(service, path); + }; + + if (waylandWindow->wlSurface()) + createAppMenu(); + + QObject::connect(waylandWindow, &QWaylandWindow::wlSurfaceCreated, menu, createAppMenu); + QObject::connect(waylandWindow, &QWaylandWindow::wlSurfaceDestroyed, menu, + [menu] { menu->release(); }); +} + +void QWaylandPlatformServices::unregisterDBusMenuForWindow(QWindow *window) +{ + delete m_appMenus.take(window); +} +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandplatformservices_p.h b/src/plugins/platforms/wayland/qwaylandplatformservices_p.h new file mode 100644 index 00000000000..017d222843b --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandplatformservices_p.h @@ -0,0 +1,54 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDPLATFORMSERVICES_H +#define QWAYLANDPLATFORMSERVICES_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandAppMenu; +class QWaylandDisplay; +class QWaylandWindow; + +class Q_WAYLANDCLIENT_EXPORT QWaylandPlatformServices : public QDesktopUnixServices +{ +public: + explicit QWaylandPlatformServices(QWaylandDisplay *waylandDisplay); + ~QWaylandPlatformServices(); + + bool openUrl(const QUrl &url) override; + bool openDocument(const QUrl &url) override; + QString portalWindowIdentifier(QWindow *window) override; + void registerDBusMenuForWindow(QWindow *window, const QString &service, + const QString &path) override; + void unregisterDBusMenuForWindow(QWindow *window) override; + +private: + QWaylandDisplay *m_display; + QMap m_appMenus; +}; + +QT_END_NAMESPACE + +} // namespace QtWaylandClient + +#endif // QWAYLANDPLATFORMSERVICES_H diff --git a/src/plugins/platforms/wayland/qwaylandpointergestures.cpp b/src/plugins/platforms/wayland/qwaylandpointergestures.cpp new file mode 100644 index 00000000000..87079d8009d --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandpointergestures.cpp @@ -0,0 +1,211 @@ +// Copyright (C) 2021 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 + +#include "qwaylandpointergestures_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandinputdevice_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandPointerGestures::QWaylandPointerGestures(QWaylandDisplay *display, uint id, uint version) + : zwp_pointer_gestures_v1(display->wl_registry(), id, qMin(version, uint(1))) +{ +} + +QWaylandPointerGestures::~QWaylandPointerGestures() noexcept +{ + if (version() >= ZWP_POINTER_GESTURES_V1_RELEASE_SINCE_VERSION) + release(); + else + zwp_pointer_gestures_v1_destroy(object()); +} + +QWaylandPointerGestureSwipe * + QWaylandPointerGestures::createPointerGestureSwipe(QWaylandInputDevice *device) +{ + return new QWaylandPointerGestureSwipe(device); +} + +QWaylandPointerGesturePinch * + QWaylandPointerGestures::createPointerGesturePinch(QWaylandInputDevice *device) +{ + return new QWaylandPointerGesturePinch(device); +} + +QWaylandPointerGestureSwipe::QWaylandPointerGestureSwipe(QWaylandInputDevice *p) + : mParent(p) +{ +} + +QWaylandPointerGestureSwipe::~QWaylandPointerGestureSwipe() +{ + destroy(); +} + +void QWaylandPointerGestureSwipe::zwp_pointer_gesture_swipe_v1_begin(uint32_t serial, uint32_t time, + struct ::wl_surface *surface, + uint32_t fingers) +{ +#ifndef QT_NO_GESTURES + mFocus = QWaylandWindow::fromWlSurface(surface); + if (!mFocus) { + return; + } + mParent->mSerial = serial; + mFingers = fingers; + + const auto* pointer = mParent->pointer(); + + qCDebug(lcQpaWaylandInput) << "zwp_pointer_gesture_swipe_v1_begin @ " + << pointer->mSurfacePos << "fingers" << fingers; + + auto e = QWaylandPointerGestureSwipeEvent(mFocus, Qt::GestureStarted, time, + pointer->mSurfacePos, pointer->mGlobalPos, mFingers, + QPointF()); + + mFocus->handleSwipeGesture(mParent, e); +#endif +} + +void QWaylandPointerGestureSwipe::zwp_pointer_gesture_swipe_v1_update(uint32_t time, + wl_fixed_t dx, wl_fixed_t dy) +{ +#ifndef QT_NO_GESTURES + if (!mFocus) { + return; + } + const auto* pointer = mParent->pointer(); + + const QPointF delta = QPointF(wl_fixed_to_double(dx), wl_fixed_to_double(dy)); + qCDebug(lcQpaWaylandInput) << "zwp_pointer_gesture_swipe_v1_update @ " + << pointer->mSurfacePos << "delta" << delta; + + auto e = QWaylandPointerGestureSwipeEvent(mFocus, Qt::GestureUpdated, time, + pointer->mSurfacePos, pointer->mGlobalPos, mFingers, delta); + + mFocus->handleSwipeGesture(mParent, e); +#endif +} + +void QWaylandPointerGestureSwipe::zwp_pointer_gesture_swipe_v1_end(uint32_t serial, uint32_t time, + int32_t cancelled) +{ +#ifndef QT_NO_GESTURES + if (!mFocus) { + return; + } + mParent->mSerial = serial; + const auto* pointer = mParent->pointer(); + + qCDebug(lcQpaWaylandInput) << "zwp_pointer_gesture_swipe_v1_end @ " + << pointer->mSurfacePos << (cancelled ? "CANCELED" : ""); + + auto gestureType = cancelled ? Qt::GestureFinished : Qt::GestureCanceled; + + auto e = QWaylandPointerGestureSwipeEvent(mFocus, gestureType, time, + pointer->mSurfacePos, pointer->mGlobalPos, mFingers, + QPointF()); + + mFocus->handleSwipeGesture(mParent, e); + + mFocus.clear(); + mFingers = 0; +#endif +} + +QWaylandPointerGesturePinch::QWaylandPointerGesturePinch(QWaylandInputDevice *p) + : mParent(p) +{ +} + +QWaylandPointerGesturePinch::~QWaylandPointerGesturePinch() +{ + destroy(); +} + +void QWaylandPointerGesturePinch::zwp_pointer_gesture_pinch_v1_begin(uint32_t serial, uint32_t time, + struct ::wl_surface *surface, + uint32_t fingers) +{ +#ifndef QT_NO_GESTURES + mFocus = QWaylandWindow::fromWlSurface(surface); + if (!mFocus) { + return; + } + mParent->mSerial = serial; + mFingers = fingers; + mLastScale = 1; + const auto* pointer = mParent->pointer(); + + qCDebug(lcQpaWaylandInput) << "zwp_pointer_gesture_pinch_v1_begin @ " + << pointer->mSurfacePos << "fingers" << fingers; + + auto e = QWaylandPointerGesturePinchEvent(mFocus, Qt::GestureStarted, time, + pointer->mSurfacePos, pointer->mGlobalPos, mFingers, + QPointF(), 0, 0); + + mFocus->handlePinchGesture(mParent, e); +#endif +} + +void QWaylandPointerGesturePinch::zwp_pointer_gesture_pinch_v1_update(uint32_t time, + wl_fixed_t dx, wl_fixed_t dy, + wl_fixed_t scale, + wl_fixed_t rotation) +{ +#ifndef QT_NO_GESTURES + if (!mFocus) { + return; + } + const auto* pointer = mParent->pointer(); + + const qreal rscale = wl_fixed_to_double(scale); + const qreal rot = wl_fixed_to_double(rotation); + const QPointF delta = QPointF(wl_fixed_to_double(dx), wl_fixed_to_double(dy)); + qCDebug(lcQpaWaylandInput) << "zwp_pointer_gesture_pinch_v1_update @ " + << pointer->mSurfacePos << "delta" << delta + << "scale" << mLastScale << "->" << rscale + << "delta" << rscale - mLastScale << "rot" << rot; + + auto e = QWaylandPointerGesturePinchEvent(mFocus, Qt::GestureUpdated, time, + pointer->mSurfacePos, pointer->mGlobalPos, mFingers, + delta, rscale - mLastScale, rot); + + mFocus->handlePinchGesture(mParent, e); + + mLastScale = rscale; +#endif +} + +void QWaylandPointerGesturePinch::zwp_pointer_gesture_pinch_v1_end(uint32_t serial, uint32_t time, + int32_t cancelled) +{ +#ifndef QT_NO_GESTURES + if (!mFocus) { + return; + } + mParent->mSerial = serial; + const auto* pointer = mParent->pointer(); + + qCDebug(lcQpaWaylandInput) << "zwp_pointer_gesture_swipe_v1_end @ " + << pointer->mSurfacePos << (cancelled ? "CANCELED" : ""); + + auto gestureType = cancelled ? Qt::GestureFinished : Qt::GestureCanceled; + + auto e = QWaylandPointerGesturePinchEvent(mFocus, gestureType, time, + pointer->mSurfacePos, pointer->mGlobalPos, mFingers, + QPointF(), 0, 0); + + mFocus->handlePinchGesture(mParent, e); + + mFocus.clear(); + mFingers = 0; + mLastScale = 1; +#endif +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandpointergestures_p.h b/src/plugins/platforms/wayland/qwaylandpointergestures_p.h new file mode 100644 index 00000000000..06ee4a6edda --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandpointergestures_p.h @@ -0,0 +1,115 @@ +// Copyright (C) 2021 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 + +#ifndef QWAYLANDPOINTERGESTURES_P_H +#define QWAYLANDPOINTERGESTURES_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; +class QWaylandWindow; +class QWaylandInputDevice; +class QWaylandPointerGestureSwipe; +class QWaylandPointerGesturePinch; + +class Q_WAYLANDCLIENT_EXPORT QWaylandPointerGestures : public QtWayland::zwp_pointer_gestures_v1 +{ +public: + explicit QWaylandPointerGestures(QWaylandDisplay *display, uint id, uint version); + ~QWaylandPointerGestures(); + + QWaylandPointerGestureSwipe *createPointerGestureSwipe(QWaylandInputDevice *device); + QWaylandPointerGesturePinch *createPointerGesturePinch(QWaylandInputDevice *device); +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandPointerGestureSwipe : + public QtWayland::zwp_pointer_gesture_swipe_v1 +{ +public: + QWaylandPointerGestureSwipe(QWaylandInputDevice *p); + ~QWaylandPointerGestureSwipe() override; + + void zwp_pointer_gesture_swipe_v1_begin(uint32_t serial, + uint32_t time, + struct ::wl_surface *surface, + uint32_t fingers) override; + + void zwp_pointer_gesture_swipe_v1_update(uint32_t time, + wl_fixed_t dx, + wl_fixed_t dy) override; + + void zwp_pointer_gesture_swipe_v1_end(uint32_t serial, + uint32_t time, + int32_t cancelled) override; + + struct ::zwp_pointer_gesture_swipe_v1 *zwp_pointer_gesture_swipe_v1() + { + return QtWayland::zwp_pointer_gesture_swipe_v1::object(); + } + + QWaylandInputDevice *mParent = nullptr; + QPointer mFocus; + uint mFingers = 0; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandPointerGesturePinch : + public QtWayland::zwp_pointer_gesture_pinch_v1 +{ +public: + QWaylandPointerGesturePinch(QWaylandInputDevice *p); + ~QWaylandPointerGesturePinch() override; + + void zwp_pointer_gesture_pinch_v1_begin(uint32_t serial, + uint32_t time, + struct ::wl_surface *surface, + uint32_t fingers) override; + + void zwp_pointer_gesture_pinch_v1_update(uint32_t time, + wl_fixed_t dx, + wl_fixed_t dy, + wl_fixed_t scale, + wl_fixed_t rotation) override; + + void zwp_pointer_gesture_pinch_v1_end(uint32_t serial, + uint32_t time, + int32_t cancelled) override; + + struct ::zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1() + { + return QtWayland::zwp_pointer_gesture_pinch_v1::object(); + } + + QWaylandInputDevice *mParent = nullptr; + QPointer mFocus; + uint mFingers = 0; + + // We need to convert between absolute scale provided by wayland/libinput and zoom deltas + // that Qt expects. This stores the scale of the last pinch event or 1.0 if there was none. + qreal mLastScale = 1; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDPOINTERGESTURES_P_H diff --git a/src/plugins/platforms/wayland/qwaylandprimaryselectionv1.cpp b/src/plugins/platforms/wayland/qwaylandprimaryselectionv1.cpp new file mode 100644 index 00000000000..4d2d0e4ec86 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandprimaryselectionv1.cpp @@ -0,0 +1,141 @@ +// 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 + +#include "qwaylandprimaryselectionv1_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandmimehelper_p.h" + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandPrimarySelectionDeviceManagerV1::QWaylandPrimarySelectionDeviceManagerV1(QWaylandDisplay *display, uint id, uint version) + : zwp_primary_selection_device_manager_v1(display->wl_registry(), id, qMin(version, uint(1))) + , m_display(display) +{ +} + +QWaylandPrimarySelectionDeviceManagerV1::~QWaylandPrimarySelectionDeviceManagerV1() +{ + destroy(); +} + +QWaylandPrimarySelectionDeviceV1 *QWaylandPrimarySelectionDeviceManagerV1::createDevice(QWaylandInputDevice *seat) +{ + return new QWaylandPrimarySelectionDeviceV1(this, seat); +} + +QWaylandPrimarySelectionOfferV1::QWaylandPrimarySelectionOfferV1(QWaylandDisplay *display, ::zwp_primary_selection_offer_v1 *offer) + : zwp_primary_selection_offer_v1(offer) + , m_display(display) + , m_mimeData(new QWaylandMimeData(this)) +{} + +void QWaylandPrimarySelectionOfferV1::startReceiving(const QString &mimeType, int fd) +{ + receive(mimeType, fd); + wl_display_flush(m_display->wl_display()); +} + +void QWaylandPrimarySelectionOfferV1::zwp_primary_selection_offer_v1_offer(const QString &mime_type) +{ + m_mimeData->appendFormat(mime_type); +} + +QWaylandPrimarySelectionDeviceV1::QWaylandPrimarySelectionDeviceV1( + QWaylandPrimarySelectionDeviceManagerV1 *manager, QWaylandInputDevice *seat) + : QtWayland::zwp_primary_selection_device_v1(manager->get_device(seat->wl_seat())) + , m_display(manager->display()) + , m_seat(seat) +{ +} + +QWaylandPrimarySelectionDeviceV1::~QWaylandPrimarySelectionDeviceV1() +{ + destroy(); +} + +void QWaylandPrimarySelectionDeviceV1::invalidateSelectionOffer() +{ + if (!m_selectionOffer) + return; + + m_selectionOffer.reset(); + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Selection); +} + +void QWaylandPrimarySelectionDeviceV1::setSelectionSource(QWaylandPrimarySelectionSourceV1 *source) +{ + if (source) { + connect(source, &QWaylandPrimarySelectionSourceV1::cancelled, this, [this]() { + m_selectionSource.reset(); + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Selection); + }); + } + set_selection(source ? source->object() : nullptr, m_seat->serial()); + m_selectionSource.reset(source); +} + +void QWaylandPrimarySelectionDeviceV1::zwp_primary_selection_device_v1_data_offer(zwp_primary_selection_offer_v1 *offer) +{ + new QWaylandPrimarySelectionOfferV1(m_display, offer); +} + +void QWaylandPrimarySelectionDeviceV1::zwp_primary_selection_device_v1_selection(zwp_primary_selection_offer_v1 *id) +{ + + if (id) + m_selectionOffer.reset(static_cast(zwp_primary_selection_offer_v1_get_user_data(id))); + else + m_selectionOffer.reset(); + + QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Selection); +} + +QWaylandPrimarySelectionSourceV1::QWaylandPrimarySelectionSourceV1(QWaylandPrimarySelectionDeviceManagerV1 *manager, QMimeData *mimeData) + : QtWayland::zwp_primary_selection_source_v1(manager->create_source()) + , m_mimeData(mimeData) +{ + if (!mimeData) + return; + for (auto &format : mimeData->formats()) + offer(format); +} + +QWaylandPrimarySelectionSourceV1::~QWaylandPrimarySelectionSourceV1() +{ + destroy(); +} + +void QWaylandPrimarySelectionSourceV1::zwp_primary_selection_source_v1_send(const QString &mime_type, int32_t fd) +{ + QByteArray content = QWaylandMimeHelper::getByteArray(m_mimeData, mime_type); + if (!content.isEmpty()) { + // Create a sigpipe handler that does nothing, or clients may be forced to terminate + // if the pipe is closed in the other end. + struct sigaction action, oldAction; + action.sa_handler = SIG_IGN; + sigemptyset (&action.sa_mask); + action.sa_flags = 0; + + sigaction(SIGPIPE, &action, &oldAction); + ssize_t unused = write(fd, content.constData(), size_t(content.size())); + Q_UNUSED(unused); + sigaction(SIGPIPE, &oldAction, nullptr); + } + close(fd); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#include "moc_qwaylandprimaryselectionv1_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylandprimaryselectionv1_p.h b/src/plugins/platforms/wayland/qwaylandprimaryselectionv1_p.h new file mode 100644 index 00000000000..f39aec526b9 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandprimaryselectionv1_p.h @@ -0,0 +1,112 @@ +// 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 + +#ifndef QWAYLANDPRIMARYSELECTIONV1_P_H +#define QWAYLANDPRIMARYSELECTIONV1_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 + +QT_REQUIRE_CONFIG(wayland_client_primary_selection); + +QT_BEGIN_NAMESPACE + +class QMimeData; + +namespace QtWaylandClient { + +class QWaylandInputDevice; +class QWaylandPrimarySelectionDeviceV1; + +class QWaylandPrimarySelectionDeviceManagerV1 : public QtWayland::zwp_primary_selection_device_manager_v1 +{ +public: + explicit QWaylandPrimarySelectionDeviceManagerV1(QWaylandDisplay *display, uint id, uint version); + ~QWaylandPrimarySelectionDeviceManagerV1(); + QWaylandPrimarySelectionDeviceV1 *createDevice(QWaylandInputDevice *seat); + QWaylandDisplay *display() const { return m_display; } + +private: + QWaylandDisplay *m_display = nullptr; +}; + +class QWaylandPrimarySelectionOfferV1 : public QtWayland::zwp_primary_selection_offer_v1, public QWaylandAbstractDataOffer +{ +public: + explicit QWaylandPrimarySelectionOfferV1(QWaylandDisplay *display, ::zwp_primary_selection_offer_v1 *offer); + ~QWaylandPrimarySelectionOfferV1() override { destroy(); } + void startReceiving(const QString &mimeType, int fd) override; + QMimeData *mimeData() override { return m_mimeData.data(); } + +protected: + void zwp_primary_selection_offer_v1_offer(const QString &mime_type) override; + +private: + QWaylandDisplay *m_display = nullptr; + QScopedPointer m_mimeData; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandPrimarySelectionSourceV1 : public QObject, public QtWayland::zwp_primary_selection_source_v1 +{ + Q_OBJECT +public: + explicit QWaylandPrimarySelectionSourceV1(QWaylandPrimarySelectionDeviceManagerV1 *manager, QMimeData *mimeData); + ~QWaylandPrimarySelectionSourceV1() override; + + QMimeData *mimeData() const { return m_mimeData; } + +Q_SIGNALS: + void cancelled(); + +protected: + void zwp_primary_selection_source_v1_send(const QString &mime_type, int32_t fd) override; + void zwp_primary_selection_source_v1_cancelled() override { emit cancelled(); } + +private: + QMimeData *m_mimeData = nullptr; +}; + +class QWaylandPrimarySelectionDeviceV1 : public QObject, public QtWayland::zwp_primary_selection_device_v1 +{ + Q_OBJECT + QWaylandPrimarySelectionDeviceV1(QWaylandPrimarySelectionDeviceManagerV1 *manager, QWaylandInputDevice *seat); + +public: + ~QWaylandPrimarySelectionDeviceV1() override; + QWaylandPrimarySelectionOfferV1 *selectionOffer() const { return m_selectionOffer.data(); } + void invalidateSelectionOffer(); + QWaylandPrimarySelectionSourceV1 *selectionSource() const { return m_selectionSource.data(); } + void setSelectionSource(QWaylandPrimarySelectionSourceV1 *source); + +protected: + void zwp_primary_selection_device_v1_data_offer(struct ::zwp_primary_selection_offer_v1 *offer) override; + void zwp_primary_selection_device_v1_selection(struct ::zwp_primary_selection_offer_v1 *id) override; + +private: + QWaylandDisplay *m_display = nullptr; + QWaylandInputDevice *m_seat = nullptr; + QScopedPointer m_selectionOffer; + QScopedPointer m_selectionSource; + friend class QWaylandPrimarySelectionDeviceManagerV1; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDPRIMARYSELECTIONV1_P_H diff --git a/src/plugins/platforms/wayland/qwaylandscreen.cpp b/src/plugins/platforms/wayland/qwaylandscreen.cpp new file mode 100644 index 00000000000..1cd3591083a --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandscreen.cpp @@ -0,0 +1,414 @@ +// Copyright (C) 2016 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 + +#include "qwaylandscreen_p.h" + +#include "qwaylanddisplay_p.h" +#include "qwaylandintegration_p.h" +#include "qwaylandcursor_p.h" +#include "qwaylandwindow_p.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandXdgOutputManagerV1::QWaylandXdgOutputManagerV1(QWaylandDisplay* display, uint id, uint version) + : QtWayland::zxdg_output_manager_v1(display->wl_registry(), id, qMin(3u, version)) +{ +} + +QWaylandXdgOutputManagerV1::~QWaylandXdgOutputManagerV1() +{ + destroy(); +} + +QWaylandScreen::QWaylandScreen(QWaylandDisplay *waylandDisplay, int version, uint32_t id) + : QtWayland::wl_output(waylandDisplay->wl_registry(), id, qMin(version, 4)) + , m_outputId(id) + , mWaylandDisplay(waylandDisplay) + , mOutputName(QStringLiteral("Screen%1").arg(id)) +{ + if (auto *xdgOutputManager = waylandDisplay->xdgOutputManager()) + initXdgOutput(xdgOutputManager); + + if (version < WL_OUTPUT_DONE_SINCE_VERSION) { + qCWarning(lcQpaWayland) << "wl_output done event not supported by compositor," + << "QScreen may not work correctly"; + mWaylandDisplay->forceRoundTrip(); // Give the compositor a chance to send geometry etc. + mProcessedEvents |= OutputDoneEvent; // Fake the done event + maybeInitialize(); + } +} + +QWaylandScreen::~QWaylandScreen() +{ + if (zxdg_output_v1::isInitialized()) + zxdg_output_v1::destroy(); + if (wl_output::version() >= WL_OUTPUT_RELEASE_SINCE_VERSION) + wl_output::release(); + else + wl_output_destroy(wl_output::object()); +} + +uint QWaylandScreen::requiredEvents() const +{ + uint ret = OutputDoneEvent; + + if (mWaylandDisplay->xdgOutputManager()) { + if (mWaylandDisplay->xdgOutputManager()->version() >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) + ret |= XdgOutputNameEvent; + + // For objects version 3 onwards, zxdg_output_v1.done is deprecated. + if (mWaylandDisplay->xdgOutputManager()->version() < 3) + ret |= XdgOutputDoneEvent; + } + return ret; +} + +void QWaylandScreen::maybeInitialize() +{ + Q_ASSERT(!mInitialized); + + const uint requiredEvents = this->requiredEvents(); + if ((mProcessedEvents & requiredEvents) != requiredEvents) + return; + + mInitialized = true; + mWaylandDisplay->handleScreenInitialized(this); + + updateOutputProperties(); + if (zxdg_output_v1::isInitialized()) + updateXdgOutputProperties(); +} + +void QWaylandScreen::initXdgOutput(QWaylandXdgOutputManagerV1 *xdgOutputManager) +{ + Q_ASSERT(xdgOutputManager); + if (zxdg_output_v1::isInitialized()) + return; + + zxdg_output_v1::init(xdgOutputManager->get_xdg_output(wl_output::object())); +} + +QWaylandDisplay * QWaylandScreen::display() const +{ + return mWaylandDisplay; +} + +QString QWaylandScreen::manufacturer() const +{ + return mManufacturer; +} + +QString QWaylandScreen::model() const +{ + return mModel; +} + +QRect QWaylandScreen::geometry() const +{ + if (zxdg_output_v1::isInitialized()) { + + // Workaround for Gnome bug + // https://gitlab.gnome.org/GNOME/mutter/-/issues/2631 + // which sends an incorrect xdg geometry + const bool xdgGeometryIsBogus = mScale > 1 && mXdgGeometry.size() == mGeometry.size(); + + if (!xdgGeometryIsBogus) { + return mXdgGeometry; + } + } + // Scale geometry for QScreen. This makes window and screen + // geometry be in the same coordinate system. + return QRect(mGeometry.topLeft(), mGeometry.size() / mScale); +} + +int QWaylandScreen::depth() const +{ + return mDepth; +} + +QImage::Format QWaylandScreen::format() const +{ + return mFormat; +} + +QSizeF QWaylandScreen::physicalSize() const +{ + if (mPhysicalSize.isEmpty()) + return QPlatformScreen::physicalSize(); + else + return mPhysicalSize; +} + +QDpi QWaylandScreen::logicalDpi() const +{ + static bool physicalDpi = qEnvironmentVariable("QT_WAYLAND_FORCE_DPI") == QStringLiteral("physical"); + if (physicalDpi) + return QPlatformScreen::logicalDpi(); + + static int forceDpi = qgetenv("QT_WAYLAND_FORCE_DPI").toInt(); + if (forceDpi) + return QDpi(forceDpi, forceDpi); + + return QDpi(96, 96); +} + +QList QWaylandScreen::virtualSiblings() const +{ + QList list; + const QList screens = mWaylandDisplay->screens(); + auto *placeholder = mWaylandDisplay->placeholderScreen(); + + list.reserve(screens.size() + (placeholder ? 1 : 0)); + + for (QWaylandScreen *screen : std::as_const(screens)) { + if (screen->screen()) + list << screen; + } + + if (placeholder) + list << placeholder; + + return list; +} + +Qt::ScreenOrientation QWaylandScreen::orientation() const +{ + return m_orientation; +} + +int QWaylandScreen::scale() const +{ + return mScale; +} + +qreal QWaylandScreen::devicePixelRatio() const +{ + return qreal(mScale); +} + +qreal QWaylandScreen::refreshRate() const +{ + return mRefreshRate / 1000.f; +} + +#if QT_CONFIG(cursor) +QPlatformCursor *QWaylandScreen::cursor() const +{ + return mWaylandDisplay->waylandCursor(); +} +#endif // QT_CONFIG(cursor) + +QPlatformScreen::SubpixelAntialiasingType QWaylandScreen::subpixelAntialiasingTypeHint() const +{ + QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint(); + if (type == QPlatformScreen::Subpixel_None) { + switch (mSubpixel) { + case wl_output::subpixel_unknown: + case wl_output::subpixel_none: + type = QPlatformScreen::Subpixel_None; + break; + case wl_output::subpixel_horizontal_rgb: + type = QPlatformScreen::Subpixel_RGB; + break; + case wl_output::subpixel_horizontal_bgr: + type = QPlatformScreen::Subpixel_BGR; + break; + case wl_output::subpixel_vertical_rgb: + type = QPlatformScreen::Subpixel_VRGB; + break; + case wl_output::subpixel_vertical_bgr: + type = QPlatformScreen::Subpixel_VBGR; + break; + } + } + return type; +} + +QWaylandScreen *QWaylandScreen::waylandScreenFromWindow(QWindow *window) +{ + QPlatformScreen *platformScreen = QPlatformScreen::platformScreenForWindow(window); + if (platformScreen->isPlaceholder()) + return nullptr; + return static_cast(platformScreen); +} + +QWaylandScreen *QWaylandScreen::fromWlOutput(::wl_output *output) +{ + if (auto *o = QtWayland::wl_output::fromObject(output)) + return static_cast(o); + return nullptr; +} + +Qt::ScreenOrientation QWaylandScreen::toScreenOrientation(int wlTransform, + Qt::ScreenOrientation fallback) const +{ + auto orientation = fallback; + bool isPortrait = mGeometry.height() > mGeometry.width(); + switch (wlTransform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + orientation = isPortrait ? Qt::PortraitOrientation : Qt::LandscapeOrientation; + break; + case WL_OUTPUT_TRANSFORM_90: + orientation = isPortrait ? Qt::InvertedLandscapeOrientation : Qt::PortraitOrientation; + break; + case WL_OUTPUT_TRANSFORM_180: + orientation = isPortrait ? Qt::InvertedPortraitOrientation : Qt::InvertedLandscapeOrientation; + break; + case WL_OUTPUT_TRANSFORM_270: + orientation = isPortrait ? Qt::LandscapeOrientation : Qt::InvertedPortraitOrientation; + break; + // Ignore these ones, at least for now + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + break; + } + + return orientation; +} + +void QWaylandScreen::output_mode(uint32_t flags, int width, int height, int refresh) +{ + if (!(flags & WL_OUTPUT_MODE_CURRENT)) + return; + + QSize size(width, height); + if (size != mGeometry.size()) + mGeometry.setSize(size); + + if (refresh != mRefreshRate) + mRefreshRate = refresh; +} + +void QWaylandScreen::output_geometry(int32_t x, int32_t y, + int32_t width, int32_t height, + int subpixel, + const QString &make, + const QString &model, + int32_t transform) +{ + mManufacturer = make; + mModel = model; + + mSubpixel = subpixel; + mTransform = transform; + + mPhysicalSize = QSize(width, height); + mGeometry.moveTopLeft(QPoint(x, y)); +} + +void QWaylandScreen::output_scale(int32_t factor) +{ + mScale = factor; +} + +void QWaylandScreen::output_done() +{ + mProcessedEvents |= OutputDoneEvent; + + if (mInitialized) { + updateOutputProperties(); + if (zxdg_output_v1::isInitialized()) + updateXdgOutputProperties(); + } else { + maybeInitialize(); + } +} + +void QWaylandScreen::output_name(const QString &name) +{ + if (Q_UNLIKELY(mInitialized)) { + qCWarning(lcQpaWayland) << "wl_output.name received after output has been initialized, this is most likely a bug in the compositor"; + return; + } + + if (Q_UNLIKELY(mProcessedEvents & OutputNameEvent)) { + qCWarning(lcQpaWayland) << "wl_output.name received more than once, this is most likely a bug in the compositor"; + return; + } + + if (!name.isEmpty()) + mOutputName = name; + + mProcessedEvents |= OutputNameEvent; +} + +void QWaylandScreen::updateOutputProperties() +{ + Q_ASSERT(mInitialized); + + if (mTransform >= 0) { + auto newOrientation = toScreenOrientation(mTransform, m_orientation); + if (m_orientation != newOrientation) { + m_orientation = newOrientation; + QWindowSystemInterface::handleScreenOrientationChange(screen(), m_orientation); + } + mTransform = -1; + } + + QWindowSystemInterface::handleScreenRefreshRateChange(screen(), refreshRate()); + + if (!zxdg_output_v1::isInitialized()) + QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), geometry()); +} + + +void QWaylandScreen::zxdg_output_v1_logical_position(int32_t x, int32_t y) +{ + mXdgGeometry.moveTopLeft(QPoint(x, y)); +} + +void QWaylandScreen::zxdg_output_v1_logical_size(int32_t width, int32_t height) +{ + mXdgGeometry.setSize(QSize(width, height)); +} + +void QWaylandScreen::zxdg_output_v1_done() +{ + if (Q_UNLIKELY(mWaylandDisplay->xdgOutputManager()->version() >= 3)) + qCWarning(lcQpaWayland) << "zxdg_output_v1.done received on version 3 or newer, this is most likely a bug in the compositor"; + + mProcessedEvents |= XdgOutputDoneEvent; + if (mInitialized) + updateXdgOutputProperties(); + else + maybeInitialize(); +} + +void QWaylandScreen::zxdg_output_v1_name(const QString &name) +{ + if (Q_UNLIKELY(mInitialized)) + qCWarning(lcQpaWayland) << "zxdg_output_v1.name received after output has been initialized, this is most likely a bug in the compositor"; + + if (Q_UNLIKELY(mProcessedEvents & XdgOutputNameEvent)) { + qCWarning(lcQpaWayland) << "zxdg_output_v1.name received more than once, this is most likely a bug in the compositor"; + return; + } + + // This event is deprecated, instead clients should use wl_output.name. + if (!(mProcessedEvents & OutputNameEvent)) { + if (!name.isEmpty()) + mOutputName = name; + } + + mProcessedEvents |= XdgOutputNameEvent; +} + +void QWaylandScreen::updateXdgOutputProperties() +{ + Q_ASSERT(mInitialized); + Q_ASSERT(zxdg_output_v1::isInitialized()); + QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), geometry()); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandscreen_p.h b/src/plugins/platforms/wayland/qwaylandscreen_p.h new file mode 100644 index 00000000000..de030732eb3 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandscreen_p.h @@ -0,0 +1,142 @@ +// Copyright (C) 2020 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 + +#ifndef QWAYLANDSCREEN_H +#define QWAYLANDSCREEN_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; +class QWaylandCursor; + +class Q_WAYLANDCLIENT_EXPORT QWaylandXdgOutputManagerV1 : public QtWayland::zxdg_output_manager_v1 { +public: + QWaylandXdgOutputManagerV1(QWaylandDisplay *display, uint id, uint version); + ~QWaylandXdgOutputManagerV1(); +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandScreen : public QPlatformScreen, + QtWayland::wl_output, + QtWayland::zxdg_output_v1, + public QNativeInterface::QWaylandScreen +{ +public: + QWaylandScreen(QWaylandDisplay *waylandDisplay, int version, uint32_t id); + ~QWaylandScreen() override; + + void maybeInitialize(); + + void initXdgOutput(QWaylandXdgOutputManagerV1 *xdgOutputManager); + + QWaylandDisplay *display() const; + + QString manufacturer() const override; + QString model() const override; + + QRect geometry() const override; + int depth() const override; + QImage::Format format() const override; + + QSizeF physicalSize() const override; + + QDpi logicalDpi() const override; + QList virtualSiblings() const override; + + Qt::ScreenOrientation orientation() const override; + int scale() const; + qreal devicePixelRatio() const override; + qreal refreshRate() const override; + + QString name() const override { return mOutputName; } + +#if QT_CONFIG(cursor) + QPlatformCursor *cursor() const override; +#endif + + SubpixelAntialiasingType subpixelAntialiasingTypeHint() const override; + + uint32_t outputId() const { return m_outputId; } + ::wl_output *output() const override + { + return const_cast<::wl_output *>(QtWayland::wl_output::object()); + } + + static QWaylandScreen *waylandScreenFromWindow(QWindow *window); + static QWaylandScreen *fromWlOutput(::wl_output *output); + + Qt::ScreenOrientation toScreenOrientation(int wlTransform, + Qt::ScreenOrientation fallback) const; + +protected: + enum Event : uint { + XdgOutputDoneEvent = 0x1, + OutputDoneEvent = 0x2, + XdgOutputNameEvent = 0x4, + OutputNameEvent = 0x8, + }; + uint requiredEvents() const; + + void output_mode(uint32_t flags, int width, int height, int refresh) override; + void output_geometry(int32_t x, int32_t y, + int32_t width, int32_t height, + int subpixel, + const QString &make, + const QString &model, + int32_t transform) override; + void output_scale(int32_t factor) override; + void output_done() override; + void output_name(const QString &name) override; + void updateOutputProperties(); + + // XdgOutput + void zxdg_output_v1_logical_position(int32_t x, int32_t y) override; + void zxdg_output_v1_logical_size(int32_t width, int32_t height) override; + void zxdg_output_v1_done() override; + void zxdg_output_v1_name(const QString &name) override; + void updateXdgOutputProperties(); + + int m_outputId; + QWaylandDisplay *mWaylandDisplay = nullptr; + QString mManufacturer; + QString mModel; + QRect mGeometry; + QRect mXdgGeometry; + int mScale = 1; + int mDepth = 32; + int mRefreshRate = 60000; + int mSubpixel = -1; + int mTransform = -1; + QImage::Format mFormat = QImage::Format_ARGB32_Premultiplied; + QSize mPhysicalSize; + QString mOutputName; + Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation; + uint mProcessedEvents = 0; + bool mInitialized = false; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDSCREEN_H diff --git a/src/plugins/platforms/wayland/qwaylandshellsurface.cpp b/src/plugins/platforms/wayland/qwaylandshellsurface.cpp new file mode 100644 index 00000000000..2dc218c2f75 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandshellsurface.cpp @@ -0,0 +1,92 @@ +// Copyright (C) 2016 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 + +#include "qwaylandshellsurface_p.h" +#include "qwaylandwindow_p.h" +#include "qwaylandinputdevice_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandShellSurface::QWaylandShellSurface(QWaylandWindow *window) + : m_window(window) +{ +} + +void QWaylandShellSurface::setWindowFlags(Qt::WindowFlags flags) +{ + Q_UNUSED(flags); +} + +void QWaylandShellSurface::sendProperty(const QString &name, const QVariant &value) +{ + Q_UNUSED(name); + Q_UNUSED(value); +} + +QPlatformWindow *QWaylandShellSurface::platformWindow() +{ + return m_window; +} + +wl_surface *QWaylandShellSurface::wlSurface() +{ + return m_window ? m_window->wlSurface() : nullptr; +} + +void QWaylandShellSurface::setWindowGeometry(const QRect &rect) +{ + setWindowPosition(rect.topLeft()); + setWindowSize(rect.size()); +} + +void QWaylandShellSurface::resizeFromApplyConfigure(const QSize &sizeWithMargins, const QPoint &offset) +{ + m_window->resizeFromApplyConfigure(sizeWithMargins, offset); +} + +void QWaylandShellSurface::repositionFromApplyConfigure(const QPoint &position) +{ + m_window->repositionFromApplyConfigure(position); +} + +void QWaylandShellSurface::setGeometryFromApplyConfigure(const QPoint &globalPosition, const QSize &sizeWithMargins) +{ + m_window->setGeometryFromApplyConfigure(globalPosition, sizeWithMargins); +} + +void QWaylandShellSurface::applyConfigureWhenPossible() +{ + m_window->applyConfigureWhenPossible(); +} + +void QWaylandShellSurface::handleActivationChanged(bool activated) +{ + if (activated) + m_window->display()->handleWindowActivated(m_window); + else + m_window->display()->handleWindowDeactivated(m_window); +} + +uint32_t QWaylandShellSurface::getSerial(QWaylandInputDevice *inputDevice) +{ + return inputDevice->serial(); +} + +void QWaylandShellSurface::setXdgActivationToken(const QString &token) +{ + Q_UNUSED(token); + qCWarning(lcQpaWayland) << "setXdgActivationToken not implemented" << token; +} + +void QWaylandShellSurface::requestXdgActivationToken(quint32 serial) +{ + Q_UNUSED(serial); + Q_EMIT m_window->xdgActivationTokenCreated({}); +} +} + +QT_END_NAMESPACE + +#include "moc_qwaylandshellsurface_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylandshellsurface_p.h b/src/plugins/platforms/wayland/qwaylandshellsurface_p.h new file mode 100644 index 00000000000..71599259977 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandshellsurface_p.h @@ -0,0 +1,113 @@ +// Copyright (C) 2021 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 + +#ifndef QWAYLANDSHELLSURFACE_H +#define QWAYLANDSHELLSURFACE_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 + +struct wl_surface; + +QT_BEGIN_NAMESPACE + +class QVariant; +class QWindow; +class QPlatformWindow; + +namespace QtWaylandClient { + +class QWaylandWindow; +class QWaylandInputDevice; + +class Q_WAYLANDCLIENT_EXPORT QWaylandShellSurface : public QObject +{ + Q_OBJECT +public: + explicit QWaylandShellSurface(QWaylandWindow *window); + ~QWaylandShellSurface() override {} + virtual bool resize(QWaylandInputDevice *, Qt::Edges) { return false; } + virtual bool move(QWaylandInputDevice *) { return false; } + virtual bool showWindowMenu(QWaylandInputDevice *seat) { Q_UNUSED(seat); return false; } + virtual void setTitle(const QString & /*title*/) {} + virtual void setAppId(const QString & /*appId*/) {} + + virtual void setWindowFlags(Qt::WindowFlags flags); + + virtual bool isExposed() const { return true; } + virtual bool handleExpose(const QRegion &) { return false; } + + virtual void raise() {} + virtual void lower() {} + virtual void setContentOrientationMask(Qt::ScreenOrientations orientation) { Q_UNUSED(orientation); } + virtual void setContentGeometry(const QRect &rect) { Q_UNUSED(rect); } + + virtual void sendProperty(const QString &name, const QVariant &value); + + virtual void applyConfigure() {} + virtual void requestWindowStates(Qt::WindowStates states) {Q_UNUSED(states);} + virtual bool wantsDecorations() const { return false; } + virtual QMargins serverSideFrameMargins() const { return QMargins(); } + + virtual void propagateSizeHints() {} + + virtual void setWindowGeometry(const QRect &rect); + virtual void setWindowPosition(const QPoint &position) { Q_UNUSED(position); } + virtual void setWindowSize(const QSize &size) { Q_UNUSED(size); } + + virtual bool requestActivate() { return false; } + virtual bool requestActivateOnShow() { return false; } + virtual void setXdgActivationToken(const QString &token); + virtual void requestXdgActivationToken(quint32 serial); + + virtual void setAlertState(bool enabled) { Q_UNUSED(enabled); } + virtual bool isAlertState() const { return false; } + + virtual QString externWindowHandle() { return QString(); } + + inline QWaylandWindow *window() { return m_window; } + QPlatformWindow *platformWindow(); + struct wl_surface *wlSurface(); + + virtual std::any surfaceRole() const { return std::any(); }; + + virtual void attachPopup(QWaylandShellSurface *popup) { Q_UNUSED(popup); } + virtual void detachPopup(QWaylandShellSurface *popup) { Q_UNUSED(popup); } + + virtual void setIcon(const QIcon &icon) { Q_UNUSED(icon); } + +protected: + void resizeFromApplyConfigure(const QSize &sizeWithMargins, const QPoint &offset = {0, 0}); + void repositionFromApplyConfigure(const QPoint &position); + void setGeometryFromApplyConfigure(const QPoint &globalPosition, const QSize &sizeWithMargins); + void applyConfigureWhenPossible(); + void handleActivationChanged(bool activated); + + static uint32_t getSerial(QWaylandInputDevice *inputDevice); + +private: + QWaylandWindow *m_window = nullptr; + friend class QWaylandWindow; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDSHELLSURFACE_H diff --git a/src/plugins/platforms/wayland/qwaylandshm.cpp b/src/plugins/platforms/wayland/qwaylandshm.cpp new file mode 100644 index 00000000000..7f61027d1db --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandshm.cpp @@ -0,0 +1,54 @@ +// Copyright (C) 2016 LG Electronics Inc, author: +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include +#include + +#include "qwaylandsharedmemoryformathelper_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandShm::QWaylandShm(QWaylandDisplay *display, int version, uint32_t id) + : QtWayland::wl_shm(display->wl_registry(), id, qMin(version, 2)) +{ +} + +QWaylandShm::~QWaylandShm() +{ + if (version() < WL_SHM_RELEASE_SINCE_VERSION) { + wl_shm_destroy(object()); + } else { + wl_shm_release(object()); + } +} + +void QWaylandShm::shm_format(uint32_t format) +{ + m_formats << format; +} + +bool QWaylandShm::formatSupported(wl_shm_format format) const +{ + return m_formats.contains(format); +} + +bool QWaylandShm::formatSupported(QImage::Format format) const +{ + wl_shm_format fmt = formatFrom(format); + return formatSupported(fmt); +} + +wl_shm_format QWaylandShm::formatFrom(QImage::Format format) +{ + return QWaylandSharedMemoryFormatHelper::fromQImageFormat(format); +} + +QImage::Format QWaylandShm::formatFrom(wl_shm_format format) +{ + return QWaylandSharedMemoryFormatHelper::fromWaylandShmFormat(format); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandshm_p.h b/src/plugins/platforms/wayland/qwaylandshm_p.h new file mode 100644 index 00000000000..eb4a90c12db --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandshm_p.h @@ -0,0 +1,58 @@ +// Copyright (C) 2016 LG Electronics Inc, author: +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDSHM_H +#define QWAYLANDSHM_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class Q_WAYLANDCLIENT_EXPORT QWaylandShm : public QtWayland::wl_shm +{ + +public: + + QWaylandShm(QWaylandDisplay *display, int version, uint32_t id); + ~QWaylandShm() override; + + bool formatSupported(wl_shm_format format) const; + bool formatSupported(QImage::Format format) const; + + static wl_shm_format formatFrom(QImage::Format format); + static QImage::Format formatFrom(wl_shm_format format); + +protected: + void shm_format(uint32_t format) override; + +private: + QList m_formats; + +}; + +} + +QT_END_NAMESPACE + +#endif + diff --git a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp new file mode 100644 index 00000000000..0d59ffef33c --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp @@ -0,0 +1,489 @@ +// Copyright (C) 2016 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 +#include "qwaylandshmbackingstore_p.h" +#include "qwaylandwindow_p.h" +#include "qwaylandsubsurface_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandscreen_p.h" +#include "qwaylandabstractdecoration_p.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#ifdef Q_OS_LINUX +# include +// from linux/memfd.h: +# ifndef MFD_CLOEXEC +# define MFD_CLOEXEC 0x0001U +# endif +# ifndef MFD_ALLOW_SEALING +# define MFD_ALLOW_SEALING 0x0002U +# endif +// from bits/fcntl-linux.h +# ifndef F_ADD_SEALS +# define F_ADD_SEALS 1033 +# endif +# ifndef F_SEAL_SEAL +# define F_SEAL_SEAL 0x0001 +# endif +# ifndef F_SEAL_SHRINK +# define F_SEAL_SHRINK 0x0002 +# endif +#endif + +QT_BEGIN_NAMESPACE + +extern void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &); + +namespace QtWaylandClient { + +QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display, + const QSize &size, QImage::Format format, qreal scale, wl_event_queue *customEventQueue) + : mDirtyRegion(QRect(QPoint(0, 0), size / scale)) +{ + int stride = size.width() * 4; + int alloc = stride * size.height(); + int fd = -1; + +#ifdef SYS_memfd_create + fd = syscall(SYS_memfd_create, "wayland-shm", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd >= 0) + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); +#endif + + std::unique_ptr filePointer; + bool opened; + + if (fd == -1) { + auto tmpFile = + std::make_unique(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + + QLatin1String("/wayland-shm-XXXXXX")); + opened = tmpFile->open(); + filePointer = std::move(tmpFile); + } else { + auto file = std::make_unique(); + opened = file->open(fd, QIODevice::ReadWrite | QIODevice::Unbuffered, QFile::AutoCloseHandle); + filePointer = std::move(file); + } + // NOTE beginPaint assumes a new buffer be all zeroes, which QFile::resize does. + if (!opened || !filePointer->resize(alloc)) { + qWarning("QWaylandShmBuffer: failed: %s", qUtf8Printable(filePointer->errorString())); + return; + } + fd = filePointer->handle(); + + // map ourselves: QFile::map() will unmap when the object is destroyed, + // but we want this mapping to persist (unmapping in destructor) + uchar *data = (uchar *) + mmap(nullptr, alloc, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == (uchar *) MAP_FAILED) { + qErrnoWarning("QWaylandShmBuffer: mmap failed"); + return; + } + + QWaylandShm* shm = display->shm(); + wl_shm_format wl_format = shm->formatFrom(format); + mImage = QImage(data, size.width(), size.height(), stride, format); + mImage.setDevicePixelRatio(scale); + + mShmPool = wl_shm_create_pool(shm->object(), fd, alloc); + init(wl_shm_pool_create_buffer(mShmPool,0, size.width(), size.height(), + stride, wl_format)); + if (customEventQueue) + wl_proxy_set_queue(reinterpret_cast(buffer()), customEventQueue); +} + +QWaylandShmBuffer::~QWaylandShmBuffer(void) +{ + delete mMarginsImage; + if (mImage.constBits()) + munmap((void *) mImage.constBits(), mImage.sizeInBytes()); + if (mShmPool) + wl_shm_pool_destroy(mShmPool); +} + +QImage *QWaylandShmBuffer::imageInsideMargins(const QMargins &marginsIn) +{ + QMargins margins = marginsIn * mImage.devicePixelRatio(); + + if (!margins.isNull() && margins != mMargins) { + if (mMarginsImage) { + delete mMarginsImage; + } + uchar *bits = const_cast(mImage.constBits()); + uchar *b_s_data = bits + margins.top() * mImage.bytesPerLine() + margins.left() * 4; + int b_s_width = mImage.size().width() - margins.left() - margins.right(); + int b_s_height = mImage.size().height() - margins.top() - margins.bottom(); + mMarginsImage = new QImage(b_s_data, b_s_width,b_s_height,mImage.bytesPerLine(),mImage.format()); + mMarginsImage->setDevicePixelRatio(mImage.devicePixelRatio()); + } + if (margins.isNull()) { + delete mMarginsImage; + mMarginsImage = nullptr; + } + + mMargins = margins; + if (!mMarginsImage) + return &mImage; + + return mMarginsImage; + +} + +QWaylandShmBackingStore::QWaylandShmBackingStore(QWindow *window, QWaylandDisplay *display) + : QPlatformBackingStore(window) + , mDisplay(display) +{ + mEventQueue = wl_display_create_queue(mDisplay->wl_display()); + QObject::connect(mDisplay, &QWaylandDisplay::connected, window, [this]() { + auto oldEventQueue = mEventQueue; + mEventQueue = wl_display_create_queue(mDisplay->wl_display()); + auto copy = mBuffers; + // clear available buffers so we create new ones + // actual deletion is deferred till after resize call so we can copy + // contents from the back buffer + mBuffers.clear(); + mFrontBuffer = nullptr; + // recreateBackBufferIfNeeded always resets mBackBuffer + if (mRequestedSize.isValid() && waylandWindow()) + recreateBackBufferIfNeeded(); + qDeleteAll(copy); + wl_event_queue_destroy(oldEventQueue); + }); +} + +QWaylandShmBackingStore::~QWaylandShmBackingStore() +{ + if (QWaylandWindow *w = waylandWindow()) + w->setBackingStore(nullptr); + +// if (mFrontBuffer == waylandWindow()->attached()) +// waylandWindow()->attach(0); + + qDeleteAll(mBuffers); + wl_event_queue_destroy(mEventQueue); +} + +QPaintDevice *QWaylandShmBackingStore::paintDevice() +{ + return contentSurface(); +} + +void QWaylandShmBackingStore::updateDirtyStates(const QRegion ®ion) +{ + // Update dirty state of buffers based on what was painted. The back buffer will + // not be dirty since we already painted on it, while other buffers will become dirty. + for (QWaylandShmBuffer *b : std::as_const(mBuffers)) { + if (b != mBackBuffer) + b->dirtyRegion() += region; + } +} + +void QWaylandShmBackingStore::beginPaint(const QRegion ®ion) +{ + mPainting = true; + waylandWindow()->setBackingStore(this); + const bool bufferWasRecreated = recreateBackBufferIfNeeded(); + + const QMargins margins = windowDecorationMargins(); + updateDirtyStates(region.translated(margins.left(), margins.top())); + + // Although undocumented, QBackingStore::beginPaint expects the painted region + // to be cleared before use if the window has a surface format with an alpha. + // Fresh QWaylandShmBuffer are already cleared, so we don't need to clear those. + if (!bufferWasRecreated && window()->format().hasAlpha()) { + QPainter p(paintDevice()); + p.setCompositionMode(QPainter::CompositionMode_Source); + const QColor blank = Qt::transparent; + for (const QRect &rect : region) + p.fillRect(rect, blank); + } +} + +void QWaylandShmBackingStore::endPaint() +{ + mPainting = false; + if (mPendingFlush) + flush(window(), mPendingRegion, QPoint()); +} + +// Inspired by QCALayerBackingStore. +bool QWaylandShmBackingStore::scroll(const QRegion ®ion, int dx, int dy) +{ + if (!mBackBuffer) + return false; + + QImage *backBufferImage = mBackBuffer->image(); + const qreal devicePixelRatio = backBufferImage->devicePixelRatio(); + + // On Wayland, the window can have a device pixel ratio different from + // the window/screen, therefore we cannot rely on QHighDpi here, cf. QBackingStore::scroll. + // With fractional scaling we cannot easily scroll the existing pixels. + if (!qFuzzyIsNull(devicePixelRatio - static_cast(devicePixelRatio))) + return false; + + const QPoint scrollDelta(dx, dy); + const QMargins margins = windowDecorationMargins(); + const QRegion adjustedRegion = region.translated(margins.left(), margins.top()); + + const QRect boundingRect = adjustedRegion.boundingRect(); + const QPoint devicePixelDelta = scrollDelta * devicePixelRatio; + + qt_scrollRectInImage(*backBufferImage, + QRect(boundingRect.topLeft() * devicePixelRatio, + boundingRect.size() * devicePixelRatio), + devicePixelDelta); + + // We do not mark the source region as dirty, even though it technically has "moved". + // This matches the behavior of other backingstore implementations using qt_scrollRectInImage. + updateDirtyStates(adjustedRegion.translated(scrollDelta)); + + return true; +} + +void QWaylandShmBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) +{ + Q_UNUSED(offset) + // Invoked when the window is of type RasterSurface or when the window is + // RasterGLSurface and there are no child widgets requiring OpenGL composition. + + // For the case of RasterGLSurface + having to compose, the composeAndFlush() is + // called instead. The default implementation from QPlatformBackingStore is sufficient + // however so no need to reimplement that. + if (window != this->window()) { + auto waylandWindow = static_cast(window->handle()); + auto newBuffer = new QWaylandShmBuffer(mDisplay, window->size(), mBackBuffer->image()->format(), mBackBuffer->scale(), mEventQueue); + newBuffer->setDeleteOnRelease(true); + QRect sourceRect(window->position(), window->size()); + QPainter painter(newBuffer->image()); + painter.drawImage(QPoint(0, 0), *mBackBuffer->image(), sourceRect); + waylandWindow->safeCommit(newBuffer, region); + return; + } + + if (mPainting) { + mPendingRegion |= region; + mPendingFlush = true; + return; + } + + mPendingFlush = false; + mPendingRegion = QRegion(); + + if (windowDecoration() && windowDecoration()->isDirty()) + updateDecorations(); + + mFrontBuffer = mBackBuffer; + + QMargins margins = windowDecorationMargins(); + waylandWindow()->safeCommit(mFrontBuffer, region.translated(margins.left(), margins.top())); +} + +void QWaylandShmBackingStore::resize(const QSize &size, const QRegion &) +{ + mRequestedSize = size; +} + +QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size, bool &bufferWasRecreated) +{ + static const int MAX_BUFFERS = 5; + static const int MAX_AGE = 10 * MAX_BUFFERS; + bufferWasRecreated = false; + + // Prune buffers that have not been used in a while or with different size. + for (auto i = mBuffers.size() - 1; i >= 0; --i) { + QWaylandShmBuffer *buffer = mBuffers[i]; + if (buffer->age() > MAX_AGE || buffer->size() != size) { + mBuffers.removeAt(i); + if (mBackBuffer == buffer) + mBackBuffer = nullptr; + delete buffer; + } + } + + QWaylandShmBuffer *buffer = nullptr; + for (QWaylandShmBuffer *candidate : std::as_const(mBuffers)) { + if (candidate->busy()) + continue; + + if (!buffer || candidate->age() < buffer->age()) + buffer = candidate; + } + + if (buffer) + return buffer; + + if (mBuffers.size() < MAX_BUFFERS) { + QImage::Format format = QImage::Format_ARGB32_Premultiplied; + if (!waylandWindow()->format().hasAlpha()) + format = QImage::Format_RGB32; + QWaylandShmBuffer *b = new QWaylandShmBuffer(mDisplay, size, format, waylandWindow()->scale(), mEventQueue); + bufferWasRecreated = true; + mBuffers.push_front(b); + return b; + } + return nullptr; +} + +bool QWaylandShmBackingStore::recreateBackBufferIfNeeded() +{ + wl_display_dispatch_queue_pending(mDisplay->wl_display(), mEventQueue); + + bool bufferWasRecreated = false; + QMargins margins = windowDecorationMargins(); + qreal scale = waylandWindow()->scale(); + const QSize sizeWithMargins = (mRequestedSize + QSize(margins.left() + margins.right(), margins.top() + margins.bottom())) * scale; + + // We look for a free buffer to draw into. If the buffer is not the last buffer we used, + // that is mBackBuffer, and the size is the same we copy the damaged content into the new + // buffer so that QPainter is happy to find the stuff it had drawn before. If the new + // buffer has a different size it needs to be redrawn completely anyway, and if the buffer + // is the same the stuff is there already. + // You can exercise the different codepaths with weston, switching between the gl and the + // pixman renderer. With the gl renderer release events are sent early so we can effectively + // run single buffered, while with the pixman renderer we have to use two. + QWaylandShmBuffer *buffer = getBuffer(sizeWithMargins, bufferWasRecreated); + while (!buffer) { + struct ::wl_display *display = mDisplay->wl_display(); + if (wl_display_dispatch_queue(display, mEventQueue) < 0) { + int ecode = wl_display_get_error(display); + if ((ecode == EPIPE || ecode == ECONNRESET)) + qWarning("The Wayland connection broke during blocking read event. Did the Wayland compositor die?"); + else + qWarning("The Wayland connection experienced a fatal error during blocking read event: %s", strerror(ecode)); + _exit(-1); + } + buffer = getBuffer(sizeWithMargins, bufferWasRecreated); + } + + qsizetype oldSizeInBytes = mBackBuffer ? mBackBuffer->image()->sizeInBytes() : 0; + qsizetype newSizeInBytes = buffer->image()->sizeInBytes(); + + // mBackBuffer may have been deleted here but if so it means its size was different so we wouldn't copy it anyway + if (mBackBuffer != buffer && oldSizeInBytes == newSizeInBytes) { + Q_ASSERT(mBackBuffer); + const QImage *sourceImage = mBackBuffer->image(); + QImage *targetImage = buffer->image(); + + QPainter painter(targetImage); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.setClipRegion(buffer->dirtyRegion()); + painter.drawImage(QRectF(QPointF(), targetImage->deviceIndependentSize()), *sourceImage, sourceImage->rect()); + } + + mBackBuffer = buffer; + + for (QWaylandShmBuffer *buffer : std::as_const(mBuffers)) { + if (mBackBuffer == buffer) { + buffer->setAge(0); + } else { + buffer->setAge(buffer->age() + 1); + } + } + + if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes) + windowDecoration()->update(); + + buffer->dirtyRegion() = QRegion(); + + return bufferWasRecreated; +} + +QImage *QWaylandShmBackingStore::entireSurface() const +{ + return mBackBuffer->image(); +} + +QImage *QWaylandShmBackingStore::contentSurface() const +{ + return windowDecoration() ? mBackBuffer->imageInsideMargins(windowDecorationMargins()) : mBackBuffer->image(); +} + +void QWaylandShmBackingStore::updateDecorations() +{ + QPainter decorationPainter(entireSurface()); + decorationPainter.setCompositionMode(QPainter::CompositionMode_Source); + QImage sourceImage = windowDecoration()->contentImage(); + + qreal dp = sourceImage.devicePixelRatio(); + int dpWidth = int(sourceImage.width() / dp); + int dpHeight = int(sourceImage.height() / dp); + QTransform sourceMatrix; + sourceMatrix.scale(dp, dp); + QRect target; // needs to be in device independent pixels + QRegion dirtyRegion; + + //Top + target.setX(0); + target.setY(0); + target.setWidth(dpWidth); + target.setHeight(windowDecorationMargins().top()); + decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target)); + dirtyRegion += target; + + //Left + target.setWidth(windowDecorationMargins().left()); + target.setHeight(dpHeight); + decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target)); + dirtyRegion += target; + + //Right + target.setX(dpWidth - windowDecorationMargins().right()); + target.setWidth(windowDecorationMargins().right()); + decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target)); + dirtyRegion += target; + + //Bottom + target.setX(0); + target.setY(dpHeight - windowDecorationMargins().bottom()); + target.setWidth(dpWidth); + target.setHeight(windowDecorationMargins().bottom()); + decorationPainter.drawImage(target, sourceImage, sourceMatrix.mapRect(target)); + dirtyRegion += target; + + updateDirtyStates(dirtyRegion); +} + +QWaylandAbstractDecoration *QWaylandShmBackingStore::windowDecoration() const +{ + return waylandWindow()->decoration(); +} + +QMargins QWaylandShmBackingStore::windowDecorationMargins() const +{ + if (windowDecoration()) + return windowDecoration()->margins(); + return QMargins(); +} + +QWaylandWindow *QWaylandShmBackingStore::waylandWindow() const +{ + return static_cast(window()->handle()); +} + +#if QT_CONFIG(opengl) +QImage QWaylandShmBackingStore::toImage() const +{ + // Invoked from QPlatformBackingStore::composeAndFlush() that is called + // instead of flush() for widgets that have renderToTexture children + // (QOpenGLWidget, QQuickWidget). + + return *contentSurface(); +} +#endif // opengl + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h b/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h new file mode 100644 index 00000000000..183c8c570d8 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h @@ -0,0 +1,108 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDSHMBACKINGSTORE_H +#define QWAYLANDSHMBACKINGSTORE_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; +class QWaylandAbstractDecoration; +class QWaylandWindow; + +class Q_WAYLANDCLIENT_EXPORT QWaylandShmBuffer : public QWaylandBuffer { +public: + QWaylandShmBuffer(QWaylandDisplay *display, + const QSize &size, QImage::Format format, qreal scale = 1, wl_event_queue *customEventQueue = nullptr); + ~QWaylandShmBuffer() override; + QSize size() const override { return mImage.size(); } + int scale() const override { return int(mImage.devicePixelRatio()); } + QImage *image() { return &mImage; } + + QImage *imageInsideMargins(const QMargins &margins); + + QRegion &dirtyRegion() { return mDirtyRegion; } + + uint age() const { return mAge; } + void setAge(uint age) { mAge = age; } + +private: + QImage mImage; + struct wl_shm_pool *mShmPool = nullptr; + QMargins mMargins; + QImage *mMarginsImage = nullptr; + QRegion mDirtyRegion; + uint mAge = 0; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandShmBackingStore : public QPlatformBackingStore +{ +public: + QWaylandShmBackingStore(QWindow *window, QWaylandDisplay *display); + ~QWaylandShmBackingStore() override; + + QPaintDevice *paintDevice() override; + void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; + void resize(const QSize &size, const QRegion &staticContents) override; + void beginPaint(const QRegion ®ion) override; + void endPaint() override; + bool scroll(const QRegion ®ion, int dx, int dy) override; + + QWaylandAbstractDecoration *windowDecoration() const; + + QMargins windowDecorationMargins() const; + QImage *entireSurface() const; + QImage *contentSurface() const; + bool recreateBackBufferIfNeeded(); + + QWaylandWindow *waylandWindow() const; + void iterateBuffer(); + +#if QT_CONFIG(opengl) + QImage toImage() const override; +#endif + +private: + void updateDirtyStates(const QRegion ®ion); + void updateDecorations(); + QWaylandShmBuffer *getBuffer(const QSize &size, bool &bufferWasRecreated); + + QWaylandDisplay *mDisplay = nullptr; + QList mBuffers; + QWaylandShmBuffer *mFrontBuffer = nullptr; + QWaylandShmBuffer *mBackBuffer = nullptr; + bool mPainting = false; + bool mPendingFlush = false; + QRegion mPendingRegion; + QMutex mMutex; + + QSize mRequestedSize; + Qt::WindowFlags mCurrentWindowFlags; + struct wl_event_queue *mEventQueue = nullptr; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/qwaylandshmwindow.cpp b/src/plugins/platforms/wayland/qwaylandshmwindow.cpp new file mode 100644 index 00000000000..10c90d1c157 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandshmwindow.cpp @@ -0,0 +1,45 @@ +// Copyright (C) 2016 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 + +#include "qwaylandshmwindow_p.h" + +#include "qwaylandbuffer_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandShmWindow::QWaylandShmWindow(QWindow *window, QWaylandDisplay *display) + : QWaylandWindow(window, display) +{ + mSurfaceFormat.setRedBufferSize(8); + mSurfaceFormat.setGreenBufferSize(8); + mSurfaceFormat.setBlueBufferSize(8); + + const QSurfaceFormat format = window->requestedFormat(); + mSurfaceFormat.setAlphaBufferSize(format.hasAlpha() ? 8 : 0); +} + +QWaylandShmWindow::~QWaylandShmWindow() +{ +} + +QWaylandWindow::WindowType QWaylandShmWindow::windowType() const +{ + return QWaylandWindow::Shm; +} + +void QWaylandShmWindow::setWindowFlags(Qt::WindowFlags flags) +{ + QWaylandWindow::setWindowFlags(flags); + + const QSurfaceFormat format = window()->requestedFormat(); + if (!format.hasAlpha()) + mSurfaceFormat.setAlphaBufferSize(mWindowDecorationEnabled ? 8 : 0); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandshmwindow_p.h b/src/plugins/platforms/wayland/qwaylandshmwindow_p.h new file mode 100644 index 00000000000..dab9e1e5350 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandshmwindow_p.h @@ -0,0 +1,40 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDSHMWINDOW_H +#define QWAYLANDSHMWINDOW_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class Q_WAYLANDCLIENT_EXPORT QWaylandShmWindow : public QWaylandWindow +{ +public: + QWaylandShmWindow(QWindow *window, QWaylandDisplay *display); + ~QWaylandShmWindow() override; + + WindowType windowType() const override; + + void setWindowFlags(Qt::WindowFlags flags) override; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDSHMWINDOW_H diff --git a/src/plugins/platforms/wayland/qwaylandsubsurface.cpp b/src/plugins/platforms/wayland/qwaylandsubsurface.cpp new file mode 100644 index 00000000000..a0afd06ed29 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandsubsurface.cpp @@ -0,0 +1,55 @@ +// Copyright (C) 2016 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 + +#include "qwaylandsubsurface_p.h" + +#include "qwaylandwindow_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandSubSurface::QWaylandSubSurface(QWaylandWindow *window, QWaylandWindow *parent, ::wl_subsurface *sub_surface) + : QtWayland::wl_subsurface(sub_surface) + , m_window(window) + , m_parent(parent) +{ + m_parent->mChildren << this; + setDeSync(); +} + +QWaylandSubSurface::~QWaylandSubSurface() +{ + m_parent->mChildren.removeOne(this); + destroy(); +} + +void QWaylandSubSurface::setSync() +{ + QMutexLocker l(&m_syncLock); + QWaylandSubSurface::set_sync(); +} + +void QWaylandSubSurface::setDeSync() +{ + QMutexLocker l(&m_syncLock); + QWaylandSubSurface::set_desync(); +} + +void QWaylandSubSurface::set_sync() +{ + m_synchronized = true; + QtWayland::wl_subsurface::set_sync(); +} + +void QWaylandSubSurface::set_desync() +{ + m_synchronized = false; + QtWayland::wl_subsurface::set_desync(); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandsubsurface_p.h b/src/plugins/platforms/wayland/qwaylandsubsurface_p.h new file mode 100644 index 00000000000..7600c580704 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandsubsurface_p.h @@ -0,0 +1,62 @@ +// Copyright (C) 2018 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 + +#ifndef QWAYLANDSUBSURFACE_H +#define QWAYLANDSUBSURFACE_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; +class QWaylandWindow; + +class Q_WAYLANDCLIENT_EXPORT QWaylandSubSurface : public QtWayland::wl_subsurface +{ +public: + QWaylandSubSurface(QWaylandWindow *window, QWaylandWindow *parent, ::wl_subsurface *subsurface); + ~QWaylandSubSurface() override; + + QWaylandWindow *window() const { return m_window; } + QWaylandWindow *parent() const { return m_parent; } + + void setSync(); + void setDeSync(); + bool isSync() const { return m_synchronized; } + QMutex *syncMutex() { return &m_syncLock; } + +private: + + // Intentionally hide public methods from ::wl_subsurface + // to keep track of the sync state + void set_sync(); + void set_desync(); + QWaylandWindow *m_window = nullptr; + QWaylandWindow *m_parent = nullptr; + bool m_synchronized = false; + QMutex m_syncLock; + +}; + +QT_END_NAMESPACE + +} + +#endif // QWAYLANDSUBSURFACE_H diff --git a/src/plugins/platforms/wayland/qwaylandsurface.cpp b/src/plugins/platforms/wayland/qwaylandsurface.cpp new file mode 100644 index 00000000000..274fdda82e5 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandsurface.cpp @@ -0,0 +1,116 @@ +// 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 + +#include "qwaylandsurface_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandscreen_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandSurface::QWaylandSurface(QWaylandDisplay *display) + : wl_surface(display->createSurface(this)) +{ + connect(qApp, &QGuiApplication::screenRemoved, this, &QWaylandSurface::handleScreenRemoved); + connect(qApp, &QGuiApplication::screenAdded, this, &QWaylandSurface::screensChanged); +} + +QWaylandSurface::~QWaylandSurface() +{ + destroy(); +} + +QWaylandScreen *QWaylandSurface::oldestEnteredScreen() +{ + for (auto *screen : std::as_const(m_screens)) { + // only report valid screens + // we can have some ouptuts waiting for xdg output information + // that are valid QPlatformScreens, but not valid QScreens + if (screen->screen()) + return screen; + } + return nullptr; +} + +QWaylandSurface *QWaylandSurface::fromWlSurface(::wl_surface *surface) +{ + if (auto *s = QtWayland::wl_surface::fromObject(surface)) + return static_cast(s); + return nullptr; +} + +void QWaylandSurface::handleScreenRemoved(QScreen *qScreen) +{ + auto *platformScreen = qScreen->handle(); + if (platformScreen->isPlaceholder()) + return; + + auto *waylandScreen = static_cast(qScreen->handle()); + if (m_screens.removeOne(waylandScreen)) + emit screensChanged(); +} + +void QWaylandSurface::surface_enter(wl_output *output) +{ + auto addedScreen = QWaylandScreen::fromWlOutput(output); + + if (!addedScreen) + return; + + if (m_screens.contains(addedScreen)) { + qCWarning(lcQpaWayland) + << "Ignoring unexpected wl_surface.enter received for output with id:" + << wl_proxy_get_id(reinterpret_cast(output)) + << "screen name:" << addedScreen->name() << "screen model:" << addedScreen->model() + << "This is most likely a bug in the compositor."; + return; + } + + m_screens.append(addedScreen); + emit screensChanged(); +} + +void QWaylandSurface::surface_leave(wl_output *output) +{ + auto *removedScreen = QWaylandScreen::fromWlOutput(output); + + if (!removedScreen) + return; + + bool wasRemoved = m_screens.removeOne(removedScreen); + if (!wasRemoved) { + qCWarning(lcQpaWayland) + << "Ignoring unexpected wl_surface.leave received for output with id:" + << wl_proxy_get_id(reinterpret_cast(output)) + << "screen name:" << removedScreen->name() + << "screen model:" << removedScreen->model() + << "This is most likely a bug in the compositor."; + return; + } + emit screensChanged(); +} + +void QWaylandSurface::surface_preferred_buffer_scale(int32_t scale) +{ + if (m_preferredBufferScale == scale) + return; + m_preferredBufferScale = scale; + Q_EMIT preferredBufferScaleChanged(); +} + +void QWaylandSurface::surface_preferred_buffer_transform(uint32_t transform) +{ + if (m_preferredBufferTransform == transform) + return; + m_preferredBufferTransform = static_cast(transform); + Q_EMIT preferredBufferTransformChanged(); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#include "moc_qwaylandsurface_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylandsurface_p.h b/src/plugins/platforms/wayland/qwaylandsurface_p.h new file mode 100644 index 00000000000..41860297e62 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandsurface_p.h @@ -0,0 +1,70 @@ +// 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 + +#ifndef QWAYLANDSURFACE_P_H +#define QWAYLANDSURFACE_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandScreen; +class QWaylandWindow; +class QWaylandDisplay; + +class QWaylandSurface : public QObject, public QtWayland::wl_surface +{ + Q_OBJECT +public: + explicit QWaylandSurface(QWaylandDisplay *display); + ~QWaylandSurface() override; + QWaylandScreen *oldestEnteredScreen(); + QWaylandWindow *waylandWindow() const { return m_window; } + std::optional preferredBufferScale() const { return m_preferredBufferScale; } + std::optional preferredBufferTransform() const { return m_preferredBufferTransform; } + + static QWaylandSurface *fromWlSurface(::wl_surface *surface); + +Q_SIGNALS: + void screensChanged(); + void preferredBufferScaleChanged(); + void preferredBufferTransformChanged(); + +private Q_SLOTS: + void handleScreenRemoved(QScreen *qScreen); + +protected: + void surface_enter(struct ::wl_output *output) override; + void surface_leave(struct ::wl_output *output) override; + void surface_preferred_buffer_scale(int32_t scale) override; + void surface_preferred_buffer_transform(uint32_t transform) override; + + QList m_screens; //As seen by wl_surface.enter/leave events. Chronological order. + QWaylandWindow *m_window = nullptr; + std::optional m_preferredBufferScale; + std::optional m_preferredBufferTransform; + + friend class QWaylandWindow; // TODO: shouldn't need to be friends +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDSURFACE_P_H diff --git a/src/plugins/platforms/wayland/qwaylandtabletv2.cpp b/src/plugins/platforms/wayland/qwaylandtabletv2.cpp new file mode 100644 index 00000000000..8973d67b72f --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandtabletv2.cpp @@ -0,0 +1,640 @@ +// 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 + +#include "qwaylandtabletv2_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandsurface_p.h" +#include "qwaylandscreen_p.h" +#include "qwaylandbuffer_p.h" +#include "qwaylandcursorsurface_p.h" +#include "qwaylandcursor_p.h" + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +using namespace Qt::StringLiterals; + +#if QT_CONFIG(cursor) +int QWaylandTabletToolV2::idealCursorScale() const +{ + if (m_tabletSeat->seat()->mQDisplay->compositor()->version() < 3) { + return 1; + } + + if (auto *s = mCursor.surface.data()) { + if (s->outputScale() > 0) + return s->outputScale(); + } + + return m_tabletSeat->seat()->mCursor.fallbackOutputScale; +} + +void QWaylandTabletToolV2::updateCursorTheme() +{ + QString cursorThemeName; + QSize cursorSize; + + if (const QPlatformTheme *platformTheme = QGuiApplicationPrivate::platformTheme()) { + cursorThemeName = platformTheme->themeHint(QPlatformTheme::MouseCursorTheme).toString(); + cursorSize = platformTheme->themeHint(QPlatformTheme::MouseCursorSize).toSize(); + } + + if (cursorThemeName.isEmpty()) + cursorThemeName = QStringLiteral("default"); + if (cursorSize.isEmpty()) + cursorSize = QSize(24, 24); + + int scale = idealCursorScale(); + int pixelSize = cursorSize.width() * scale; + auto *display = m_tabletSeat->seat()->mQDisplay; + mCursor.theme = display->loadCursorTheme(cursorThemeName, pixelSize); + + if (!mCursor.theme) + return; // A warning has already been printed in loadCursorTheme + + if (auto *arrow = mCursor.theme->cursor(Qt::ArrowCursor)) { + int arrowPixelSize = qMax(arrow->images[0]->width, + arrow->images[0]->height); // Not all cursor themes are square + while (scale > 1 && arrowPixelSize / scale < cursorSize.width()) + --scale; + } else { + qCWarning(lcQpaWayland) << "Cursor theme does not support the arrow cursor"; + } + mCursor.themeBufferScale = scale; +} + +void QWaylandTabletToolV2::updateCursor() +{ + if (mEnterSerial == 0) + return; + + auto shape = m_tabletSeat->seat()->mCursor.shape; + + if (shape == Qt::BlankCursor) { + if (mCursor.surface) + mCursor.surface->reset(); + set_cursor(mEnterSerial, nullptr, 0, 0); + return; + } + + if (shape == Qt::BitmapCursor) { + auto buffer = m_tabletSeat->seat()->mCursor.bitmapBuffer; + if (!buffer) { + qCWarning(lcQpaWayland) << "No buffer for bitmap cursor, can't set cursor"; + return; + } + auto hotspot = m_tabletSeat->seat()->mCursor.hotspot; + int bufferScale = m_tabletSeat->seat()->mCursor.bitmapScale; + getOrCreateCursorSurface()->update(buffer->buffer(), hotspot, buffer->size(), bufferScale); + return; + } + + if (mCursor.shape) { + if (mCursor.surface) { + mCursor.surface->reset(); + } + mCursor.shape->setShape(mEnterSerial, shape); + return; + } + + if (!mCursor.theme || idealCursorScale() != mCursor.themeBufferScale) + updateCursorTheme(); + + if (!mCursor.theme) + return; + + // Set from shape using theme + QElapsedTimer &timer = m_tabletSeat->seat()->mCursor.animationTimer; + const uint time = timer.isValid() ? timer.elapsed() : 0; + + if (struct ::wl_cursor *waylandCursor = mCursor.theme->cursor(shape)) { + uint duration = 0; + int frame = wl_cursor_frame_and_duration(waylandCursor, time, &duration); + ::wl_cursor_image *image = waylandCursor->images[frame]; + + struct wl_buffer *buffer = wl_cursor_image_get_buffer(image); + if (!buffer) { + qCWarning(lcQpaWayland) << "Could not find buffer for cursor" << shape; + return; + } + int bufferScale = mCursor.themeBufferScale; + QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale; + QSize size = QSize(image->width, image->height) / bufferScale; + bool animated = duration > 0; + if (animated) { + mCursor.gotFrameCallback = false; + mCursor.gotTimerCallback = false; + mCursor.frameTimer.start(duration); + } + getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale, animated); + return; + } + + qCWarning(lcQpaWayland) << "Unable to change to cursor" << shape; +} + +CursorSurface *QWaylandTabletToolV2::getOrCreateCursorSurface() +{ + if (!mCursor.surface) + mCursor.surface.reset( + new CursorSurface(this, m_tabletSeat->seat()->mQDisplay)); + return mCursor.surface.get(); +} + +void QWaylandTabletToolV2::cursorTimerCallback() +{ + mCursor.gotTimerCallback = true; + if (mCursor.gotFrameCallback) + updateCursor(); +} + +void QWaylandTabletToolV2::cursorFrameCallback() +{ + mCursor.gotFrameCallback = true; + if (mCursor.gotTimerCallback) + updateCursor(); +} + +#endif // QT_CONFIG(cursor) + +QWaylandTabletManagerV2::QWaylandTabletManagerV2(QWaylandDisplay *display, uint id, uint version) + : zwp_tablet_manager_v2(display->wl_registry(), id, qMin(version, uint(1))) +{ + qCDebug(lcQpaInputDevices, "new tablet manager: ID %d version %d", id, version); +} + +QWaylandTabletManagerV2::~QWaylandTabletManagerV2() +{ + destroy(); +} + +QWaylandTabletSeatV2::QWaylandTabletSeatV2(QWaylandTabletManagerV2 *manager, QWaylandInputDevice *seat) + : QtWayland::zwp_tablet_seat_v2(manager->get_tablet_seat(seat->wl_seat())) + , m_seat(seat) +{ + qCDebug(lcQpaInputDevices) << "new tablet seat" << seat->seatname() << "id" << seat->id(); +} + +QWaylandTabletSeatV2::~QWaylandTabletSeatV2() +{ + qDeleteAll(m_tablets); + qDeleteAll(m_tools); + qDeleteAll(m_deadTools); + qDeleteAll(m_pads); + destroy(); +} + +void QWaylandTabletSeatV2::zwp_tablet_seat_v2_tablet_added(zwp_tablet_v2 *id) +{ + auto *tablet = new QWaylandTabletV2(id, m_seat->seatname()); + qCDebug(lcQpaInputDevices) << "seat" << this << id << "has tablet" << tablet; + tablet->setParent(this); + m_tablets.push_back(tablet); + connect(tablet, &QWaylandTabletV2::destroyed, this, [this, tablet] { m_tablets.removeOne(tablet); }); +} + +void QWaylandTabletSeatV2::zwp_tablet_seat_v2_tool_added(zwp_tablet_tool_v2 *id) +{ + qDeleteAll(m_deadTools); + auto *tool = new QWaylandTabletToolV2(this, id); + if (m_tablets.size() == 1) { + tool->setParent(m_tablets.first()); + QPointingDevicePrivate *d = QPointingDevicePrivate::get(tool); + d->name = m_tablets.first()->name() + u" stylus"; + } else { + qCDebug(lcQpaInputDevices) << "seat" << this << "has tool" << tool << "for one of these tablets:" << m_tablets; + // TODO identify which tablet if there are several; then tool->setParent(tablet) + } + m_tools.push_back(tool); + connect(tool, &QWaylandTabletToolV2::destroyed, this, [this, tool] { + m_tools.removeOne(tool); + m_deadTools.removeOne(tool); + }); +} + +void QWaylandTabletSeatV2::zwp_tablet_seat_v2_pad_added(zwp_tablet_pad_v2 *id) +{ + auto *pad = new QWaylandTabletPadV2(id); + if (m_tablets.size() == 1) { + pad->setParent(m_tablets.first()); + QPointingDevicePrivate *d = QPointingDevicePrivate::get(pad); + d->name = m_tablets.first()->name() + u" touchpad"; + } else { + qCDebug(lcQpaInputDevices) << "seat" << this << "has touchpad" << pad << "for one of these tablets:" << m_tablets; + // TODO identify which tablet if there are several + } + m_pads.push_back(pad); + connect(pad, &QWaylandTabletPadV2::destroyed, this, [this, pad] { m_pads.removeOne(pad); }); +} + +QWaylandTabletV2::QWaylandTabletV2(::zwp_tablet_v2 *tablet, const QString &seatName) + : QPointingDevice(u"unknown"_s, -1, DeviceType::Stylus, PointerType::Pen, + Capability::Position | Capability::Hover, + 1, 1, seatName) + , QtWayland::zwp_tablet_v2(tablet) +{ + qCDebug(lcQpaInputDevices) << "new tablet on seat" << seatName; + QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); + d->seatName = seatName; +} + +QWaylandTabletV2::~QWaylandTabletV2() +{ + destroy(); +} + +void QWaylandTabletV2::zwp_tablet_v2_name(const QString &name) +{ + QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); + d->name = name; +} + +void QWaylandTabletV2::zwp_tablet_v2_id(uint32_t vid, uint32_t pid) +{ + QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); + d->systemId = (quint64(vid) << 32) | pid; + qCDebug(lcQpaInputDevices) << "vid" << vid << "pid" << pid << "stored as systemId in" << this; +} + +void QWaylandTabletV2::zwp_tablet_v2_path(const QString &path) +{ + QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); + d->busId = path; +} + +void QWaylandTabletV2::zwp_tablet_v2_done() +{ + QWindowSystemInterface::registerInputDevice(this); +} + +void QWaylandTabletSeatV2::updateCursor() +{ + for (auto tool : m_tools) + tool->updateCursor(); +} + +void QWaylandTabletSeatV2::toolRemoved(QWaylandTabletToolV2 *tool) +{ + m_tools.removeOne(tool); + m_deadTools.append(tool); +} + +void QWaylandTabletV2::zwp_tablet_v2_removed() +{ + deleteLater(); +} + +QWaylandTabletToolV2::QWaylandTabletToolV2(QWaylandTabletSeatV2 *tabletSeat, ::zwp_tablet_tool_v2 *tool) + : QPointingDevice(u"tool"_s, -1, DeviceType::Stylus, PointerType::Pen, + Capability::Position | Capability::Hover, + 1, 1, tabletSeat->seat()->seatname()) + , QtWayland::zwp_tablet_tool_v2(tool) + , m_tabletSeat(tabletSeat) +{ + // TODO get the number of buttons somehow? + +#if QT_CONFIG(cursor) + if (auto cursorShapeManager = m_tabletSeat->seat()->mQDisplay->cursorShapeManager()) { + mCursor.shape.reset( + new QWaylandCursorShape(cursorShapeManager->get_tablet_tool_v2(object()))); + } + + mCursor.frameTimer.setSingleShot(true); + mCursor.frameTimer.callOnTimeout(this, [&]() { cursorTimerCallback(); }); +#endif +} + +QWaylandTabletToolV2::~QWaylandTabletToolV2() +{ + destroy(); +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_type(uint32_t tool_type) +{ + QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); + + switch (tool_type) { + case type_airbrush: + case type_brush: + case type_pencil: + case type_pen: + d->pointerType = QPointingDevice::PointerType::Pen; + break; + case type_eraser: + d->pointerType = QPointingDevice::PointerType::Eraser; + break; + case type_mouse: + case type_lens: + d->pointerType = QPointingDevice::PointerType::Cursor; + break; + case type_finger: + d->pointerType = QPointingDevice::PointerType::Unknown; + break; + } + + switch (tool_type) { + case type::type_airbrush: + d->deviceType = QInputDevice::DeviceType::Airbrush; + d->capabilities.setFlag(QInputDevice::Capability::TangentialPressure); + break; + case type::type_brush: + case type::type_pencil: + case type::type_pen: + case type::type_eraser: + d->deviceType = QInputDevice::DeviceType::Stylus; + break; + case type::type_lens: + d->deviceType = QInputDevice::DeviceType::Puck; + break; + case type::type_mouse: + case type::type_finger: + d->deviceType = QInputDevice::DeviceType::Unknown; + break; + } +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_hardware_serial(uint32_t hardware_serial_hi, uint32_t hardware_serial_lo) +{ + QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); + d->uniqueId = QPointingDeviceUniqueId::fromNumericId((quint64(hardware_serial_hi) << 32) + hardware_serial_lo); +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_hardware_id_wacom(uint32_t hardware_id_hi, uint32_t hardware_id_lo) +{ + QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); + d->systemId = (quint64(hardware_id_hi) << 32) + hardware_id_lo; +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_capability(uint32_t capability) +{ + QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); + switch (capability) { + case capability_tilt: + // no distinction... we have to assume it has both axes + d->capabilities.setFlag(QInputDevice::Capability::XTilt); + d->capabilities.setFlag(QInputDevice::Capability::YTilt); + break; + case capability_pressure: + d->capabilities.setFlag(QInputDevice::Capability::Pressure); + break; + case capability_distance: + d->capabilities.setFlag(QInputDevice::Capability::ZPosition); + break; + case capability_rotation: + d->capabilities.setFlag(QInputDevice::Capability::Rotation); + break; + case capability_slider: + // nothing to represent that so far + break; + case capability_wheel: + d->capabilities.setFlag(QInputDevice::Capability::Scroll); + d->capabilities.setFlag(QInputDevice::Capability::PixelScroll); + break; + } + qCDebug(lcQpaInputDevices) << capability << "->" << this; +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_done() +{ + QWindowSystemInterface::registerInputDevice(this); +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_removed() +{ + m_tabletSeat->toolRemoved(this); +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_proximity_in(uint32_t serial, zwp_tablet_v2 *tablet, wl_surface *surface) +{ + Q_UNUSED(tablet); + + m_tabletSeat->seat()->mSerial = serial; + mEnterSerial = serial; + + if (Q_UNLIKELY(!surface)) { + qCDebug(lcQpaWayland) << "Ignoring zwp_tablet_tool_v2_proximity_v2 with no surface"; + return; + } + m_pending.enteredSurface = true; + m_pending.proximitySurface = QWaylandSurface::fromWlSurface(surface); + +#if QT_CONFIG(cursor) + // Depends on mEnterSerial being updated + updateCursor(); +#endif +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_proximity_out() +{ + m_pending.enteredSurface = false; + m_pending.proximitySurface = nullptr; +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_down(uint32_t serial) +{ + m_pending.down = true; + + m_tabletSeat->seat()->mSerial = serial; + + if (m_pending.proximitySurface) { + if (QWaylandWindow *window = m_pending.proximitySurface->waylandWindow()) { + QWaylandInputDevice *seat = m_tabletSeat->seat(); + seat->display()->setLastInputDevice(seat, serial, window); + } + } +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_up() +{ + m_pending.down = false; +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_motion(wl_fixed_t x, wl_fixed_t y) +{ + m_pending.surfacePosition = QPointF(wl_fixed_to_double(x), wl_fixed_to_double(y)); +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_pressure(uint32_t pressure) +{ + const int maxPressure = 65535; + m_pending.pressure = qreal(pressure)/maxPressure; +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_distance(uint32_t distance) +{ + m_pending.distance = distance; +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_tilt(wl_fixed_t tilt_x, wl_fixed_t tilt_y) +{ + m_pending.xTilt = wl_fixed_to_double(tilt_x); + m_pending.yTilt = wl_fixed_to_double(tilt_y); +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_rotation(wl_fixed_t degrees) +{ + m_pending.rotation = wl_fixed_to_double(degrees); +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_slider(int32_t position) +{ + m_pending.slider = qreal(position) / 65535; +} + +static Qt::MouseButton mouseButtonFromTablet(uint button) +{ + switch (button) { + case 0x110: return Qt::MouseButton::LeftButton; // BTN_LEFT + case 0x14b: return Qt::MouseButton::MiddleButton; // BTN_STYLUS + case 0x14c: return Qt::MouseButton::RightButton; // BTN_STYLUS2 + default: + return Qt::NoButton; + } +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_button(uint32_t serial, uint32_t button, uint32_t state) +{ + m_tabletSeat->seat()->mSerial = serial; + + QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); + Qt::MouseButton mouseButton = mouseButtonFromTablet(button); + if (state == button_state_pressed) + m_pending.buttons |= mouseButton; + else + m_pending.buttons &= ~mouseButton; + // ideally we'd get button count when the tool is discovered; seems to be a shortcoming in tablet-unstable-v2 + // but if we get events from buttons we didn't know existed, increase it + if (mouseButton == Qt::RightButton) + d->buttonCount = qMax(d->buttonCount, 2); + else if (mouseButton == Qt::MiddleButton) + d->buttonCount = qMax(d->buttonCount, 3); +} + +void QWaylandTabletToolV2::zwp_tablet_tool_v2_frame(uint32_t time) +{ + if (!m_pending.proximitySurface) { + if (m_applied.enteredSurface) { + // leaving proximity + QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(nullptr, this, false); + m_pending = State(); // Don't leave pressure etc. lying around when we enter the next surface + m_applied = State(); + } else { + qCWarning(lcQpaWayland) << "Can't send tablet event with no proximity surface, ignoring"; + } + return; + } + + QWaylandWindow *waylandWindow = QWaylandWindow::fromWlSurface(m_pending.proximitySurface->object()); + QWindow *window = waylandWindow->window(); + + if (!m_applied.proximitySurface) { + // TODO get position etc. as below + QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, this, true); + m_applied.proximitySurface = m_pending.proximitySurface; + } + + if (!(m_pending == m_applied)) { + ulong timestamp = time; + const QPointF localPosition = waylandWindow->mapFromWlSurface(m_pending.surfacePosition); + + const QPointF globalPosition = waylandWindow->mapToGlobalF(localPosition); + + Qt::MouseButtons buttons = m_pending.down ? Qt::MouseButton::LeftButton : Qt::MouseButton::NoButton; + buttons |= m_pending.buttons; + qreal pressure = m_pending.pressure; + qreal xTilt = m_pending.xTilt; + qreal yTilt = m_pending.yTilt; + qreal tangentialPressure = m_pending.slider; + qreal rotation = m_pending.rotation; + int z = int(m_pending.distance); + + // do not use localPosition here since that is in Qt window coordinates + // but we need surface coordinates to include the decoration + bool decorationHandledEvent = waylandWindow->handleTabletEventDecoration( + m_tabletSeat->seat(), m_pending.surfacePosition, + window->mapToGlobal(m_pending.surfacePosition), buttons, + m_tabletSeat->seat()->modifiers()); + + if (!decorationHandledEvent) { + QWindowSystemInterface::handleTabletEvent(window, timestamp, this, localPosition, globalPosition, + buttons, pressure, + xTilt, yTilt, tangentialPressure, rotation, z, + m_tabletSeat->seat()->modifiers()); + } + } + + m_applied = m_pending; +} + +// TODO: delete when upgrading to c++20 +bool QWaylandTabletToolV2::State::operator==(const QWaylandTabletToolV2::State &o) const { + return + down == o.down && + proximitySurface.data() == o.proximitySurface.data() && + enteredSurface == o.enteredSurface && + surfacePosition == o.surfacePosition && + distance == o.distance && + pressure == o.pressure && + rotation == o.rotation && + xTilt == o.xTilt && + yTilt == o.yTilt && + slider == o.slider && + buttons == o.buttons; +} + +QWaylandTabletPadV2::QWaylandTabletPadV2(::zwp_tablet_pad_v2 *pad) + : QPointingDevice(u"tablet touchpad"_s, -1, DeviceType::TouchPad, PointerType::Finger, + Capability::Position, + 1, 1) + , QtWayland::zwp_tablet_pad_v2(pad) +{ +} + +QWaylandTabletPadV2::~QWaylandTabletPadV2() +{ + destroy(); +} + +void QWaylandTabletPadV2::zwp_tablet_pad_v2_path(const QString &path) +{ + QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); + d->busId = path; +} + +void QWaylandTabletPadV2::zwp_tablet_pad_v2_buttons(uint32_t buttons) +{ + QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); + d->buttonCount = buttons; +} + +void QWaylandTabletPadV2::zwp_tablet_pad_v2_group(zwp_tablet_pad_group_v2 *pad_group) +{ + // As of writing Qt does not handle tablet pads group and the controls on it + // This proxy is server created so it is just deleted here to not leak it + zwp_tablet_pad_group_v2_destroy(pad_group); +} + +void QWaylandTabletPadV2::zwp_tablet_pad_v2_done() +{ + QWindowSystemInterface::registerInputDevice(this); +} + +void QWaylandTabletPadV2::zwp_tablet_pad_v2_removed() +{ + delete this; +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#include "moc_qwaylandtabletv2_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylandtabletv2_p.h b/src/plugins/platforms/wayland/qwaylandtabletv2_p.h new file mode 100644 index 00000000000..6c8ef272723 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandtabletv2_p.h @@ -0,0 +1,202 @@ +// 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 + +#ifndef QWAYLANDTABLETV2_P_H +#define QWAYLANDTABLETV2_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; +class QWaylandInputDevice; +class QWaylandSurface; + +class QWaylandTabletSeatV2; +class QWaylandTabletV2; +class QWaylandTabletToolV2; +class QWaylandTabletPadV2; + +#if QT_CONFIG(cursor) +class QWaylandCursorTheme; +class QWaylandCursorShape; +template +class CursorSurface; +#endif + +class Q_WAYLANDCLIENT_EXPORT QWaylandTabletManagerV2 : public QtWayland::zwp_tablet_manager_v2 +{ +public: + explicit QWaylandTabletManagerV2(QWaylandDisplay *display, uint id, uint version); + ~QWaylandTabletManagerV2() override; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandTabletSeatV2 : public QObject, public QtWayland::zwp_tablet_seat_v2 +{ + Q_OBJECT +public: + explicit QWaylandTabletSeatV2(QWaylandTabletManagerV2 *manager, QWaylandInputDevice *seat); + ~QWaylandTabletSeatV2() override; + + QWaylandInputDevice *seat() const { return m_seat; } + + void updateCursor(); + void toolRemoved(QWaylandTabletToolV2 *tool); + +protected: + void zwp_tablet_seat_v2_tablet_added(struct ::zwp_tablet_v2 *id) override; + void zwp_tablet_seat_v2_tool_added(struct ::zwp_tablet_tool_v2 *id) override; + void zwp_tablet_seat_v2_pad_added(struct ::zwp_tablet_pad_v2 *id) override; + +private: + QWaylandInputDevice *m_seat; + QList m_tablets; + QList m_tools; + QList m_deadTools; + QList m_pads; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandTabletV2 : public QPointingDevice, public QtWayland::zwp_tablet_v2 +{ + Q_OBJECT +public: + explicit QWaylandTabletV2(::zwp_tablet_v2 *tablet, const QString &seatName); + ~QWaylandTabletV2(); + +protected: + // callbacks which act as setters + void zwp_tablet_v2_name(const QString &name) override; + void zwp_tablet_v2_id(uint32_t vid, uint32_t pid) override; + void zwp_tablet_v2_path(const QString &path) override; + void zwp_tablet_v2_done() override; + void zwp_tablet_v2_removed() override; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandTabletToolV2 : public QPointingDevice, public QtWayland::zwp_tablet_tool_v2 +{ + Q_OBJECT +public: + QWaylandTabletToolV2(QWaylandTabletSeatV2 *tabletSeat, ::zwp_tablet_tool_v2 *tool); + ~QWaylandTabletToolV2(); + + void updateCursor(); + +protected: + void zwp_tablet_tool_v2_type(uint32_t tool_type) override; + void zwp_tablet_tool_v2_hardware_serial(uint32_t hardware_serial_hi, uint32_t hardware_serial_lo) override; + void zwp_tablet_tool_v2_hardware_id_wacom(uint32_t hardware_id_hi, uint32_t hardware_id_lo) override; + void zwp_tablet_tool_v2_capability(uint32_t capability) override; + void zwp_tablet_tool_v2_done() override; + void zwp_tablet_tool_v2_removed() override; + void zwp_tablet_tool_v2_proximity_in(uint32_t serial, struct ::zwp_tablet_v2 *tablet, struct ::wl_surface *surface) override; + void zwp_tablet_tool_v2_proximity_out() override; + void zwp_tablet_tool_v2_down(uint32_t serial) override; + void zwp_tablet_tool_v2_up() override; + void zwp_tablet_tool_v2_motion(wl_fixed_t x, wl_fixed_t y) override; + void zwp_tablet_tool_v2_pressure(uint32_t pressure) override; + void zwp_tablet_tool_v2_distance(uint32_t distance) override; + void zwp_tablet_tool_v2_tilt(wl_fixed_t tilt_x, wl_fixed_t tilt_y) override; + void zwp_tablet_tool_v2_rotation(wl_fixed_t degrees) override; + void zwp_tablet_tool_v2_slider(int32_t position) override; +// void zwp_tablet_tool_v2_wheel(wl_fixed_t degrees, int32_t clicks) override; + void zwp_tablet_tool_v2_button(uint32_t serial, uint32_t button, uint32_t state) override; + void zwp_tablet_tool_v2_frame(uint32_t time) override; + +private: +#if QT_CONFIG(cursor) + int idealCursorScale() const; + void updateCursorTheme(); + void cursorTimerCallback(); + void cursorFrameCallback(); + CursorSurface *getOrCreateCursorSurface(); +#endif + + QWaylandTabletSeatV2 *m_tabletSeat; + + // Static state (sent before done event) + QPointingDevice::PointerType m_pointerType = QPointingDevice::PointerType::Unknown; + QInputDevice::DeviceType m_tabletDevice = QInputDevice::DeviceType::Unknown; + zwp_tablet_tool_v2::type m_toolType = type_pen; + bool m_hasRotation = false; + quint64 m_uid = 0; + + uint32_t mEnterSerial = 0; +#if QT_CONFIG(cursor) + struct + { + QScopedPointer shape; + QWaylandCursorTheme *theme = nullptr; + int themeBufferScale = 0; + QScopedPointer> surface; + QTimer frameTimer; + bool gotFrameCallback = false; + bool gotTimerCallback = false; + } mCursor; +#endif + + // Accumulated state (applied on frame event) + struct State { + bool down = false; + QPointer proximitySurface; + bool enteredSurface = false; // Not enough with just proximitySurface, if the surface is deleted, we still want to send a leave event + QPointF surfacePosition; + uint distance = 0; + qreal pressure = 0; + qreal rotation = 0; + qreal xTilt = 0; + qreal yTilt = 0; + qreal slider = 0; + Qt::MouseButtons buttons = Qt::MouseButton::NoButton; // Actual buttons, down state -> left mouse is mapped inside the frame handler + //auto operator<=>(const Point&) const = default; // TODO: use this when upgrading to C++20 + bool operator==(const State &o) const; + } m_pending, m_applied; + + template + friend class CursorSurface; +}; + +class Q_WAYLANDCLIENT_EXPORT QWaylandTabletPadV2 : public QPointingDevice, public QtWayland::zwp_tablet_pad_v2 +{ + Q_OBJECT +public: + explicit QWaylandTabletPadV2(::zwp_tablet_pad_v2 *pad); + ~QWaylandTabletPadV2(); + +protected: + void zwp_tablet_pad_v2_group(struct ::zwp_tablet_pad_group_v2 *pad_group) override; + void zwp_tablet_pad_v2_path(const QString &path) override; + void zwp_tablet_pad_v2_buttons(uint32_t buttons) override; + void zwp_tablet_pad_v2_done() override; +// void zwp_tablet_pad_v2_button(uint32_t time, uint32_t button, uint32_t state) override; +// void zwp_tablet_pad_v2_enter(uint32_t serial, struct ::zwp_tablet_v2 *tablet, struct ::wl_surface *surface) override; +// void zwp_tablet_pad_v2_leave(uint32_t serial, struct ::wl_surface *surface) override; + void zwp_tablet_pad_v2_removed() override; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDTABLETV2_P_H diff --git a/src/plugins/platforms/wayland/qwaylandtextinputinterface.cpp b/src/plugins/platforms/wayland/qwaylandtextinputinterface.cpp new file mode 100644 index 00000000000..7943964755a --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandtextinputinterface.cpp @@ -0,0 +1,8 @@ +// Copyright (C) 2021 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 + +#include "qwaylandtextinputinterface_p.h" + +QT_BEGIN_NAMESPACE + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandtextinputinterface_p.h b/src/plugins/platforms/wayland/qwaylandtextinputinterface_p.h new file mode 100644 index 00000000000..6f695575fb0 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandtextinputinterface_p.h @@ -0,0 +1,61 @@ +// Copyright (C) 2021 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 + +#ifndef QWAYLANDTEXTINPUTINTERFACE_P_H +#define QWAYLANDTEXTINPUTINTERFACE_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 + +struct wl_surface; + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandTextInputInterface +{ +public: + virtual ~QWaylandTextInputInterface() {} + virtual void reset() = 0; + virtual void commit() = 0; + virtual void disableSurface(::wl_surface *surface) = 0; + virtual void enableSurface(::wl_surface *surface) = 0; + virtual void updateState(Qt::InputMethodQueries queries, uint32_t flags) = 0; + virtual void showInputPanel() {} + virtual void hideInputPanel() {} + virtual bool isInputPanelVisible() const = 0; + virtual QRectF keyboardRect() const = 0; + virtual QLocale locale() const = 0; + virtual Qt::LayoutDirection inputDirection() const = 0; + virtual void setCursorInsidePreedit(int cursor) = 0; + + // This enum should be compatible with update_state of text-input-unstable-v2. + // Higher versions of text-input-* protocol may not use it directly + // but QtWaylandClient can determine clients' states based on the values + enum TextInputState { + update_state_change = 0, // updated state because it changed + update_state_full = 1, // full state after enter or input_method_changed event + update_state_reset = 2, // full state after reset + update_state_enter = 3, // full state after switching focus to a different widget on client side + }; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDTEXTINPUTINTERFACE_P_H + diff --git a/src/plugins/platforms/wayland/qwaylandtextinputv1.cpp b/src/plugins/platforms/wayland/qwaylandtextinputv1.cpp new file mode 100644 index 00000000000..dc999624b76 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandtextinputv1.cpp @@ -0,0 +1,361 @@ +// Copyright (C) 2021 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 +#include + +#include "qwaylandtextinputv1_p.h" + +#include "qwaylandinputcontext_p.h" +#include "qwaylandwindow_p.h" +#include "qwaylandinputmethodeventbuilder_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +namespace { + +const Qt::InputMethodQueries supportedQueries1 = Qt::ImEnabled | + Qt::ImSurroundingText | + Qt::ImCursorPosition | + Qt::ImAnchorPosition | + Qt::ImHints | + Qt::ImCursorRectangle | + Qt::ImPreferredLanguage; +} + +QWaylandTextInputv1::QWaylandTextInputv1(QWaylandDisplay *display, struct ::zwp_text_input_v1 *text_input) + : QtWayland::zwp_text_input_v1(text_input) +{ + Q_UNUSED(display); +} + +QWaylandTextInputv1::~QWaylandTextInputv1() +{ + if (m_resetCallback) + wl_callback_destroy(m_resetCallback); + zwp_text_input_v1_destroy(object()); +} + +void QWaylandTextInputv1::reset() +{ + m_builder.reset(); + m_preeditCommit = QString(); + updateState(Qt::ImQueryAll, QWaylandTextInputInterface::update_state_reset); +} + +void QWaylandTextInputv1::commit() +{ + if (QObject *o = QGuiApplication::focusObject()) { + QInputMethodEvent event; + event.setCommitString(m_preeditCommit); + QCoreApplication::sendEvent(o, &event); + } + + reset(); +} + +const wl_callback_listener QWaylandTextInputv1::callbackListener = { + QWaylandTextInputv1::resetCallback +}; + +void QWaylandTextInputv1::resetCallback(void *data, wl_callback *, uint32_t) +{ + QWaylandTextInputv1 *self = static_cast(data); + + if (self->m_resetCallback) { + wl_callback_destroy(self->m_resetCallback); + self->m_resetCallback = nullptr; + } +} + +void QWaylandTextInputv1::updateState(Qt::InputMethodQueries queries, uint32_t flags) +{ + if (!QGuiApplication::focusObject()) + return; + + if (!QGuiApplication::focusWindow() || !QGuiApplication::focusWindow()->handle()) + return; + + auto *window = static_cast(QGuiApplication::focusWindow()->handle()); + auto *surface = window->wlSurface(); + if (!surface || (surface != m_surface)) + return; + + queries &= supportedQueries1; + + // Surrounding text, cursor and anchor positions are transferred together + if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) + queries |= Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition; + + QInputMethodQueryEvent event(queries); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + + if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) { + QString text = event.value(Qt::ImSurroundingText).toString(); + int cursor = event.value(Qt::ImCursorPosition).toInt(); + int anchor = event.value(Qt::ImAnchorPosition).toInt(); + + // Make sure text is not too big + if (text.toUtf8().size() > 2048) { + int c = qAbs(cursor - anchor) <= 512 ? qMin(cursor, anchor) + qAbs(cursor - anchor) / 2: cursor; + + const int offset = c - qBound(0, c, 512 - qMin(text.size() - c, 256)); + text = text.mid(offset + c - 256, 512); + cursor -= offset; + anchor -= offset; + } + + set_surrounding_text(text, QWaylandInputMethodEventBuilder::indexToWayland(text, cursor), QWaylandInputMethodEventBuilder::indexToWayland(text, anchor)); + } + + if (queries & Qt::ImHints) { + QWaylandInputMethodContentType contentType = QWaylandInputMethodContentType::convert(static_cast(event.value(Qt::ImHints).toInt())); + set_content_type(contentType.hint, contentType.purpose); + } + + if (queries & Qt::ImCursorRectangle) { + const QRect &cRect = event.value(Qt::ImCursorRectangle).toRect(); + const QRect &windowRect = QGuiApplication::inputMethod()->inputItemTransform().mapRect(cRect); + const QRect &nativeRect = QHighDpi::toNativePixels(windowRect, QGuiApplication::focusWindow()); + const QMargins margins = window->clientSideMargins(); + const QRect &surfaceRect = nativeRect.translated(margins.left(), margins.top()); + set_cursor_rectangle(surfaceRect.x(), surfaceRect.y(), surfaceRect.width(), surfaceRect.height()); + } + + if (queries & Qt::ImPreferredLanguage) { + const QString &language = event.value(Qt::ImPreferredLanguage).toString(); + set_preferred_language(language); + } + + if (flags == QWaylandTextInputInterface::update_state_reset) + QtWayland::zwp_text_input_v1::reset(); + else + commit_state(m_serial); +} + +void QWaylandTextInputv1::setCursorInsidePreedit(int) +{ + // Not supported yet +} + +bool QWaylandTextInputv1::isInputPanelVisible() const +{ + return m_inputPanelVisible; +} + +QRectF QWaylandTextInputv1::keyboardRect() const +{ + return m_keyboardRectangle; +} + +QLocale QWaylandTextInputv1::locale() const +{ + return m_locale; +} + +Qt::LayoutDirection QWaylandTextInputv1::inputDirection() const +{ + return m_inputDirection; +} + +void QWaylandTextInputv1::zwp_text_input_v1_enter(::wl_surface *surface) +{ + m_surface = surface; + + updateState(Qt::ImQueryAll, QWaylandTextInputInterface::update_state_reset); +} + +void QWaylandTextInputv1::zwp_text_input_v1_leave() +{ + m_surface = nullptr; +} + +void QWaylandTextInputv1::zwp_text_input_v1_modifiers_map(wl_array *map) +{ + const QList modifiersMap = QByteArray::fromRawData(static_cast(map->data), map->size).split('\0'); + + m_modifiersMap.clear(); + + for (const QByteArray &modifier : modifiersMap) { + if (modifier == "Shift") + m_modifiersMap.append(Qt::ShiftModifier); + else if (modifier == "Control") + m_modifiersMap.append(Qt::ControlModifier); + else if (modifier == "Alt") + m_modifiersMap.append(Qt::AltModifier); + else if (modifier == "Mod1") + m_modifiersMap.append(Qt::AltModifier); + else if (modifier == "Mod4") + m_modifiersMap.append(Qt::MetaModifier); + else + m_modifiersMap.append(Qt::NoModifier); + } +} + +void QWaylandTextInputv1::zwp_text_input_v1_input_panel_state(uint32_t visible) +{ + const bool inputPanelVisible = (visible == 1); + if (m_inputPanelVisible != inputPanelVisible) { + m_inputPanelVisible = inputPanelVisible; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputPanelVisibleChanged(); + } +} + +void QWaylandTextInputv1::zwp_text_input_v1_preedit_string(uint32_t serial, const QString &text, const QString &commit) +{ + m_serial = serial; + + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard preedit_string: reset not confirmed"; + m_builder.reset(); + return; + } + + if (!QGuiApplication::focusObject()) + return; + + QInputMethodEvent *event = m_builder.buildPreedit(text); + + m_builder.reset(); + m_preeditCommit = commit; + + QCoreApplication::sendEvent(QGuiApplication::focusObject(), event); + delete event; +} + +void QWaylandTextInputv1::zwp_text_input_v1_preedit_styling(uint32_t index, uint32_t length, uint32_t style) +{ + m_builder.addPreeditStyling(index, length, style); +} + +void QWaylandTextInputv1::zwp_text_input_v1_preedit_cursor(int32_t index) +{ + m_builder.setPreeditCursor(index); +} + +void QWaylandTextInputv1::zwp_text_input_v1_commit_string(uint32_t serial, const QString &text) +{ + m_serial = serial; + + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard commit_string: reset not confirmed"; + m_builder.reset(); + return; + } + + if (!QGuiApplication::focusObject()) + return; + + // When committing the text, the preeditString needs to be reset, to prevent it to be + // send again in the commit() function + m_preeditCommit.clear(); + + QInputMethodEvent *event = m_builder.buildCommit(text); + + m_builder.reset(); + + QCoreApplication::sendEvent(QGuiApplication::focusObject(), event); + delete event; +} + +void QWaylandTextInputv1::zwp_text_input_v1_cursor_position(int32_t index, int32_t anchor) +{ + m_builder.setCursorPosition(index, anchor); +} + +void QWaylandTextInputv1::zwp_text_input_v1_delete_surrounding_text(int32_t before_length, uint32_t after_length) +{ + //before_length is negative, but the builder expects it to be positive + m_builder.setDeleteSurroundingText(-before_length, after_length); +} + +void QWaylandTextInputv1::zwp_text_input_v1_keysym(uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) +{ + m_serial = serial; + +#if QT_CONFIG(xkbcommon) + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard keysym: reset not confirmed"; + return; + } + + if (!QGuiApplication::focusWindow()) + return; + + Qt::KeyboardModifiers qtModifiers = modifiersToQtModifiers(modifiers); + + QEvent::Type type = state == WL_KEYBOARD_KEY_STATE_PRESSED ? QEvent::KeyPress : QEvent::KeyRelease; + QString text = QXkbCommon::lookupStringNoKeysymTransformations(sym); + int qtkey = QXkbCommon::keysymToQtKey(sym, qtModifiers); + + QWindowSystemInterface::handleKeyEvent(QGuiApplication::focusWindow(), + time, type, qtkey, qtModifiers, text); +#else + Q_UNUSED(time); + Q_UNUSED(sym); + Q_UNUSED(state); + Q_UNUSED(modifiers); +#endif +} + +void QWaylandTextInputv1::zwp_text_input_v1_language(uint32_t serial, const QString &language) +{ + m_serial = serial; + + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard language: reset not confirmed"; + return; + } + + const QLocale locale(language); + if (m_locale != locale) { + m_locale = locale; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitLocaleChanged(); + } +} + +void QWaylandTextInputv1::zwp_text_input_v1_text_direction(uint32_t serial, uint32_t direction) +{ + m_serial = serial; + + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard text_direction: reset not confirmed"; + return; + } + + const Qt::LayoutDirection inputDirection = (direction == text_direction_auto) ? Qt::LayoutDirectionAuto : + (direction == text_direction_ltr) ? Qt::LeftToRight : + (direction == text_direction_rtl) ? Qt::RightToLeft : Qt::LayoutDirectionAuto; + if (m_inputDirection != inputDirection) { + m_inputDirection = inputDirection; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputDirectionChanged(m_inputDirection); + } +} + +Qt::KeyboardModifiers QWaylandTextInputv1::modifiersToQtModifiers(uint32_t modifiers) +{ + Qt::KeyboardModifiers ret = Qt::NoModifier; + for (int i = 0; i < m_modifiersMap.size(); ++i) { + if (modifiers & (1 << i)) { + ret |= m_modifiersMap[i]; + } + } + return ret; +} + +} + +QT_END_NAMESPACE + diff --git a/src/plugins/platforms/wayland/qwaylandtextinputv1_p.h b/src/plugins/platforms/wayland/qwaylandtextinputv1_p.h new file mode 100644 index 00000000000..dc591cdb1e1 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandtextinputv1_p.h @@ -0,0 +1,112 @@ +// Copyright (C) 2021 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 + + +#ifndef QWAYLANDTEXTINPUTV1_H +#define QWAYLANDTEXTINPUTV1_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 "qwaylandtextinputinterface_p.h" +#include +#include + +struct wl_callback; +struct wl_callback_listener; + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class QWaylandTextInputv1 : public QtWayland::zwp_text_input_v1, public QWaylandTextInputInterface +{ +public: + QWaylandTextInputv1(QWaylandDisplay *display, struct ::zwp_text_input_v1 *text_input); + ~QWaylandTextInputv1() override; + + void setSeat(struct ::wl_seat *seat) { m_seat = seat; } + + void reset() override; + void commit() override; + void updateState(Qt::InputMethodQueries queries, uint32_t flags) override; + + void setCursorInsidePreedit(int cursor) override; + + bool isInputPanelVisible() const override; + QRectF keyboardRect() const override; + + QLocale locale() const override; + Qt::LayoutDirection inputDirection() const override; + + void showInputPanel() override + { + show_input_panel(); + } + void hideInputPanel() override + { + hide_input_panel(); + } + void enableSurface(::wl_surface *surface) override + { + activate(m_seat, surface); + } + void disableSurface(::wl_surface *surface) override + { + Q_UNUSED(surface); + deactivate(m_seat); + } + +protected: + void zwp_text_input_v1_enter(struct ::wl_surface *surface) override; + void zwp_text_input_v1_leave() override; + void zwp_text_input_v1_modifiers_map(wl_array *map) override; + void zwp_text_input_v1_input_panel_state(uint32_t state) override; + void zwp_text_input_v1_preedit_string(uint32_t serial, const QString &text, const QString &commit) override; + void zwp_text_input_v1_preedit_styling(uint32_t index, uint32_t length, uint32_t style) override; + void zwp_text_input_v1_preedit_cursor(int32_t index) override; + void zwp_text_input_v1_commit_string(uint32_t serial, const QString &text) override; + void zwp_text_input_v1_cursor_position(int32_t index, int32_t anchor) override; + void zwp_text_input_v1_delete_surrounding_text(int32_t before_length, uint32_t after_length) override; + void zwp_text_input_v1_keysym(uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) override; + void zwp_text_input_v1_language(uint32_t serial, const QString &language) override; + void zwp_text_input_v1_text_direction(uint32_t serial, uint32_t direction) override; + +private: + Qt::KeyboardModifiers modifiersToQtModifiers(uint32_t modifiers); + + QWaylandInputMethodEventBuilder m_builder; + + QList m_modifiersMap; + + uint32_t m_serial = 0; + struct ::wl_surface *m_surface = nullptr; + struct ::wl_seat *m_seat = nullptr; + + QString m_preeditCommit; + + bool m_inputPanelVisible = false; + QRectF m_keyboardRectangle; + QLocale m_locale; + Qt::LayoutDirection m_inputDirection = Qt::LayoutDirectionAuto; + + struct ::wl_callback *m_resetCallback = nullptr; + static const wl_callback_listener callbackListener; + static void resetCallback(void *data, struct wl_callback *wl_callback, uint32_t time); +}; + +} + +QT_END_NAMESPACE +#endif // QWAYLANDTEXTINPUTV1_H + diff --git a/src/plugins/platforms/wayland/qwaylandtextinputv2.cpp b/src/plugins/platforms/wayland/qwaylandtextinputv2.cpp new file mode 100644 index 00000000000..eb327e4c765 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandtextinputv2.cpp @@ -0,0 +1,400 @@ +// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include + +#include "qwaylandtextinputv2_p.h" + +#include "qwaylandinputcontext_p.h" +#include "qwaylandwindow_p.h" +#include "qwaylandinputmethodeventbuilder_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +namespace { + +const Qt::InputMethodQueries supportedQueries2 = Qt::ImEnabled | + Qt::ImSurroundingText | + Qt::ImCursorPosition | + Qt::ImAnchorPosition | + Qt::ImHints | + Qt::ImCursorRectangle | + Qt::ImPreferredLanguage; +} + +QWaylandTextInputv2::QWaylandTextInputv2(QWaylandDisplay *display, struct ::zwp_text_input_v2 *text_input) + : QtWayland::zwp_text_input_v2(text_input) + , m_display(display) +{ +} + +QWaylandTextInputv2::~QWaylandTextInputv2() +{ + if (m_resetCallback) + wl_callback_destroy(m_resetCallback); + destroy(); +} + +void QWaylandTextInputv2::showInputPanel() +{ + qCDebug(qLcQpaInputMethods()) << Q_FUNC_INFO; + show_input_panel(); +} +void QWaylandTextInputv2::hideInputPanel() +{ + qCDebug(qLcQpaInputMethods()) << Q_FUNC_INFO; + hide_input_panel(); +} +void QWaylandTextInputv2::enableSurface(::wl_surface *surface) +{ + qCDebug(qLcQpaInputMethods()) << Q_FUNC_INFO << surface; + enable(surface); +} +void QWaylandTextInputv2::disableSurface(::wl_surface *surface) +{ + qCDebug(qLcQpaInputMethods()) << Q_FUNC_INFO << surface; + disable(surface); +} + +void QWaylandTextInputv2::reset() +{ + qCDebug(qLcQpaInputMethods()) << Q_FUNC_INFO; + m_builder.reset(); + m_preeditCommit = QString(); + updateState(Qt::ImQueryAll, QtWayland::zwp_text_input_v2::update_state_reset); +} + +void QWaylandTextInputv2::commit() +{ + qCDebug(qLcQpaInputMethods()) << Q_FUNC_INFO; + if (QObject *o = QGuiApplication::focusObject()) { + if (!m_preeditCommit.isEmpty()) { + + QInputMethodEvent event; + event.setCommitString(m_preeditCommit); + m_preeditCommit = QString(); + + QCoreApplication::sendEvent(o, &event); + } + } + + reset(); +} + +const wl_callback_listener QWaylandTextInputv2::callbackListener = { + QWaylandTextInputv2::resetCallback +}; + +void QWaylandTextInputv2::resetCallback(void *data, wl_callback *, uint32_t) +{ + QWaylandTextInputv2 *self = static_cast(data); + + if (self->m_resetCallback) { + wl_callback_destroy(self->m_resetCallback); + self->m_resetCallback = nullptr; + } +} + +void QWaylandTextInputv2::updateState(Qt::InputMethodQueries queries, uint32_t flags) +{ + qCDebug(qLcQpaInputMethods()) << Q_FUNC_INFO << queries << flags; + if (!QGuiApplication::focusObject()) + return; + + if (!QGuiApplication::focusWindow() || !QGuiApplication::focusWindow()->handle()) + return; + + auto *window = static_cast(QGuiApplication::focusWindow()->handle()); + auto *surface = window->wlSurface(); + if (!surface || (surface != m_surface)) + return; + + queries &= supportedQueries2; + + // Surrounding text, cursor and anchor positions are transferred together + if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) + queries |= Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition; + + QInputMethodQueryEvent event(queries); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + + if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) { + QString text = event.value(Qt::ImSurroundingText).toString(); + int cursor = event.value(Qt::ImCursorPosition).toInt(); + int anchor = event.value(Qt::ImAnchorPosition).toInt(); + + // Make sure text is not too big + if (text.toUtf8().size() > 2048) { + int c = qAbs(cursor - anchor) <= 512 ? qMin(cursor, anchor) + qAbs(cursor - anchor) / 2: cursor; + + const int offset = c - qBound(0, c, 512 - qMin(text.size() - c, 256)); + text = text.mid(offset + c - 256, 512); + cursor -= offset; + anchor -= offset; + } + + set_surrounding_text(text, QWaylandInputMethodEventBuilder::indexToWayland(text, cursor), QWaylandInputMethodEventBuilder::indexToWayland(text, anchor)); + } + + if (queries & Qt::ImHints) { + QWaylandInputMethodContentType contentType = QWaylandInputMethodContentType::convert(static_cast(event.value(Qt::ImHints).toInt())); + set_content_type(contentType.hint, contentType.purpose); + } + + if (queries & Qt::ImCursorRectangle) { + const QRect &cRect = event.value(Qt::ImCursorRectangle).toRect(); + const QRect &windowRect = QGuiApplication::inputMethod()->inputItemTransform().mapRect(cRect); + const QRect &nativeRect = QHighDpi::toNativePixels(windowRect, QGuiApplication::focusWindow()); + const QMargins margins = window->clientSideMargins(); + const QRect &surfaceRect = nativeRect.translated(margins.left(), margins.top()); + set_cursor_rectangle(surfaceRect.x(), surfaceRect.y(), surfaceRect.width(), surfaceRect.height()); + } + + if (queries & Qt::ImPreferredLanguage) { + const QString &language = event.value(Qt::ImPreferredLanguage).toString(); + set_preferred_language(language); + } + + update_state(m_serial, flags); + if (flags != QtWayland::zwp_text_input_v2::update_state_change) { + if (m_resetCallback) + wl_callback_destroy(m_resetCallback); + m_resetCallback = wl_display_sync(m_display->wl_display()); + wl_callback_add_listener(m_resetCallback, &QWaylandTextInputv2::callbackListener, this); + } +} + +void QWaylandTextInputv2::setCursorInsidePreedit(int) +{ + // Not supported yet +} + +bool QWaylandTextInputv2::isInputPanelVisible() const +{ + return m_inputPanelVisible; +} + +QRectF QWaylandTextInputv2::keyboardRect() const +{ + return m_keyboardRectangle; +} + +QLocale QWaylandTextInputv2::locale() const +{ + return m_locale; +} + +Qt::LayoutDirection QWaylandTextInputv2::inputDirection() const +{ + return m_inputDirection; +} + +void QWaylandTextInputv2::zwp_text_input_v2_enter(uint32_t serial, ::wl_surface *surface) +{ + qCDebug(qLcQpaInputMethods()) << Q_FUNC_INFO << serial << surface; + m_serial = serial; + m_surface = surface; + + updateState(Qt::ImQueryAll, QtWayland::zwp_text_input_v2::update_state_enter); +} + +void QWaylandTextInputv2::zwp_text_input_v2_leave(uint32_t serial, ::wl_surface *surface) +{ + qCDebug(qLcQpaInputMethods()) << Q_FUNC_INFO << serial << surface; + m_serial = serial; + + if (m_surface != surface) { + qCDebug(qLcQpaInputMethods()) << Q_FUNC_INFO << "Got leave event for surface" << surface << "focused surface" << m_surface; + } + + m_surface = nullptr; +} + +void QWaylandTextInputv2::zwp_text_input_v2_modifiers_map(wl_array *map) +{ + const QList modifiersMap = QByteArray::fromRawData(static_cast(map->data), map->size).split('\0'); + + m_modifiersMap.clear(); + + for (const QByteArray &modifier : modifiersMap) { + if (modifier == "Shift") + m_modifiersMap.append(Qt::ShiftModifier); + else if (modifier == "Control") + m_modifiersMap.append(Qt::ControlModifier); + else if (modifier == "Alt") + m_modifiersMap.append(Qt::AltModifier); + else if (modifier == "Mod1") + m_modifiersMap.append(Qt::AltModifier); + else if (modifier == "Mod4") + m_modifiersMap.append(Qt::MetaModifier); + else + m_modifiersMap.append(Qt::NoModifier); + } +} + +void QWaylandTextInputv2::zwp_text_input_v2_input_panel_state(uint32_t visible, int32_t x, int32_t y, int32_t width, int32_t height) +{ + const bool inputPanelVisible = (visible == input_panel_visibility_visible); + if (m_inputPanelVisible != inputPanelVisible) { + m_inputPanelVisible = inputPanelVisible; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputPanelVisibleChanged(); + } + const QRectF keyboardRectangle(x, y, width, height); + if (m_keyboardRectangle != keyboardRectangle) { + m_keyboardRectangle = keyboardRectangle; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitKeyboardRectChanged(); + } +} + +void QWaylandTextInputv2::zwp_text_input_v2_preedit_string(const QString &text, const QString &commit) +{ + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard preedit_string: reset not confirmed"; + m_builder.reset(); + return; + } + + if (!QGuiApplication::focusObject()) + return; + + QInputMethodEvent *event = m_builder.buildPreedit(text); + + m_builder.reset(); + m_preeditCommit = commit; + + QCoreApplication::sendEvent(QGuiApplication::focusObject(), event); + delete event; +} + +void QWaylandTextInputv2::zwp_text_input_v2_preedit_styling(uint32_t index, uint32_t length, uint32_t style) +{ + m_builder.addPreeditStyling(index, length, style); +} + +void QWaylandTextInputv2::zwp_text_input_v2_preedit_cursor(int32_t index) +{ + m_builder.setPreeditCursor(index); +} + +void QWaylandTextInputv2::zwp_text_input_v2_commit_string(const QString &text) +{ + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard commit_string: reset not confirmed"; + m_builder.reset(); + return; + } + + if (!QGuiApplication::focusObject()) + return; + + QInputMethodEvent *event = m_builder.buildCommit(text); + + m_builder.reset(); + + QCoreApplication::sendEvent(QGuiApplication::focusObject(), event); + delete event; +} + +void QWaylandTextInputv2::zwp_text_input_v2_cursor_position(int32_t index, int32_t anchor) +{ + m_builder.setCursorPosition(index, anchor); +} + +void QWaylandTextInputv2::zwp_text_input_v2_delete_surrounding_text(uint32_t before_length, uint32_t after_length) +{ + m_builder.setDeleteSurroundingText(before_length, after_length); +} + +void QWaylandTextInputv2::zwp_text_input_v2_keysym(uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) +{ +#if QT_CONFIG(xkbcommon) + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard keysym: reset not confirmed"; + return; + } + + if (!QGuiApplication::focusWindow()) + return; + + Qt::KeyboardModifiers qtModifiers = modifiersToQtModifiers(modifiers); + + QEvent::Type type = state == WL_KEYBOARD_KEY_STATE_PRESSED ? QEvent::KeyPress : QEvent::KeyRelease; + QString text = QXkbCommon::lookupStringNoKeysymTransformations(sym); + int qtkey = QXkbCommon::keysymToQtKey(sym, qtModifiers); + + QWindowSystemInterface::handleKeyEvent(QGuiApplication::focusWindow(), + time, type, qtkey, qtModifiers, text); +#else + Q_UNUSED(time); + Q_UNUSED(sym); + Q_UNUSED(state); + Q_UNUSED(modifiers); +#endif +} + +void QWaylandTextInputv2::zwp_text_input_v2_language(const QString &language) +{ + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard language: reset not confirmed"; + return; + } + + const QLocale locale(language); + if (m_locale != locale) { + m_locale = locale; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitLocaleChanged(); + } +} + +void QWaylandTextInputv2::zwp_text_input_v2_text_direction(uint32_t direction) +{ + if (m_resetCallback) { + qCDebug(qLcQpaInputMethods()) << "discard text_direction: reset not confirmed"; + return; + } + + const Qt::LayoutDirection inputDirection = (direction == text_direction_auto) ? Qt::LayoutDirectionAuto : + (direction == text_direction_ltr) ? Qt::LeftToRight : + (direction == text_direction_rtl) ? Qt::RightToLeft : Qt::LayoutDirectionAuto; + if (m_inputDirection != inputDirection) { + m_inputDirection = inputDirection; + QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputDirectionChanged(m_inputDirection); + } +} + +void QWaylandTextInputv2::zwp_text_input_v2_input_method_changed(uint32_t serial, uint32_t flags) +{ + Q_UNUSED(flags); + + m_serial = serial; + updateState(Qt::ImQueryAll, QtWayland::zwp_text_input_v2::update_state_full); +} + +Qt::KeyboardModifiers QWaylandTextInputv2::modifiersToQtModifiers(uint32_t modifiers) +{ + Qt::KeyboardModifiers ret = Qt::NoModifier; + for (int i = 0; i < m_modifiersMap.size(); ++i) { + if (modifiers & (1 << i)) { + ret |= m_modifiersMap[i]; + } + } + return ret; +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandtextinputv2_p.h b/src/plugins/platforms/wayland/qwaylandtextinputv2_p.h new file mode 100644 index 00000000000..0258fbe8840 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandtextinputv2_p.h @@ -0,0 +1,98 @@ +// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + +#ifndef QWAYLANDINPUTV2_P_H +#define QWAYLANDINPUTV2_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 "qwaylandtextinputinterface_p.h" +#include +#include + +struct wl_callback; +struct wl_callback_listener; + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class QWaylandTextInputv2 : public QtWayland::zwp_text_input_v2, public QWaylandTextInputInterface +{ +public: + QWaylandTextInputv2(QWaylandDisplay *display, struct ::zwp_text_input_v2 *text_input); + ~QWaylandTextInputv2() override; + + void reset() override; + void commit() override; + void updateState(Qt::InputMethodQueries queries, uint32_t flags) override; + + void setCursorInsidePreedit(int cursor) override; + + bool isInputPanelVisible() const override; + QRectF keyboardRect() const override; + + QLocale locale() const override; + Qt::LayoutDirection inputDirection() const override; + + void showInputPanel() override; + void hideInputPanel() override; + void enableSurface(::wl_surface *surface) override; + void disableSurface(::wl_surface *surface) override; + +protected: + void zwp_text_input_v2_enter(uint32_t serial, struct ::wl_surface *surface) override; + void zwp_text_input_v2_leave(uint32_t serial, struct ::wl_surface *surface) override; + void zwp_text_input_v2_modifiers_map(wl_array *map) override; + void zwp_text_input_v2_input_panel_state(uint32_t state, int32_t x, int32_t y, int32_t width, int32_t height) override; + void zwp_text_input_v2_preedit_string(const QString &text, const QString &commit) override; + void zwp_text_input_v2_preedit_styling(uint32_t index, uint32_t length, uint32_t style) override; + void zwp_text_input_v2_preedit_cursor(int32_t index) override; + void zwp_text_input_v2_commit_string(const QString &text) override; + void zwp_text_input_v2_cursor_position(int32_t index, int32_t anchor) override; + void zwp_text_input_v2_delete_surrounding_text(uint32_t before_length, uint32_t after_length) override; + void zwp_text_input_v2_keysym(uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) override; + void zwp_text_input_v2_language(const QString &language) override; + void zwp_text_input_v2_text_direction(uint32_t direction) override; + void zwp_text_input_v2_input_method_changed(uint32_t serial, uint32_t flags) override; + +private: + Qt::KeyboardModifiers modifiersToQtModifiers(uint32_t modifiers); + + QWaylandDisplay *m_display = nullptr; + QWaylandInputMethodEventBuilder m_builder; + + QList m_modifiersMap; + + uint32_t m_serial = 0; + struct ::wl_surface *m_surface = nullptr; + + QString m_preeditCommit; + + bool m_inputPanelVisible = false; + QRectF m_keyboardRectangle; + QLocale m_locale; + Qt::LayoutDirection m_inputDirection = Qt::LayoutDirectionAuto; + + struct ::wl_callback *m_resetCallback = nullptr; + static const wl_callback_listener callbackListener; + static void resetCallback(void *data, struct wl_callback *wl_callback, uint32_t time); +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDTEXTINPUTV2_P_H diff --git a/src/plugins/platforms/wayland/qwaylandtextinputv3.cpp b/src/plugins/platforms/wayland/qwaylandtextinputv3.cpp new file mode 100644 index 00000000000..a9538370781 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandtextinputv3.cpp @@ -0,0 +1,420 @@ +// Copyright (C) 2021 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 + +#include "qwaylandtextinputv3_p.h" + +#include "qwaylandwindow_p.h" +#include "qwaylandinputmethodeventbuilder_p.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcQpaWaylandTextInput, "qt.qpa.wayland.textinput") + +namespace QtWaylandClient { + +QWaylandTextInputv3::QWaylandTextInputv3(QWaylandDisplay *display, + struct ::zwp_text_input_v3 *text_input) + : QtWayland::zwp_text_input_v3(text_input) +{ + Q_UNUSED(display) +} + +QWaylandTextInputv3::~QWaylandTextInputv3() +{ + destroy(); +} + +namespace { +const Qt::InputMethodQueries supportedQueries3 = Qt::ImEnabled | + Qt::ImSurroundingText | + Qt::ImCursorPosition | + Qt::ImAnchorPosition | + Qt::ImHints | + Qt::ImCursorRectangle; +} + +void QWaylandTextInputv3::enableSurface(::wl_surface *surface) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << surface; + + if (m_surface == surface) + return; // already enabled + if (m_surface) + qCWarning(qLcQpaWaylandTextInput()) << Q_FUNC_INFO << "Try to enable surface" << surface << "with focusing surface" << m_surface; + + m_surface = surface; + m_pendingPreeditString.clear(); + m_pendingCommitString.clear(); + m_pendingDeleteBeforeText = 0; + m_pendingDeleteAfterText = 0; + + enable(); + updateState(supportedQueries3, update_state_enter); +} + +void QWaylandTextInputv3::disableSurface(::wl_surface *surface) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << surface; + + if (!m_surface) + return; // already disabled + if (m_surface != surface) + qCWarning(qLcQpaWaylandTextInput()) << Q_FUNC_INFO << "Try to disable surface" << surface << "with focusing surface" << m_surface; + + m_currentPreeditString.clear(); + m_surface = nullptr; + disable(); + commit(); +} + +void QWaylandTextInputv3::zwp_text_input_v3_enter(struct ::wl_surface *surface) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << m_surface << surface; + + if (m_surface) + qCWarning(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "Got enter event without leaving a surface " << m_surface; + + enableSurface(surface); +} + +void QWaylandTextInputv3::zwp_text_input_v3_leave(struct ::wl_surface *surface) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + + if (!m_surface) + return; // Nothing to leave + + if (m_surface != surface) + qCWarning(qLcQpaWaylandTextInput()) << Q_FUNC_INFO << "Got leave event for surface" << surface << "with focusing surface" << m_surface; + + disableSurface(surface); +} + +void QWaylandTextInputv3::zwp_text_input_v3_preedit_string(const QString &text, int32_t cursorBegin, int32_t cursorEnd) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << text << cursorBegin << cursorEnd; + if (!m_surface) { + qCWarning(qLcQpaWaylandTextInput) << "Got preedit_string event without entering a surface"; + return; + } + + if (!QGuiApplication::focusObject()) + return; + + m_pendingPreeditString.text = text; + m_pendingPreeditString.cursorBegin = QWaylandInputMethodEventBuilder::indexFromWayland(text, cursorBegin); + m_pendingPreeditString.cursorEnd = QWaylandInputMethodEventBuilder::indexFromWayland(text, cursorEnd); +} + +void QWaylandTextInputv3::zwp_text_input_v3_commit_string(const QString &text) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << text; + if (!m_surface) { + qCWarning(qLcQpaWaylandTextInput) << "Got commit_string event without entering a surface"; + return; + } + + if (!QGuiApplication::focusObject()) + return; + + m_pendingCommitString = text; +} + +void QWaylandTextInputv3::zwp_text_input_v3_delete_surrounding_text(uint32_t beforeText, uint32_t afterText) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << beforeText << afterText; + if (!m_surface) { + qCWarning(qLcQpaWaylandTextInput) << "Got delete_surrounding_text event without entering a surface"; + return; + } + + if (!QGuiApplication::focusObject()) + return; + + m_pendingDeleteBeforeText = beforeText; + m_pendingDeleteAfterText = afterText; +} + +void QWaylandTextInputv3::zwp_text_input_v3_done(uint32_t serial) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "with serial" << serial << m_currentSerial; + + if (!m_surface) + return; + + // This is a case of double click. + // text_input_v3 will ignore this done signal and just keep the selection of the clicked word. + if (m_cursorPos != m_anchorPos && (m_pendingDeleteBeforeText != 0 || m_pendingDeleteAfterText != 0)) { + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "Ignore done"; + m_pendingDeleteBeforeText = 0; + m_pendingDeleteAfterText = 0; + m_pendingPreeditString.clear(); + m_pendingCommitString.clear(); + return; + } + + QObject *focusObject = QGuiApplication::focusObject(); + if (!focusObject) + return; + + if (!m_surface) { + qCWarning(qLcQpaWaylandTextInput) << Q_FUNC_INFO << serial << "Surface is not enabled yet"; + return; + } + + if ((m_pendingPreeditString == m_currentPreeditString) + && (m_pendingCommitString.isEmpty() && m_pendingDeleteBeforeText == 0 + && m_pendingDeleteAfterText == 0)) { + // Current done doesn't need additional updates + m_pendingPreeditString.clear(); + return; + } + + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "PREEDIT" << m_pendingPreeditString.text << m_pendingPreeditString.cursorBegin; + + QList attributes; + { + if (m_pendingPreeditString.cursorBegin != -1 || + m_pendingPreeditString.cursorEnd != -1) { + // Current supported cursor shape is just line. + // It means, cursorEnd and cursorBegin are the same. + QInputMethodEvent::Attribute attribute1(QInputMethodEvent::Cursor, + m_pendingPreeditString.cursorBegin, + 1); + attributes.append(attribute1); + } + + // only use single underline style for now + QTextCharFormat format; + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + QInputMethodEvent::Attribute attribute2(QInputMethodEvent::TextFormat, + 0, + m_pendingPreeditString.text.length(), format); + attributes.append(attribute2); + } + QInputMethodEvent event(m_pendingPreeditString.text, attributes); + + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "DELETE" << m_pendingDeleteBeforeText << m_pendingDeleteAfterText; + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "COMMIT" << m_pendingCommitString; + + int replaceFrom = 0; + int replaceLength = 0; + if (m_pendingDeleteBeforeText != 0 || m_pendingDeleteAfterText != 0) { + // A workaround for reselection + // It will disable redundant commit after reselection + m_condReselection = true; + const QByteArray &utf8 = QStringView{m_surroundingText}.toUtf8(); + if (m_cursorPos < int(m_pendingDeleteBeforeText)) { + replaceFrom = -QString::fromUtf8(QByteArrayView{utf8}.first(m_pendingDeleteBeforeText)).size(); + replaceLength = QString::fromUtf8(QByteArrayView{utf8}.first(m_pendingDeleteBeforeText + m_pendingDeleteAfterText)).size(); + } else { + replaceFrom = -QString::fromUtf8(QByteArrayView{utf8}.sliced(m_cursorPos - m_pendingDeleteBeforeText, m_pendingDeleteBeforeText)).size(); + replaceLength = QString::fromUtf8(QByteArrayView{utf8}.sliced(m_cursorPos - m_pendingDeleteBeforeText, m_pendingDeleteBeforeText + m_pendingDeleteAfterText)).size(); + } + } + + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "DELETE from " << replaceFrom << " length " << replaceLength; + event.setCommitString(m_pendingCommitString, + replaceFrom, + replaceLength); + m_currentPreeditString = m_pendingPreeditString; + m_pendingPreeditString.clear(); + m_pendingCommitString.clear(); + m_pendingDeleteBeforeText = 0; + m_pendingDeleteAfterText = 0; + QCoreApplication::sendEvent(focusObject, &event); + + if (serial == m_currentSerial) + updateState(supportedQueries3, update_state_full); +} + +void QWaylandTextInputv3::reset() +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + + m_pendingPreeditString.clear(); +} + +void QWaylandTextInputv3::commit() +{ + m_currentSerial = (m_currentSerial < UINT_MAX) ? m_currentSerial + 1U: 0U; + + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "with serial" << m_currentSerial; + QtWayland::zwp_text_input_v3::commit(); +} + +void QWaylandTextInputv3::updateState(Qt::InputMethodQueries queries, uint32_t flags) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << queries << flags; + + if (!QGuiApplication::focusObject()) + return; + + if (!QGuiApplication::focusWindow() || !QGuiApplication::focusWindow()->handle()) + return; + + auto *window = static_cast(QGuiApplication::focusWindow()->handle()); + auto *surface = window->wlSurface(); + if (!surface || (surface != m_surface)) + return; + + queries &= supportedQueries3; + bool needsCommit = false; + + QInputMethodQueryEvent event(queries); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + + // For some reason, a query for Qt::ImSurroundingText gives an empty string even though it is not. + if (!(queries & Qt::ImSurroundingText) && event.value(Qt::ImSurroundingText).toString().isEmpty()) { + return; + } + + if (queries & Qt::ImCursorRectangle) { + const QRect &cRect = event.value(Qt::ImCursorRectangle).toRect(); + const QRect &windowRect = QGuiApplication::inputMethod()->inputItemTransform().mapRect(cRect); + const QRect &nativeRect = QHighDpi::toNativePixels(windowRect, QGuiApplication::focusWindow()); + const QMargins margins = window->clientSideMargins(); + const QRect &surfaceRect = nativeRect.translated(margins.left(), margins.top()); + if (surfaceRect != m_cursorRect) { + set_cursor_rectangle(surfaceRect.x(), surfaceRect.y(), surfaceRect.width(), surfaceRect.height()); + m_cursorRect = surfaceRect; + needsCommit = true; + } + } + + if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) { + QString text = event.value(Qt::ImSurroundingText).toString(); + int cursor = event.value(Qt::ImCursorPosition).toInt(); + int anchor = event.value(Qt::ImAnchorPosition).toInt(); + + qCDebug(qLcQpaWaylandTextInput) << "Original surrounding_text from InputMethodQuery: " << text << cursor << anchor; + + // Make sure text is not too big + // surround_text cannot exceed 4000byte in wayland protocol + // The worst case will be supposed here. + const int MAX_MESSAGE_SIZE = 4000; + + const int textSize = text.toUtf8().size(); + if (textSize > MAX_MESSAGE_SIZE) { + qCDebug(qLcQpaWaylandTextInput) << "SurroundText size is over " + << MAX_MESSAGE_SIZE + << " byte, some text will be clipped."; + const int selectionStart = qMin(cursor, anchor); + const int selectionEnd = qMax(cursor, anchor); + const int selectionLength = selectionEnd - selectionStart; + const int selectionSize = QStringView{text}.sliced(selectionStart, selectionLength).toUtf8().size(); + // If selection is bigger than 4000 byte, it is fixed to 4000 byte. + // anchor will be moved in the 4000 byte boundary. + if (selectionSize > MAX_MESSAGE_SIZE) { + if (anchor > cursor) { + cursor = 0; + anchor = MAX_MESSAGE_SIZE; + text = text.sliced(selectionStart, selectionLength); + } else { + anchor = 0; + cursor = MAX_MESSAGE_SIZE; + text = text.sliced(selectionEnd - selectionLength, selectionLength); + } + } else { + // This is not optimal in some cases. + // For examples, if the cursor position and + // the selectionEnd are close to the end of the surround text, + // the tail of the text might always be clipped. + // However all the cases of over 4000 byte are just exceptions. + int selEndSize = QStringView{text}.first(selectionEnd).toUtf8().size(); + cursor = QWaylandInputMethodEventBuilder::indexToWayland(text, cursor); + anchor = QWaylandInputMethodEventBuilder::indexToWayland(text, anchor); + if (selEndSize < MAX_MESSAGE_SIZE) { + text = QString::fromUtf8(QByteArrayView{text.toUtf8()}.first(MAX_MESSAGE_SIZE)); + } else { + const int startOffset = selEndSize - MAX_MESSAGE_SIZE; + text = QString::fromUtf8(QByteArrayView{text.toUtf8()}.sliced(startOffset, MAX_MESSAGE_SIZE)); + cursor -= startOffset; + anchor -= startOffset; + } + } + } else { + cursor = QWaylandInputMethodEventBuilder::indexToWayland(text, cursor); + anchor = QWaylandInputMethodEventBuilder::indexToWayland(text, anchor); + } + qCDebug(qLcQpaWaylandTextInput) << "Modified surrounding_text: " << text << cursor << anchor; + + if (m_surroundingText != text || m_cursorPos != cursor || m_anchorPos != anchor) { + qCDebug(qLcQpaWaylandTextInput) << "Current surrounding_text: " << m_surroundingText << m_cursorPos << m_anchorPos; + qCDebug(qLcQpaWaylandTextInput) << "New surrounding_text: " << text << cursor << anchor; + + set_surrounding_text(text, cursor, anchor); + + // A workaround in the case of reselection + // It will work when re-clicking a preedit text + if (m_condReselection) { + qCDebug(qLcQpaWaylandTextInput) << "\"commit\" is disabled when Reselection by changing focus"; + m_condReselection = false; + needsCommit = false; + + } + + m_surroundingText = text; + m_cursorPos = cursor; + m_anchorPos = anchor; + m_cursor = cursor; + } + } + + if (queries & Qt::ImHints) { + QWaylandInputMethodContentType contentType = QWaylandInputMethodContentType::convertV3(static_cast(event.value(Qt::ImHints).toInt())); + qCDebug(qLcQpaWaylandTextInput) << m_contentHint << contentType.hint; + qCDebug(qLcQpaWaylandTextInput) << m_contentPurpose << contentType.purpose; + + if (m_contentHint != contentType.hint || m_contentPurpose != contentType.purpose) { + qCDebug(qLcQpaWaylandTextInput) << "set_content_type: " << contentType.hint << contentType.purpose; + set_content_type(contentType.hint, contentType.purpose); + + m_contentHint = contentType.hint; + m_contentPurpose = contentType.purpose; + needsCommit = true; + } + } + + if (flags == update_state_enter + || (flags == update_state_change && needsCommit)) + commit(); +} + +void QWaylandTextInputv3::setCursorInsidePreedit(int cursor) +{ + Q_UNUSED(cursor); +} + +bool QWaylandTextInputv3::isInputPanelVisible() const +{ + return false; +} + +QRectF QWaylandTextInputv3::keyboardRect() const +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + return m_cursorRect; +} + +QLocale QWaylandTextInputv3::locale() const +{ + return QLocale(); +} + +Qt::LayoutDirection QWaylandTextInputv3::inputDirection() const +{ + return Qt::LeftToRight; +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandtextinputv3_p.h b/src/plugins/platforms/wayland/qwaylandtextinputv3_p.h new file mode 100644 index 00000000000..9a0d7e8a5f4 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandtextinputv3_p.h @@ -0,0 +1,105 @@ +// Copyright (C) 2021 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 + +#ifndef QWAYLANDTEXTINPUTV3_P_H +#define QWAYLANDTEXTINPUTV3_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 "qwaylandtextinputinterface_p.h" +#include +#include + +struct wl_callback; +struct wl_callback_listener; + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcQpaWaylandTextInput) + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class QWaylandTextInputv3 : public QtWayland::zwp_text_input_v3, public QWaylandTextInputInterface +{ +public: + QWaylandTextInputv3(QWaylandDisplay *display, struct ::zwp_text_input_v3 *text_input); + ~QWaylandTextInputv3() override; + + void reset() override; + void commit() override; + void updateState(Qt::InputMethodQueries queries, uint32_t flags) override; + // TODO: not supported yet + void setCursorInsidePreedit(int cursor) override; + + bool isInputPanelVisible() const override; + QRectF keyboardRect() const override; + + QLocale locale() const override; + Qt::LayoutDirection inputDirection() const override; + + void enableSurface(::wl_surface *) override; + void disableSurface(::wl_surface *) override; + +protected: + void zwp_text_input_v3_enter(struct ::wl_surface *surface) override; + void zwp_text_input_v3_leave(struct ::wl_surface *surface) override; + void zwp_text_input_v3_preedit_string(const QString &text, int32_t cursor_begin, int32_t cursor_end) override; + void zwp_text_input_v3_commit_string(const QString &text) override; + void zwp_text_input_v3_delete_surrounding_text(uint32_t before_length, uint32_t after_length) override; + void zwp_text_input_v3_done(uint32_t serial) override; + +private: + ::wl_surface *m_surface = nullptr; // ### Here for debugging purposes + + struct PreeditInfo { + QString text; + int cursorBegin = 0; + int cursorEnd = 0; + + void clear() { + text.clear(); + cursorBegin = 0; + cursorEnd = 0; + } + friend bool operator==(const PreeditInfo& lhs, const PreeditInfo& rhs) { + return (lhs.text == rhs.text) + && (lhs.cursorBegin == rhs.cursorBegin) + && (lhs.cursorEnd == rhs.cursorEnd); + } + }; + + PreeditInfo m_pendingPreeditString; + PreeditInfo m_currentPreeditString; + QString m_pendingCommitString; + uint m_pendingDeleteBeforeText = 0; // byte length + uint m_pendingDeleteAfterText = 0; // byte length + + QString m_surroundingText; + int m_cursor; // cursor position in QString + int m_cursorPos; // cursor position in wayland index + int m_anchorPos; // anchor position in wayland index + uint32_t m_contentHint = 0; + uint32_t m_contentPurpose = 0; + QRect m_cursorRect; + + uint m_currentSerial = 0; + + bool m_condReselection = false; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDTEXTINPUTV3_P_H diff --git a/src/plugins/platforms/wayland/qwaylandviewport.cpp b/src/plugins/platforms/wayland/qwaylandviewport.cpp new file mode 100644 index 00000000000..3252718c054 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandviewport.cpp @@ -0,0 +1,35 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandviewport_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandViewport::QWaylandViewport(::wp_viewport *viewport) + : QtWayland::wp_viewport(viewport) +{ +} + +QWaylandViewport::~QWaylandViewport() +{ + destroy(); +} + +void QWaylandViewport::setSource(const QRectF &source) +{ + set_source(wl_fixed_from_double(source.x()), + wl_fixed_from_double(source.y()), + wl_fixed_from_double(source.width()), + wl_fixed_from_double(source.height())); +} + +void QWaylandViewport::setDestination(const QSize &destination) +{ + set_destination(destination.width(), destination.height()); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandviewport_p.h b/src/plugins/platforms/wayland/qwaylandviewport_p.h new file mode 100644 index 00000000000..e1dfeb3a73e --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandviewport_p.h @@ -0,0 +1,42 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDVIEWPORT_P_H +#define QWAYLANDVIEWPORT_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandViewport : public QtWayland::wp_viewport +{ +public: + explicit QWaylandViewport(::wp_viewport *viewport); + ~QWaylandViewport() override; + + void setSource(const QRectF &source); + void setDestination(const QSize &destination); + +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDVIEWPORT_P_H diff --git a/src/plugins/platforms/wayland/qwaylandvulkaninstance.cpp b/src/plugins/platforms/wayland/qwaylandvulkaninstance.cpp new file mode 100644 index 00000000000..89f97f28f2c --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandvulkaninstance.cpp @@ -0,0 +1,126 @@ +// 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 + +#include "qwaylandvulkaninstance_p.h" +#include "qwaylandwindow_p.h" +#include "qwaylandscreen_p.h" +#include "qwaylanddisplay_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandVulkanInstance::QWaylandVulkanInstance(QVulkanInstance *instance) + : m_instance(instance) +{ + loadVulkanLibrary(QStringLiteral("vulkan"), 1); +} + +QWaylandVulkanInstance::~QWaylandVulkanInstance() = default; + +void QWaylandVulkanInstance::createOrAdoptInstance() +{ + QByteArrayList extraExtensions; + extraExtensions << QByteArrayLiteral("VK_KHR_wayland_surface"); + initInstance(m_instance, extraExtensions); + + if (!m_vkInst) + return; + + m_getPhysDevPresSupport = reinterpret_cast( + m_vkGetInstanceProcAddr(m_vkInst, "vkGetPhysicalDeviceWaylandPresentationSupportKHR")); + if (!m_getPhysDevPresSupport) + qWarning() << "Failed to find vkGetPhysicalDeviceWaylandPresentationSupportKHR"; +} + +bool QWaylandVulkanInstance::supportsPresent(VkPhysicalDevice physicalDevice, + uint32_t queueFamilyIndex, + QWindow *window) +{ + if (!m_getPhysDevPresSupport || !m_getPhysDevSurfaceSupport) + return true; + + auto *w = static_cast(window->handle()); + if (!w) { + qWarning() << "Attempted to call supportsPresent() without a valid platform window"; + return false; + } + wl_display *display = w->display()->wl_display(); + bool ok = m_getPhysDevPresSupport(physicalDevice, queueFamilyIndex, display); + + VkSurfaceKHR surface = QVulkanInstance::surfaceForWindow(window); + VkBool32 supported = false; + m_getPhysDevSurfaceSupport(physicalDevice, queueFamilyIndex, surface, &supported); + ok &= bool(supported); + + return ok; +} + +VkSurfaceKHR QWaylandVulkanInstance::createSurface(QWaylandWindow *window) +{ + VkSurfaceKHR surface = VK_NULL_HANDLE; + + if (!m_createSurface) { + m_createSurface = reinterpret_cast( + m_vkGetInstanceProcAddr(m_vkInst, "vkCreateWaylandSurfaceKHR")); + } + if (!m_createSurface) { + qWarning() << "Failed to find vkCreateWaylandSurfaceKHR"; + return surface; + } + + VkWaylandSurfaceCreateInfoKHR surfaceInfo; + memset(&surfaceInfo, 0, sizeof(surfaceInfo)); + surfaceInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; + surfaceInfo.display = window->display()->wl_display(); + surfaceInfo.surface = window->wlSurface(); + VkResult err = m_createSurface(m_vkInst, &surfaceInfo, nullptr, &surface); + if (err != VK_SUCCESS) + qWarning("Failed to create Vulkan surface: %d", err); + + return surface; +} + +void QWaylandVulkanInstance::presentAboutToBeQueued(QWindow *window) +{ + auto *w = static_cast(window->handle()); + if (!w) { + qWarning() << "Attempted to call presentAboutToBeQueued() without a valid platform window"; + return; + } + + bool ok; + int frameCallbackTimeout = qEnvironmentVariableIntValue("QT_WAYLAND_FRAME_CALLBACK_TIMEOUT", &ok); + + if (ok) + mFrameCallbackTimeout = frameCallbackTimeout; + + if (w->format().swapInterval() > 0) + w->waitForFrameSync(mFrameCallbackTimeout); + + w->handleUpdate(); +} + +void QWaylandVulkanInstance::beginFrame(QWindow *window) +{ + auto *w = static_cast(window->handle()); + if (!w) { + qWarning() << "Attempted to call beginFrame() without a valid platform window"; + return; + } + w->beginFrame(); +} + +void QWaylandVulkanInstance::endFrame(QWindow *window) +{ + auto *w = static_cast(window->handle()); + if (!w) { + qWarning() << "Attempted to call endFrame() without a valid platform window"; + return; + } + w->endFrame(); +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandvulkaninstance_p.h b/src/plugins/platforms/wayland/qwaylandvulkaninstance_p.h new file mode 100644 index 00000000000..7683ce5664e --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandvulkaninstance_p.h @@ -0,0 +1,59 @@ +// 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 + +#ifndef QWAYLANDVULKANINSTANCE_P_H +#define QWAYLANDVULKANINSTANCE_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. +// + +#if defined(VULKAN_H_) && !defined(VK_USE_PLATFORM_WAYLAND_KHR) +#error "vulkan.h included without Wayland WSI" +#endif + +#define VK_USE_PLATFORM_WAYLAND_KHR + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandWindow; + +class QWaylandVulkanInstance : public QBasicPlatformVulkanInstance +{ +public: + explicit QWaylandVulkanInstance(QVulkanInstance *instance); + ~QWaylandVulkanInstance() override; + + void createOrAdoptInstance() override; + bool supportsPresent(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, QWindow *window) override; + void presentAboutToBeQueued(QWindow *window) override; + + VkSurfaceKHR createSurface(QWaylandWindow *window); + + void beginFrame(QWindow *window) override; + void endFrame(QWindow *window) override; + +private: + QVulkanInstance *m_instance = nullptr; + PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR m_getPhysDevPresSupport = nullptr; + PFN_vkCreateWaylandSurfaceKHR m_createSurface = nullptr; + int mFrameCallbackTimeout = 100; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDVULKANINSTANCE_P_H diff --git a/src/plugins/platforms/wayland/qwaylandvulkanwindow.cpp b/src/plugins/platforms/wayland/qwaylandvulkanwindow.cpp new file mode 100644 index 00000000000..2bc52829d60 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandvulkanwindow.cpp @@ -0,0 +1,55 @@ +// 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 + +#include "qwaylandvulkanwindow_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandVulkanWindow::QWaylandVulkanWindow(QWindow *window, QWaylandDisplay *display) + : QWaylandWindow(window, display) +{ +} + +QWaylandVulkanWindow::~QWaylandVulkanWindow() +{ + invalidateSurface(); +} + +QWaylandWindow::WindowType QWaylandVulkanWindow::windowType() const +{ + return QWaylandWindow::Vulkan; +} + +void QWaylandVulkanWindow::invalidateSurface() +{ + if (m_surface) { + QVulkanInstance *inst = window()->vulkanInstance(); + if (inst) + static_cast(inst->handle())->destroySurface(m_surface); + } + m_surface = VK_NULL_HANDLE; + QWaylandWindow::invalidateSurface(); +} + +VkSurfaceKHR *QWaylandVulkanWindow::vkSurface() +{ + if (m_surface) + return &m_surface; + + QVulkanInstance *vulkanInstance = window()->vulkanInstance(); + if (!vulkanInstance) { + qWarning() << "Attempted to create Vulkan surface without an instance; was QWindow::setVulkanInstance() called?"; + return nullptr; + } + + auto *waylandVulkanInstance = static_cast(vulkanInstance->handle()); + m_surface = waylandVulkanInstance->createSurface(this); + + return &m_surface; +} + +} // namespace QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandvulkanwindow_p.h b/src/plugins/platforms/wayland/qwaylandvulkanwindow_p.h new file mode 100644 index 00000000000..c5692bc7f84 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandvulkanwindow_p.h @@ -0,0 +1,44 @@ +// 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 + +#ifndef QWAYLANDVULKANWINDOW_P_H +#define QWAYLANDVULKANWINDOW_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 "qwaylandwindow_p.h" +#include "qwaylandvulkaninstance_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandVulkanWindow : public QWaylandWindow +{ +public: + explicit QWaylandVulkanWindow(QWindow *window, QWaylandDisplay *display); + ~QWaylandVulkanWindow() override; + + WindowType windowType() const override; + void invalidateSurface() override; + + VkSurfaceKHR *vkSurface(); + +private: + VkSurfaceKHR m_surface = VK_NULL_HANDLE; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDVULKANWINDOW_P_H diff --git a/src/plugins/platforms/wayland/qwaylandwindow.cpp b/src/plugins/platforms/wayland/qwaylandwindow.cpp new file mode 100644 index 00000000000..d6fd087c256 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandwindow.cpp @@ -0,0 +1,1898 @@ +// Copyright (C) 2016 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 + +#include "qwaylandwindow_p.h" + +#include "qwaylandbuffer_p.h" +#include "qwaylandcursor_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandsurface_p.h" +#include "qwaylandinputdevice_p.h" +#include "qwaylandfractionalscale_p.h" +#include "qwaylandscreen_p.h" +#include "qwaylandshellsurface_p.h" +#include "qwaylandsubsurface_p.h" +#include "qwaylandabstractdecoration_p.h" +#include "qwaylandplatformservices_p.h" +#include "qwaylandnativeinterface_p.h" +#include "qwaylanddecorationfactory_p.h" +#include "qwaylandshmbackingstore_p.h" +#include "qwaylandshellintegration_p.h" +#include "qwaylandviewport_p.h" +#include "qwaylandcolormanagement_p.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace QtWaylandClient { + +Q_LOGGING_CATEGORY(lcWaylandBackingstore, "qt.qpa.wayland.backingstore") + +QWaylandWindow *QWaylandWindow::mMouseGrab = nullptr; +QWaylandWindow *QWaylandWindow::mTopPopup = nullptr; + +QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display) + : QPlatformWindow(window) + , mDisplay(display) + , mSurfaceLock(QReadWriteLock::Recursive) + , mShellIntegration(display->shellIntegration()) +{ + { + bool ok; + int frameCallbackTimeout = qEnvironmentVariableIntValue("QT_WAYLAND_FRAME_CALLBACK_TIMEOUT", &ok); + if (ok) + mFrameCallbackTimeout = frameCallbackTimeout; + } + + initializeWlSurface(); + + setWindowIcon(window->icon()); + + connect(this, &QWaylandWindow::wlSurfaceCreated, this, + &QNativeInterface::Private::QWaylandWindow::surfaceCreated); + connect(this, &QWaylandWindow::wlSurfaceDestroyed, this, + &QNativeInterface::Private::QWaylandWindow::surfaceDestroyed); +} + +QWaylandWindow::~QWaylandWindow() +{ + mWindowDecoration.reset(); + reset(); + + const QWindow *parent = window(); + const auto tlw = QGuiApplication::topLevelWindows(); + for (QWindow *w : tlw) { + if (w->transientParent() == parent) + QWindowSystemInterface::handleCloseEvent(w); + } + + if (mMouseGrab == this) { + mMouseGrab = nullptr; + } +} + +void QWaylandWindow::ensureSize() +{ + if (mBackingStore) { + setBackingStore(mBackingStore); + mBackingStore->recreateBackBufferIfNeeded(); + } +} + +void QWaylandWindow::initWindow() +{ + /** + * Cleanup window state just before showing. + * This is necessary because a render could still have been running and commit + * after the window was last hidden and the last null was attached + * + * When we port to synchronous delivery it should be possible to drop this + */ + mSurface->attach(nullptr, 0, 0); + mSurface->commit(); + resetFrameCallback(); + + if (window()->type() == Qt::Desktop) + return; + + if (shouldCreateSubSurface()) { + Q_ASSERT(!mSubSurfaceWindow); + + auto *parent = static_cast(QPlatformWindow::parent()); + if (!parent->mSurface) + parent->initializeWlSurface(); + if (parent->wlSurface()) { + if (::wl_subsurface *subsurface = mDisplay->createSubSurface(this, parent)) + mSubSurfaceWindow = new QWaylandSubSurface(this, parent, subsurface); + } + } else if (shouldCreateShellSurface()) { + Q_ASSERT(!mShellSurface); + Q_ASSERT(mShellIntegration); + mTransientParent = guessTransientParent(); + if (mTransientParent) { + if (window()->type() == Qt::Popup) { + if (mTopPopup && mTopPopup != mTransientParent) { + qCWarning(lcQpaWayland) << "Creating a popup with a parent," << mTransientParent->window() + << "which does not match the current topmost grabbing popup," + << mTopPopup->window() << "With some shell surface protocols, this" + << "is not allowed. The wayland QPA plugin is currently handling" + << "it by setting the parent to the topmost grabbing popup." + << "Note, however, that this may cause positioning errors and" + << "popups closing unxpectedly. Please fix the transient parent of the popup."; + mTransientParent = mTopPopup; + } + mTopPopup = this; + } + } + + mShellSurface = mShellIntegration->createShellSurface(this); + if (mShellSurface) { + if (mTransientParent) { + if (window()->type() == Qt::ToolTip || window()->type() == Qt::Popup) + mTransientParent->addChildPopup(this); + } + + // Set initial surface title + setWindowTitle(window()->title()); + mShellSurface->setIcon(mWindowIcon); + + // The appId is the desktop entry identifier that should follow the + // reverse DNS convention (see + // http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s02.html). According + // to xdg-shell the appId is only the name, without the .desktop suffix. + // + // If the application specifies the desktop file name use that, + // otherwise fall back to the executable name and prepend the + // reversed organization domain when available. + if (!QGuiApplication::desktopFileName().isEmpty()) { + mShellSurface->setAppId(QGuiApplication::desktopFileName()); + } else { + QFileInfo fi = QFileInfo(QCoreApplication::instance()->applicationFilePath()); + QStringList domainName = + QCoreApplication::instance()->organizationDomain().split(QLatin1Char('.'), + Qt::SkipEmptyParts); + + if (domainName.isEmpty()) { + mShellSurface->setAppId(fi.baseName()); + } else { + QString appId; + for (int i = 0; i < domainName.size(); ++i) + appId.prepend(QLatin1Char('.')).prepend(domainName.at(i)); + appId.append(fi.baseName()); + mShellSurface->setAppId(appId); + } + } + // the user may have already set some window properties, so make sure to send them out + for (auto it = m_properties.cbegin(); it != m_properties.cend(); ++it) + mShellSurface->sendProperty(it.key(), it.value()); + + emit surfaceRoleCreated(); + } else { + qWarning("Could not create a shell surface object."); + } + } + + // Enable high-dpi rendering. Scale() returns the screen scale factor and will + // typically be integer 1 (normal-dpi) or 2 (high-dpi). Call set_buffer_scale() + // to inform the compositor that high-resolution buffers will be provided. + if (mViewport) + updateViewport(); + else if (mSurface->version() >= 3) + mSurface->set_buffer_scale(std::ceil(scale())); + + setWindowFlags(window()->flags()); + QRect geometry = windowGeometry(); + QRect defaultGeometry = this->defaultGeometry(); + if (geometry.width() <= 0) + geometry.setWidth(defaultGeometry.width()); + if (geometry.height() <= 0) + geometry.setHeight(defaultGeometry.height()); + + setGeometry_helper(geometry); + setMask(window()->mask()); + if (mShellSurface) + mShellSurface->requestWindowStates(window()->windowStates()); + handleContentOrientationChange(window()->contentOrientation()); + mFlags = window()->flags(); + + mSurface->commit(); +} + +void QWaylandWindow::setPendingImageDescription() +{ + mColorManagementSurface->setImageDescription(mPendingImageDescription.get()); +} + +void QWaylandWindow::initializeWlSurface() +{ + Q_ASSERT(!mSurface); + { + QWriteLocker lock(&mSurfaceLock); + mSurface.reset(new QWaylandSurface(mDisplay)); + connect(mSurface.data(), &QWaylandSurface::screensChanged, + this, &QWaylandWindow::handleScreensChanged); + connect(mSurface.data(), &QWaylandSurface::preferredBufferScaleChanged, + this, &QWaylandWindow::updateScale); + connect(mSurface.data(), &QWaylandSurface::preferredBufferTransformChanged, + this, &QWaylandWindow::updateBufferTransform); + mSurface->m_window = this; + } + emit wlSurfaceCreated(); + + if (mDisplay->fractionalScaleManager() && qApp->highDpiScaleFactorRoundingPolicy() == Qt::HighDpiScaleFactorRoundingPolicy::PassThrough) { + mFractionalScale.reset(new QWaylandFractionalScale(mDisplay->fractionalScaleManager()->get_fractional_scale(mSurface->object()))); + + connect(mFractionalScale.data(), &QWaylandFractionalScale::preferredScaleChanged, + this, &QWaylandWindow::updateScale); + } + // The fractional scale manager check is needed to work around Gnome < 36 where viewports don't work + // Right now viewports are only necessary when a fractional scale manager is used + if (display()->viewporter() && display()->fractionalScaleManager()) { + mViewport.reset(new QWaylandViewport(display()->createViewport(this))); + } + + QColorSpace requestedColorSpace = window()->requestedFormat().colorSpace(); + if (requestedColorSpace != QColorSpace{} && mDisplay->colorManager()) { + // TODO try a similar (same primaries + supported transfer function) color space if this fails? + mPendingImageDescription = mDisplay->colorManager()->createImageDescription(requestedColorSpace); + if (mPendingImageDescription) { + if (!mColorManagementSurface) + mColorManagementSurface = std::make_unique(mDisplay->colorManager()->get_surface(surface())); + connect(mPendingImageDescription.get(), &ImageDescription::ready, this, &QWaylandWindow::setPendingImageDescription, Qt::SingleShotConnection); + mSurfaceFormat.setColorSpace(requestedColorSpace); + } else { + qCWarning(lcQpaWayland) << "couldn't create image description for requested color space" << requestedColorSpace; + } + } +} + +void QWaylandWindow::setFormat(const QSurfaceFormat &format) +{ + const auto colorSpace = mSurfaceFormat.colorSpace(); + mSurfaceFormat = format; + mSurfaceFormat.setColorSpace(colorSpace); +} + +void QWaylandWindow::setShellIntegration(QWaylandShellIntegration *shellIntegration) +{ + Q_ASSERT(shellIntegration); + if (mShellSurface) { + qCWarning(lcQpaWayland) << "Cannot set shell integration while there's already a shell surface created"; + return; + } + mShellIntegration = shellIntegration; +} + +bool QWaylandWindow::shouldCreateShellSurface() const +{ + if (!shellIntegration()) + return false; + + if (shouldCreateSubSurface()) + return false; + + if (window()->inherits("QShapedPixmapWindow")) + return false; + + if (qEnvironmentVariableIsSet("QT_WAYLAND_USE_BYPASSWINDOWMANAGERHINT")) + return !(window()->flags() & Qt::BypassWindowManagerHint); + + return true; +} + +bool QWaylandWindow::shouldCreateSubSurface() const +{ + return QPlatformWindow::parent() != nullptr; +} + +void QWaylandWindow::beginFrame() +{ + mSurfaceLock.lockForRead(); + mInFrameRender = true; +} + +void QWaylandWindow::endFrame() +{ + mSurfaceLock.unlock(); + mInFrameRender = false; +} + +void QWaylandWindow::reset() +{ + resetSurfaceRole(); + + if (mSurface) { + { + QWriteLocker lock(&mSurfaceLock); + invalidateSurface(); + mSurface.reset(); + mViewport.reset(); + mFractionalScale.reset(); + mColorManagementSurface.reset(); + mPendingImageDescription.reset(); + } + emit wlSurfaceDestroyed(); + } + + + mScale = std::nullopt; + mOpaqueArea = QRegion(); + mMask = QRegion(); + + mInputRegion = QRegion(); + mTransparentInputRegion = false; + + mDisplay->handleWindowDestroyed(this); +} + +void QWaylandWindow::resetSurfaceRole() +{ + // Old Reset + closeChildPopups(); + + if (mTopPopup == this) + mTopPopup = mTransientParent && (mTransientParent->window()->type() == Qt::Popup) ? mTransientParent : nullptr; + if (mTransientParent) + mTransientParent->removeChildPopup(this); + mTransientParent = nullptr; + delete std::exchange(mShellSurface, nullptr); + delete std::exchange(mSubSurfaceWindow, nullptr); + emit surfaceRoleDestroyed(); + + resetFrameCallback(); + mInFrameRender = false; + mWaitingToApplyConfigure = false; + mExposed = false; +} + +void QWaylandWindow::resetFrameCallback() +{ + { + QMutexLocker lock(&mFrameSyncMutex); + if (mFrameCallback) { + wl_callback_destroy(mFrameCallback); + mFrameCallback = nullptr; + } + mFrameCallbackElapsedTimer.invalidate(); + mWaitingForFrameCallback = false; + } + if (mFrameCallbackCheckIntervalTimerId != -1) { + killTimer(mFrameCallbackCheckIntervalTimerId); + mFrameCallbackCheckIntervalTimerId = -1; + } + mFrameCallbackTimedOut = false; +} + +QWaylandWindow *QWaylandWindow::fromWlSurface(::wl_surface *surface) +{ + if (auto *s = QWaylandSurface::fromWlSurface(surface)) + return s->m_window; + return nullptr; +} + +WId QWaylandWindow::winId() const +{ + return reinterpret_cast(wlSurface()); +} + +void QWaylandWindow::setParent(const QPlatformWindow *parent) +{ + if (lastParent == parent) + return; + + if (mSubSurfaceWindow && parent) { // new parent, but we were a subsurface already + delete mSubSurfaceWindow; + QWaylandWindow *p = const_cast(static_cast(parent)); + mSubSurfaceWindow = new QWaylandSubSurface(this, p, mDisplay->createSubSurface(this, p)); + } else if ((!lastParent && parent) || (lastParent && !parent)) { + // we're changing role, need to make a new wl_surface + reset(); + initializeWlSurface(); + if (window()->isVisible()) { + initWindow(); + } + } + lastParent = parent; +} + +QString QWaylandWindow::windowTitle() const +{ + return mWindowTitle; +} + +void QWaylandWindow::setWindowTitle(const QString &title) +{ + const QString separator = QString::fromUtf8(" \xe2\x80\x94 "); // unicode character U+2014, EM DASH + const QString formatted = formatWindowTitle(title, separator); + + const int libwaylandMaxBufferSize = 4096; + // Some parts of the buffer is used for metadata, so subtract 100 to be on the safe side. + // Also, QString is in utf-16, which means that in the worst case each character will be + // three bytes when converted to utf-8 (which is what libwayland uses), so divide by three. + const int maxLength = libwaylandMaxBufferSize / 3 - 100; + + auto truncated = QStringView{formatted}.left(maxLength); + if (truncated.size() < formatted.size()) { + qCWarning(lcQpaWayland) << "Window titles longer than" << maxLength << "characters are not supported." + << "Truncating window title (from" << formatted.size() << "chars)"; + } + + mWindowTitle = truncated.toString(); + + if (mShellSurface) + mShellSurface->setTitle(mWindowTitle); + + if (mWindowDecorationEnabled && window()->isVisible()) + mWindowDecoration->update(); +} + +void QWaylandWindow::setWindowIcon(const QIcon &icon) +{ + mWindowIcon = icon; + + if (mWindowDecorationEnabled && window()->isVisible()) + mWindowDecoration->update(); + if (mShellSurface) + mShellSurface->setIcon(icon); +} + +QRect QWaylandWindow::defaultGeometry() const +{ + return QRect(QPoint(), QSize(500,500)); +} + +void QWaylandWindow::setGeometry_helper(const QRect &rect) +{ + QPlatformWindow::setGeometry(rect); + if (mViewport) + updateViewport(); + + if (mSubSurfaceWindow) { + QMargins m = static_cast(QPlatformWindow::parent())->clientSideMargins(); + mSubSurfaceWindow->set_position(rect.x() + m.left(), rect.y() + m.top()); + + QWaylandWindow *parentWindow = mSubSurfaceWindow->parent(); + if (parentWindow && parentWindow->isExposed()) { + QRect parentExposeGeometry(QPoint(), parentWindow->geometry().size()); + parentWindow->sendExposeEvent(parentExposeGeometry); + } + } +} + +void QWaylandWindow::setGeometry(const QRect &r) +{ + auto rect = r; + if (fixedToplevelPositions && !QPlatformWindow::parent() && window()->type() != Qt::Popup + && window()->type() != Qt::ToolTip) { + rect.moveTo(screen()->geometry().topLeft()); + } + setGeometry_helper(rect); + + if (mShellSurface) { + if (!mInResizeFromApplyConfigure) { + const QRect frameGeometry = r.marginsAdded(clientSideMargins()).marginsRemoved(windowContentMargins()); + if (qt_window_private(window())->positionAutomatic) + mShellSurface->setWindowSize(frameGeometry.size()); + else + mShellSurface->setWindowGeometry(frameGeometry); + } + } + + if (mShellSurface) + mShellSurface->setContentGeometry(windowContentGeometry()); + + if (isOpaque() && mMask.isEmpty()) + setOpaqueArea(QRect(QPoint(0, 0), rect.size())); + + + if (window()->isVisible() && rect.isValid()) { + ensureSize(); + if (mWindowDecorationEnabled) + mWindowDecoration->update(); + + QWindowSystemInterface::handleGeometryChange(window(), geometry()); + mSentInitialResize = true; + } + QRect exposeGeometry(QPoint(), geometry().size()); + if (isExposed() && !mInResizeFromApplyConfigure && exposeGeometry != mLastExposeGeometry) + sendExposeEvent(exposeGeometry); +} + +void QWaylandWindow::updateInputRegion() +{ + if (!mSurface) + return; + + const bool transparentInputRegion = mFlags.testFlag(Qt::WindowTransparentForInput); + + QRegion inputRegion; + if (!transparentInputRegion) + inputRegion = mMask; + + if (mInputRegion == inputRegion && mTransparentInputRegion == transparentInputRegion) + return; + + mInputRegion = inputRegion; + mTransparentInputRegion = transparentInputRegion; + + if (mInputRegion.isEmpty() && !mTransparentInputRegion) { + mSurface->set_input_region(nullptr); + } else { + struct ::wl_region *region = mDisplay->createRegion(mInputRegion); + mSurface->set_input_region(region); + wl_region_destroy(region); + } +} + +void QWaylandWindow::updateViewport() +{ + if (!surfaceSize().isEmpty()) + mViewport->setDestination(surfaceSize()); +} + +void QWaylandWindow::setGeometryFromApplyConfigure(const QPoint &globalPosition, const QSize &sizeWithMargins) +{ + QMargins margins = clientSideMargins(); + + QPoint positionWithoutMargins = globalPosition + QPoint(margins.left(), margins.top()); + int widthWithoutMargins = qMax(sizeWithMargins.width() - (margins.left() + margins.right()), 1); + int heightWithoutMargins = qMax(sizeWithMargins.height() - (margins.top() + margins.bottom()), 1); + + QRect geometry(positionWithoutMargins, QSize(widthWithoutMargins, heightWithoutMargins)); + + mInResizeFromApplyConfigure = true; + setGeometry(geometry); + mInResizeFromApplyConfigure = false; +} + +void QWaylandWindow::repositionFromApplyConfigure(const QPoint &globalPosition) +{ + QMargins margins = clientSideMargins(); + QPoint positionWithoutMargins = globalPosition + QPoint(margins.left(), margins.top()); + + QRect geometry(positionWithoutMargins, windowGeometry().size()); + mInResizeFromApplyConfigure = true; + setGeometry(geometry); + mInResizeFromApplyConfigure = false; +} + +void QWaylandWindow::resizeFromApplyConfigure(const QSize &sizeWithMargins, const QPoint &offset) +{ + QMargins margins = clientSideMargins(); + int widthWithoutMargins = qMax(sizeWithMargins.width() - (margins.left() + margins.right()), 1); + int heightWithoutMargins = qMax(sizeWithMargins.height() - (margins.top() + margins.bottom()), 1); + QRect geometry(windowGeometry().topLeft(), QSize(widthWithoutMargins, heightWithoutMargins)); + + mOffset += offset; + mInResizeFromApplyConfigure = true; + setGeometry(geometry); + mInResizeFromApplyConfigure = false; +} + +void QWaylandWindow::sendExposeEvent(const QRect &rect) +{ + if (!(mShellSurface && mShellSurface->handleExpose(rect))) { + mLastExposeGeometry = rect; + QWindowSystemInterface::handleExposeEvent(window(), rect); + } + else + qCDebug(lcQpaWayland) << "sendExposeEvent: intercepted by shell extension, not sending"; +} + +QPlatformScreen *QWaylandWindow::calculateScreenFromSurfaceEvents() const +{ + QReadLocker lock(&mSurfaceLock); + if (mSurface) { + if (auto *screen = mSurface->oldestEnteredScreen()) + return screen; + } + return QPlatformWindow::screen(); +} + +void QWaylandWindow::setVisible(bool visible) +{ + // Workaround for issue where setVisible may be called with the same value twice + if (lastVisible == visible) + return; + lastVisible = visible; + + if (visible) { + setGeometry(windowGeometry()); + initWindow(); + updateExposure(); + // Don't flush the events here, or else the newly visible window may start drawing, but since + // there was no frame before it will be stuck at the waitForFrameSync() in + // QWaylandShmBackingStore::beginPaint(). + + if (mShellSurface) + mShellSurface->requestActivateOnShow(); + } else { + // make sure isExposed is false during the next event dispatch + mExposed = false; + sendExposeEvent(QRect()); + resetSurfaceRole(); + mSurface->attach(nullptr, 0, 0); + mSurface->commit(); + } +} + + +void QWaylandWindow::raise() +{ + if (mShellSurface) + mShellSurface->raise(); +} + + +void QWaylandWindow::lower() +{ + if (mShellSurface) + mShellSurface->lower(); +} + +void QWaylandWindow::setMask(const QRegion &mask) +{ + QReadLocker locker(&mSurfaceLock); + if (!mSurface) + return; + + if (mMask == mask) + return; + + mMask = mask; + + updateInputRegion(); + + if (isOpaque()) { + if (mMask.isEmpty()) + setOpaqueArea(QRect(QPoint(0, 0), geometry().size())); + else + setOpaqueArea(mMask); + } +} + +void QWaylandWindow::setAlertState(bool enabled) +{ + if (mShellSurface) + mShellSurface->setAlertState(enabled); +} + +bool QWaylandWindow::isAlertState() const +{ + if (mShellSurface) + return mShellSurface->isAlertState(); + + return false; +} + +void QWaylandWindow::applyConfigureWhenPossible() +{ + if (!mWaitingToApplyConfigure) { + mWaitingToApplyConfigure = true; + QMetaObject::invokeMethod(this, "applyConfigure", Qt::QueuedConnection); + } +} + +void QWaylandWindow::applyConfigure() +{ + if (!mWaitingToApplyConfigure) + return; + + Q_ASSERT_X(QThread::currentThreadId() == QThreadData::get2(thread())->threadId.loadRelaxed(), + "QWaylandWindow::applyConfigure", "not called from main thread"); + + // If we're mid paint, use an exposeEvent to flush the current frame. + // When this completes we know that no other frames will be rendering. + // This could be improved in future as we 're blocking for not just the frame to finish but one additional extra frame. + if (mInFrameRender) + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), geometry().size())); + if (mShellSurface) + mShellSurface->applyConfigure(); + + mWaitingToApplyConfigure = false; + QRect exposeGeometry(QPoint(), geometry().size()); + sendExposeEvent(exposeGeometry); + QWindowSystemInterface::flushWindowSystemEvents(); +} + +void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) +{ + QReadLocker locker(&mSurfaceLock); + if (mSurface == nullptr) + return; + + if (buffer) { + Q_ASSERT(!buffer->committed()); + handleUpdate(); + buffer->setBusy(true); + if (mSurface->version() >= WL_SURFACE_OFFSET_SINCE_VERSION) { + mSurface->offset(x, y); + mSurface->attach(buffer->buffer(), 0, 0); + } else { + mSurface->attach(buffer->buffer(), x, y); + } + } else { + mSurface->attach(nullptr, 0, 0); + } +} + +void QWaylandWindow::attachOffset(QWaylandBuffer *buffer) +{ + attach(buffer, mOffset.x(), mOffset.y()); + mOffset = QPoint(); +} + +void QWaylandWindow::damage(const QRect &rect) +{ + QReadLocker locker(&mSurfaceLock); + if (mSurface == nullptr) + return; + + const qreal s = scale(); + if (mSurface->version() >= 4) { + const QRect bufferRect = + QRectF(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()) + .toAlignedRect(); + mSurface->damage_buffer(bufferRect.x(), bufferRect.y(), bufferRect.width(), + bufferRect.height()); + } else { + mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); + } +} + +void QWaylandWindow::safeCommit(QWaylandBuffer *buffer, const QRegion &damage) +{ + if (isExposed()) { + commit(buffer, damage); + } else { + buffer->setBusy(false); + } +} + +bool QWaylandWindow::allowsIndependentThreadedRendering() const +{ + return !mWaitingToApplyConfigure; +} + +void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage) +{ + Q_ASSERT(isExposed()); + if (buffer->committed()) { + mSurface->commit(); + qCDebug(lcWaylandBackingstore) << "Buffer already committed, not attaching."; + return; + } + + QReadLocker locker(&mSurfaceLock); + if (!mSurface) + return; + + attachOffset(buffer); + if (mSurface->version() >= 4) { + const qreal s = scale(); + for (const QRect &rect : damage) { + const QRect bufferRect = + QRectF(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()) + .toAlignedRect(); + mSurface->damage_buffer(bufferRect.x(), bufferRect.y(), bufferRect.width(), + bufferRect.height()); + } + } else { + for (const QRect &rect: damage) + mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); + } + Q_ASSERT(!buffer->committed()); + buffer->setCommitted(); + mSurface->commit(); +} + +void QWaylandWindow::commit() +{ + QReadLocker locker(&mSurfaceLock); + if (mSurface != nullptr) + mSurface->commit(); +} + +const wl_callback_listener QWaylandWindow::callbackListener = { + [](void *data, wl_callback *callback, uint32_t time) { + Q_UNUSED(time); + auto *window = static_cast(data); + window->handleFrameCallback(callback); + } +}; + +void QWaylandWindow::handleFrameCallback(wl_callback* callback) +{ + QMutexLocker locker(&mFrameSyncMutex); + if (!mFrameCallback) { + // This means the callback is already unset by QWaylandWindow::reset. + // The wl_callback object will be destroyed there too. + return; + } + Q_ASSERT(callback == mFrameCallback); + wl_callback_destroy(callback); + mFrameCallback = nullptr; + + mWaitingForFrameCallback = false; + mFrameCallbackElapsedTimer.invalidate(); + + // The rest can wait until we can run it on the correct thread + if (mWaitingForUpdateDelivery.testAndSetAcquire(false, true)) { + // Queued connection, to make sure we don't call handleUpdate() from inside waitForFrameSync() + // in the single-threaded case. + QMetaObject::invokeMethod(this, &QWaylandWindow::doHandleFrameCallback, Qt::QueuedConnection); + } + mFrameSyncWait.notify_all(); +} + +void QWaylandWindow::doHandleFrameCallback() +{ + mWaitingForUpdateDelivery.storeRelease(false); + bool wasExposed = isExposed(); + mFrameCallbackTimedOut = false; + // Did setting mFrameCallbackTimedOut make the window exposed? + updateExposure(); + if (wasExposed && hasPendingUpdateRequest()) + deliverUpdateRequest(); + +} + +bool QWaylandWindow::waitForFrameSync(int timeout) +{ + QMutexLocker locker(&mFrameSyncMutex); + + QDeadlineTimer deadline(timeout); + while (mWaitingForFrameCallback && mFrameSyncWait.wait(&mFrameSyncMutex, deadline)) { } + + if (mWaitingForFrameCallback) { + qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; + mFrameCallbackTimedOut = true; + mWaitingForUpdate = false; + QMetaObject::invokeMethod(this, &QWaylandWindow::updateExposure, Qt::QueuedConnection); + } + + return !mWaitingForFrameCallback; +} + +QMargins QWaylandWindow::frameMargins() const +{ + if (mWindowDecorationEnabled) + return mWindowDecoration->margins(); + else if (mShellSurface) + return mShellSurface->serverSideFrameMargins(); + else + return QPlatformWindow::frameMargins(); +} + +QMargins QWaylandWindow::clientSideMargins() const +{ + return mWindowDecorationEnabled ? mWindowDecoration->margins() : QMargins{}; +} + +void QWaylandWindow::setCustomMargins(const QMargins &margins) { + const QMargins oldMargins = mCustomMargins; + mCustomMargins = margins; + propagateSizeHints(); + setGeometry(geometry().marginsRemoved(oldMargins).marginsAdded(margins)); +} + +/*! + * Size, with decorations (including including eventual shadows) in wl_surface coordinates + */ +QSize QWaylandWindow::surfaceSize() const +{ + return geometry().marginsAdded(clientSideMargins()).size(); +} + +QMargins QWaylandWindow::windowContentMargins() const +{ + QMargins shadowMargins; + + if (mWindowDecorationEnabled) + shadowMargins = mWindowDecoration->margins(QWaylandAbstractDecoration::ShadowsOnly); + + if (!mCustomMargins.isNull()) + shadowMargins += mCustomMargins; + + return shadowMargins; +} + +/*! + * Window geometry as defined by the xdg-shell spec (in wl_surface coordinates) + * topLeft is where the shadow stops and the decorations border start. + */ +QRect QWaylandWindow::windowContentGeometry() const +{ + const QMargins margins = windowContentMargins(); + return QRect(QPoint(margins.left(), margins.top()), surfaceSize().shrunkBy(margins)); +} + +/*! + * Converts from wl_surface coordinates to Qt window coordinates. Qt window + * coordinates start inside (not including) the window decorations, while + * wl_surface coordinates start at the first pixel of the buffer. Potentially, + * this should be in the window shadow, although we don't have those. So for + * now, it's the first pixel of the decorations. + */ +QPointF QWaylandWindow::mapFromWlSurface(const QPointF &surfacePosition) const +{ + const QMargins margins = clientSideMargins(); + return QPointF(surfacePosition.x() - margins.left(), surfacePosition.y() - margins.top()); +} + +wl_surface *QWaylandWindow::wlSurface() const +{ + QReadLocker locker(&mSurfaceLock); + return mSurface ? mSurface->object() : nullptr; +} + +QWaylandShellSurface *QWaylandWindow::shellSurface() const +{ + return mShellSurface; +} + +std::any QWaylandWindow::_surfaceRole() const +{ + if (mSubSurfaceWindow) + return mSubSurfaceWindow->object(); + if (mShellSurface) + return mShellSurface->surfaceRole(); + return {}; +} + +QWaylandSubSurface *QWaylandWindow::subSurfaceWindow() const +{ + return mSubSurfaceWindow; +} + +QWaylandScreen *QWaylandWindow::waylandScreen() const +{ + auto *platformScreen = QPlatformWindow::screen(); + Q_ASSERT(platformScreen); + if (platformScreen->isPlaceholder()) + return nullptr; + return static_cast(platformScreen); +} + +void QWaylandWindow::handleContentOrientationChange(Qt::ScreenOrientation orientation) +{ + mLastReportedContentOrientation = orientation; + updateBufferTransform(); +} + +void QWaylandWindow::updateBufferTransform() +{ + QReadLocker locker(&mSurfaceLock); + if (mSurface == nullptr || mSurface->version() < 2) + return; + + wl_output_transform transform; + Qt::ScreenOrientation screenOrientation = Qt::PrimaryOrientation; + + if (mSurface->version() >= 6) { + const auto transform = mSurface->preferredBufferTransform().value_or(WL_OUTPUT_TRANSFORM_NORMAL); + if (auto screen = waylandScreen()) + screenOrientation = screen->toScreenOrientation(transform, Qt::PrimaryOrientation); + } else { + if (auto screen = window()->screen()) + screenOrientation = screen->primaryOrientation(); + } + + const bool isPortrait = (screenOrientation == Qt::PortraitOrientation); + + switch (mLastReportedContentOrientation) { + case Qt::PrimaryOrientation: + transform = WL_OUTPUT_TRANSFORM_NORMAL; + break; + case Qt::LandscapeOrientation: + transform = isPortrait ? WL_OUTPUT_TRANSFORM_270 : WL_OUTPUT_TRANSFORM_NORMAL; + break; + case Qt::PortraitOrientation: + transform = isPortrait ? WL_OUTPUT_TRANSFORM_NORMAL : WL_OUTPUT_TRANSFORM_90; + break; + case Qt::InvertedLandscapeOrientation: + transform = isPortrait ? WL_OUTPUT_TRANSFORM_90 : WL_OUTPUT_TRANSFORM_180; + break; + case Qt::InvertedPortraitOrientation: + transform = isPortrait ? WL_OUTPUT_TRANSFORM_180 : WL_OUTPUT_TRANSFORM_270; + break; + default: + Q_UNREACHABLE(); + } + mSurface->set_buffer_transform(transform); +} + +void QWaylandWindow::setOrientationMask(Qt::ScreenOrientations mask) +{ + if (mShellSurface) + mShellSurface->setContentOrientationMask(mask); +} + +void QWaylandWindow::setWindowState(Qt::WindowStates states) +{ + if (mShellSurface) + mShellSurface->requestWindowStates(states); +} + +void QWaylandWindow::setWindowFlags(Qt::WindowFlags flags) +{ + if (mShellSurface) + mShellSurface->setWindowFlags(flags); + + mFlags = flags; + createDecoration(); + + QReadLocker locker(&mSurfaceLock); + updateInputRegion(); +} + +bool QWaylandWindow::createDecoration() +{ + Q_ASSERT_X(QThread::currentThreadId() == QThreadData::get2(thread())->threadId.loadRelaxed(), + "QWaylandWindow::createDecoration", "not called from main thread"); + // TODO: client side decorations do not work with Vulkan backend. + if (window()->surfaceType() == QSurface::VulkanSurface) + return false; + if (!mDisplay->supportsWindowDecoration()) + return false; + + static bool decorationPluginFailed = false; + bool decoration = false; + switch (window()->type()) { + case Qt::Window: + case Qt::Widget: + case Qt::Dialog: + case Qt::Tool: + case Qt::Drawer: + decoration = true; + break; + default: + break; + } + if (mFlags & Qt::FramelessWindowHint) + decoration = false; + if (mFlags & Qt::BypassWindowManagerHint) + decoration = false; + if (mSubSurfaceWindow) + decoration = false; + if (!mShellSurface || !mShellSurface->wantsDecorations()) + decoration = false; + + bool hadDecoration = mWindowDecorationEnabled; + if (decoration && !decorationPluginFailed) { + if (!mWindowDecorationEnabled) { + if (mWindowDecoration) { + mWindowDecoration.reset(); + } + + QStringList decorations = QWaylandDecorationFactory::keys(); + if (decorations.empty()) { + qWarning() << "No decoration plugins available. Running with no decorations."; + decorationPluginFailed = true; + return false; + } + + QString targetKey; + QByteArray decorationPluginName = qgetenv("QT_WAYLAND_DECORATION"); + if (!decorationPluginName.isEmpty()) { + targetKey = QString::fromLocal8Bit(decorationPluginName); + if (!decorations.contains(targetKey)) { + qWarning() << "Requested decoration " << targetKey << " not found, falling back to default"; + targetKey = QString(); // fallthrough + } + } + + if (targetKey.isEmpty()) { + auto unixServices = dynamic_cast( + QGuiApplicationPrivate::platformIntegration()->services()); + const QList desktopNames = unixServices->desktopEnvironment().split(':'); + if (desktopNames.contains("GNOME")) { + if (decorations.contains("adwaita"_L1)) + targetKey = "adwaita"_L1; + else if (decorations.contains("gnome"_L1)) + targetKey = "gnome"_L1; + } else { + // Do not use Adwaita/GNOME decorations on other DEs + decorations.removeAll("adwaita"_L1); + decorations.removeAll("gnome"_L1); + } + } + + if (targetKey.isEmpty()) + targetKey = decorations.first(); // first come, first served. + + mWindowDecoration.reset(QWaylandDecorationFactory::create(targetKey, QStringList())); + if (!mWindowDecoration) { + qWarning() << "Could not create decoration from factory! Running with no decorations."; + decorationPluginFailed = true; + return false; + } + mWindowDecoration->setWaylandWindow(this); + mWindowDecorationEnabled = true; + } + } else { + mWindowDecorationEnabled = false; + } + + if (hadDecoration != mWindowDecorationEnabled) { + for (QWaylandSubSurface *subsurf : std::as_const(mChildren)) { + QPoint pos = subsurf->window()->geometry().topLeft(); + QMargins m = frameMargins(); + subsurf->set_position(pos.x() + m.left(), pos.y() + m.top()); + } + setGeometry(geometry()); + + // creating a decoration changes our margins which in turn change size hints + propagateSizeHints(); + + // This is a special case where the buffer is recreated, but since + // the content rect remains the same, the widgets remain the same + // size and are not redrawn, leaving the new buffer empty. As a simple + // work-around, we trigger a full extra update whenever the client-side + // window decorations are toggled while the window is showing. + window()->requestUpdate(); + } + + return mWindowDecoration.get(); +} + +QWaylandAbstractDecoration *QWaylandWindow::decoration() const +{ + return mWindowDecorationEnabled ? mWindowDecoration.get() : nullptr; +} + +static QWaylandWindow *closestShellSurfaceWindow(QWindow *window) +{ + while (window) { + auto w = static_cast(window->handle()); + if (w && w->shellSurface()) + return w; + window = window->transientParent() ? window->transientParent() : window->parent(); + } + return nullptr; +} + +QWaylandWindow *QWaylandWindow::transientParent() const +{ + return mTransientParent; +} + +QWaylandWindow *QWaylandWindow::guessTransientParent() const +{ + // Take the closest window with a shell surface, since the transient parent may be a + // QWidgetWindow or some other window without a shell surface, which is then not able to + // get mouse events. + if (auto transientParent = closestShellSurfaceWindow(window()->transientParent())) + return transientParent; + + if (window()->type() == Qt::Popup) { + if (mTopPopup) + return mTopPopup; + } + + if (window()->type() == Qt::ToolTip || window()->type() == Qt::Popup) { + if (auto lastInputWindow = display()->lastInputWindow()) + return closestShellSurfaceWindow(lastInputWindow->window()); + } + + return nullptr; +} + +void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e) +{ + // There's currently no way to get info about the actual hardware device in use. + // At least we get the correct seat. + const QPointingDevice *device = QPointingDevice::primaryPointingDevice(inputDevice->seatname()); + if (e.type == QEvent::Leave) { + if (mWindowDecorationEnabled) { + if (mMouseEventsInContentArea) + QWindowSystemInterface::handleLeaveEvent(window()); + } else { + QWindowSystemInterface::handleLeaveEvent(window()); + } +#if QT_CONFIG(cursor) + restoreMouseCursor(inputDevice); +#endif + return; + } + + if (mWindowDecorationEnabled) { + handleMouseEventWithDecoration(inputDevice, e); + } else { + switch (e.type) { + case QEvent::Enter: + QWindowSystemInterface::handleEnterEvent(window(), e.local, e.global); +#if QT_CONFIG(cursor) + mDisplay->waylandCursor()->setPosFromEnterEvent(e.global.toPoint()); +#endif + break; + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, device, e.local, e.global, e.buttons, e.button, e.type, e.modifiers); + break; + case QEvent::Wheel: + QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, device, e.local, e.global, + e.pixelDelta, e.angleDelta, e.modifiers, + e.phase, e.source, e.inverted); + break; + default: + Q_UNREACHABLE(); + } + } + +#if QT_CONFIG(cursor) + if (e.type == QEvent::Enter) { + QRect contentGeometry = QRect(QPoint(), surfaceSize()).marginsRemoved(clientSideMargins()); + if (contentGeometry.contains(e.local.toPoint())) + restoreMouseCursor(inputDevice); + } +#endif +} + +#ifndef QT_NO_GESTURES +void QWaylandWindow::handleSwipeGesture(QWaylandInputDevice *inputDevice, + const QWaylandPointerGestureSwipeEvent &e) +{ + switch (e.state) { + case Qt::GestureStarted: + if (mGestureState != GestureNotActive) + qCWarning(lcQpaWaylandInput) << "Unexpected GestureStarted while already active"; + + if (mWindowDecorationEnabled && !mMouseEventsInContentArea) { + // whole gesture sequence will be ignored + mGestureState = GestureActiveInDecoration; + return; + } + + mGestureState = GestureActiveInContentArea; + QWindowSystemInterface::handleGestureEvent(window(), e.timestamp, + inputDevice->mTouchPadDevice, + Qt::BeginNativeGesture, + e.local, e.global, e.fingers); + break; + case Qt::GestureUpdated: + if (mGestureState != GestureActiveInContentArea) + return; + + if (!e.delta.isNull()) { + QWindowSystemInterface::handleGestureEventWithValueAndDelta( + window(), e.timestamp, inputDevice->mTouchPadDevice, + Qt::PanNativeGesture, + 0, e.delta, e.local, e.global, e.fingers); + } + break; + case Qt::GestureFinished: + case Qt::GestureCanceled: + if (mGestureState == GestureActiveInDecoration) { + mGestureState = GestureNotActive; + return; + } + + if (mGestureState != GestureActiveInContentArea) + qCWarning(lcQpaWaylandInput) << "Unexpected" << (e.state == Qt::GestureFinished ? "GestureFinished" : "GestureCanceled"); + + mGestureState = GestureNotActive; + + // There's currently no way to expose cancelled gestures to the rest of Qt, so + // this part of information is lost. + QWindowSystemInterface::handleGestureEvent(window(), e.timestamp, + inputDevice->mTouchPadDevice, + Qt::EndNativeGesture, + e.local, e.global, e.fingers); + break; + default: + break; + } +} + +void QWaylandWindow::handlePinchGesture(QWaylandInputDevice *inputDevice, + const QWaylandPointerGesturePinchEvent &e) +{ + switch (e.state) { + case Qt::GestureStarted: + if (mGestureState != GestureNotActive) + qCWarning(lcQpaWaylandInput) << "Unexpected GestureStarted while already active"; + + if (mWindowDecorationEnabled && !mMouseEventsInContentArea) { + // whole gesture sequence will be ignored + mGestureState = GestureActiveInDecoration; + return; + } + + mGestureState = GestureActiveInContentArea; + QWindowSystemInterface::handleGestureEvent(window(), e.timestamp, + inputDevice->mTouchPadDevice, + Qt::BeginNativeGesture, + e.local, e.global, e.fingers); + break; + case Qt::GestureUpdated: + if (mGestureState != GestureActiveInContentArea) + return; + + if (!e.delta.isNull()) { + QWindowSystemInterface::handleGestureEventWithValueAndDelta( + window(), e.timestamp, inputDevice->mTouchPadDevice, + Qt::PanNativeGesture, + 0, e.delta, e.local, e.global, e.fingers); + } + if (e.rotation_delta != 0) { + QWindowSystemInterface::handleGestureEventWithRealValue(window(), e.timestamp, + inputDevice->mTouchPadDevice, + Qt::RotateNativeGesture, + e.rotation_delta, + e.local, e.global, e.fingers); + } + if (e.scale_delta != 0) { + QWindowSystemInterface::handleGestureEventWithRealValue(window(), e.timestamp, + inputDevice->mTouchPadDevice, + Qt::ZoomNativeGesture, + e.scale_delta, + e.local, e.global, e.fingers); + } + break; + case Qt::GestureFinished: + case Qt::GestureCanceled: + if (mGestureState == GestureActiveInDecoration) { + mGestureState = GestureNotActive; + return; + } + + if (mGestureState != GestureActiveInContentArea) + qCWarning(lcQpaWaylandInput) << "Unexpected" << (e.state == Qt::GestureFinished ? "GestureFinished" : "GestureCanceled"); + + mGestureState = GestureNotActive; + + // There's currently no way to expose cancelled gestures to the rest of Qt, so + // this part of information is lost. + QWindowSystemInterface::handleGestureEvent(window(), e.timestamp, + inputDevice->mTouchPadDevice, + Qt::EndNativeGesture, + e.local, e.global, e.fingers); + break; + default: + break; + } +} +#endif // #ifndef QT_NO_GESTURES + + +bool QWaylandWindow::touchDragDecoration(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, QEventPoint::State state, Qt::KeyboardModifiers mods) +{ + if (!mWindowDecorationEnabled) + return false; + return mWindowDecoration->handleTouch(inputDevice, local, global, state, mods); +} + +bool QWaylandWindow::handleTabletEventDecoration(QWaylandInputDevice *inputDevice, + const QPointF &local, const QPointF &global, + Qt::MouseButtons buttons, + Qt::KeyboardModifiers modifiers) +{ + if (!mWindowDecorationEnabled) + return false; + return mWindowDecoration->handleMouse(inputDevice, local, global, buttons, modifiers); +} + +void QWaylandWindow::handleMouseEventWithDecoration(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e) +{ + // There's currently no way to get info about the actual hardware device in use. + // At least we get the correct seat. + const QPointingDevice *device = QPointingDevice::primaryPointingDevice(inputDevice->seatname()); + if (mMousePressedInContentArea == Qt::NoButton && + mWindowDecoration->handleMouse(inputDevice, e.local, e.global, e.buttons, e.modifiers)) { + if (mMouseEventsInContentArea) { + QWindowSystemInterface::handleLeaveEvent(window()); + mMouseEventsInContentArea = false; + } + return; + } + + QMargins marg = clientSideMargins(); + QRect windowRect(0 + marg.left(), + 0 + marg.top(), + geometry().size().width(), + geometry().size().height()); + if (windowRect.contains(e.local.toPoint()) || mMousePressedInContentArea != Qt::NoButton) { + const QPointF localTranslated = mapFromWlSurface(e.local); + QPointF globalTranslated = e.global; + globalTranslated.setX(globalTranslated.x() - marg.left()); + globalTranslated.setY(globalTranslated.y() - marg.top()); + if (!mMouseEventsInContentArea) { +#if QT_CONFIG(cursor) + restoreMouseCursor(inputDevice); +#endif + QWindowSystemInterface::handleEnterEvent(window()); + } + + switch (e.type) { + case QEvent::Enter: + QWindowSystemInterface::handleEnterEvent(window(), localTranslated, globalTranslated); +#if QT_CONFIG(cursor) + mDisplay->waylandCursor()->setPosFromEnterEvent(e.global.toPoint()); +#endif + break; + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, device, localTranslated, globalTranslated, e.buttons, e.button, e.type, e.modifiers); + break; + case QEvent::Wheel: { + QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, device, + localTranslated, globalTranslated, + e.pixelDelta, e.angleDelta, e.modifiers, + e.phase, e.source, e.inverted); + break; + } + default: + Q_UNREACHABLE(); + } + + mMouseEventsInContentArea = true; + mMousePressedInContentArea = e.buttons; + } else { + if (mMouseEventsInContentArea) { + QWindowSystemInterface::handleLeaveEvent(window()); + mMouseEventsInContentArea = false; + } + } +} + +void QWaylandWindow::handleScreensChanged() +{ + QPlatformScreen *newScreen = calculateScreenFromSurfaceEvents(); + + if (!newScreen || newScreen->screen() == window()->screen()) + return; + + QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); + + if (fixedToplevelPositions && !QPlatformWindow::parent() && window()->type() != Qt::Popup + && window()->type() != Qt::ToolTip + && geometry().topLeft() != newScreen->geometry().topLeft()) { + auto geometry = this->geometry(); + geometry.moveTo(newScreen->geometry().topLeft()); + setGeometry(geometry); + } + + updateScale(); + updateBufferTransform(); +} + +void QWaylandWindow::updateScale() +{ + if (mFractionalScale) { + qreal preferredScale = mFractionalScale->preferredScale().value_or(1.0); + preferredScale = std::max(1.0, preferredScale); + Q_ASSERT(mViewport); + setScale(preferredScale); + return; + } + + if (mSurface && mSurface->version() >= 6) { + auto preferredScale = mSurface->preferredBufferScale().value_or(1); + preferredScale = std::max(1, preferredScale); + setScale(preferredScale); + return; + } + + int scale = screen()->isPlaceholder() ? 1 : static_cast(screen())->scale(); + setScale(scale); +} + +void QWaylandWindow::setScale(qreal newScale) +{ + if (mScale.has_value() && qFuzzyCompare(mScale.value(), newScale)) + return; + mScale = newScale; + + if (mSurface) { + if (mViewport) + updateViewport(); + else if (mSurface->version() >= 3) + mSurface->set_buffer_scale(std::ceil(newScale)); + } + ensureSize(); + + QWindowSystemInterface::handleWindowDevicePixelRatioChanged(window()); + if (isExposed()) { + // redraw at the new DPR + window()->requestUpdate(); + sendExposeEvent(QRect(QPoint(), geometry().size())); + } +} + +#if QT_CONFIG(cursor) +void QWaylandWindow::setMouseCursor(QWaylandInputDevice *device, const QCursor &cursor) +{ + int fallbackBufferScale = qCeil(devicePixelRatio()); + device->setCursor(&cursor, {}, fallbackBufferScale); +} + +void QWaylandWindow::restoreMouseCursor(QWaylandInputDevice *device) +{ + if (const QCursor *overrideCursor = QGuiApplication::overrideCursor()) + setMouseCursor(device, *overrideCursor); + else + setMouseCursor(device, window()->cursor()); +} +#endif + +void QWaylandWindow::requestActivateWindow() +{ + if (mShellSurface) + mShellSurface->requestActivate(); +} + +bool QWaylandWindow::calculateExposure() const +{ + if (!window()->isVisible()) + return false; + + if (mFrameCallbackTimedOut) + return false; + + if (mShellSurface) + return mShellSurface->isExposed(); + + if (mSubSurfaceWindow) + return mSubSurfaceWindow->parent()->isExposed(); + + return !(shouldCreateShellSurface() || shouldCreateSubSurface()); +} + +void QWaylandWindow::updateExposure() +{ + bool exposed = calculateExposure(); + if (exposed == mExposed) + return; + + mExposed = exposed; + + if (!exposed) + sendExposeEvent(QRect()); + else + sendExposeEvent(QRect(QPoint(), geometry().size())); + + for (QWaylandSubSurface *subSurface : std::as_const(mChildren)) { + auto subWindow = subSurface->window(); + subWindow->updateExposure(); + } +} + +bool QWaylandWindow::isExposed() const +{ + return mExposed; +} + +bool QWaylandWindow::isActive() const +{ + return mDisplay->isWindowActivated(this); +} + +qreal QWaylandWindow::scale() const +{ + return devicePixelRatio(); +} + +qreal QWaylandWindow::devicePixelRatio() const +{ + return mScale.value_or(waylandScreen() ? waylandScreen()->scale() : 1); +} + +bool QWaylandWindow::setMouseGrabEnabled(bool grab) +{ + if (window()->type() != Qt::Popup) { + qWarning("This plugin supports grabbing the mouse only for popup windows"); + return false; + } + + mMouseGrab = grab ? this : nullptr; + return true; +} + +QWaylandWindow::ToplevelWindowTilingStates QWaylandWindow::toplevelWindowTilingStates() const +{ + return mLastReportedToplevelWindowTilingStates; +} + +void QWaylandWindow::handleToplevelWindowTilingStatesChanged(ToplevelWindowTilingStates states) +{ + mLastReportedToplevelWindowTilingStates = states; +} + +Qt::WindowStates QWaylandWindow::windowStates() const +{ + return mLastReportedWindowStates; +} + +void QWaylandWindow::handleWindowStatesChanged(Qt::WindowStates states) +{ + createDecoration(); + Qt::WindowStates statesWithoutActive = states & ~Qt::WindowActive; + Qt::WindowStates lastStatesWithoutActive = mLastReportedWindowStates & ~Qt::WindowActive; + QWindowSystemInterface::handleWindowStateChanged(window(), statesWithoutActive, + lastStatesWithoutActive); + mLastReportedWindowStates = states; +} + +void QWaylandWindow::sendProperty(const QString &name, const QVariant &value) +{ + m_properties.insert(name, value); + QWaylandNativeInterface *nativeInterface = static_cast( + QGuiApplication::platformNativeInterface()); + nativeInterface->emitWindowPropertyChanged(this, name); + if (mShellSurface) + mShellSurface->sendProperty(name, value); +} + +void QWaylandWindow::setProperty(const QString &name, const QVariant &value) +{ + m_properties.insert(name, value); + QWaylandNativeInterface *nativeInterface = static_cast( + QGuiApplication::platformNativeInterface()); + nativeInterface->emitWindowPropertyChanged(this, name); +} + +QVariantMap QWaylandWindow::properties() const +{ + return m_properties; +} + +QVariant QWaylandWindow::property(const QString &name) +{ + return m_properties.value(name); +} + +QVariant QWaylandWindow::property(const QString &name, const QVariant &defaultValue) +{ + return m_properties.value(name, defaultValue); +} + +#ifdef QT_PLATFORM_WINDOW_HAS_VIRTUAL_SET_BACKING_STORE +void QWaylandWindow::setBackingStore(QPlatformBackingStore *store) +{ + mBackingStore = dynamic_cast(store); +} +#endif + +void QWaylandWindow::timerEvent(QTimerEvent *event) +{ + if (event->timerId() != mFrameCallbackCheckIntervalTimerId) + return; + + { + QMutexLocker lock(&mFrameSyncMutex); + + const bool callbackTimerValid = mFrameCallbackElapsedTimer.isValid(); + const bool callbackTimerExpired = callbackTimerValid && mFrameCallbackElapsedTimer.hasExpired(mFrameCallbackTimeout); + if (!callbackTimerValid || callbackTimerExpired) { + killTimer(mFrameCallbackCheckIntervalTimerId); + mFrameCallbackCheckIntervalTimerId = -1; + } + if (!callbackTimerValid || !callbackTimerExpired) { + return; + } + mFrameCallbackElapsedTimer.invalidate(); + } + + qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; + mFrameCallbackTimedOut = true; + mWaitingForUpdate = false; + updateExposure(); +} + +void QWaylandWindow::requestUpdate() +{ + qCDebug(lcWaylandBackingstore) << "requestUpdate"; + Q_ASSERT(hasPendingUpdateRequest()); // should be set by QPA + + // If we have a frame callback all is good and will be taken care of there + { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; + } + + // If we've already called deliverUpdateRequest(), but haven't seen any attach+commit/swap yet + // This is a somewhat redundant behavior and might indicate a bug in the calling code, so log + // here so we can get this information when debugging update/frame callback issues. + // Continue as nothing happened, though. + if (mWaitingForUpdate) + qCDebug(lcWaylandBackingstore) << "requestUpdate called twice without committing anything"; + + // Some applications (such as Qt Quick) depend on updates being delivered asynchronously, + // so use invokeMethod to delay the delivery a bit. + QMetaObject::invokeMethod(this, [this] { + // Things might have changed in the meantime + { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; + } + if (hasPendingUpdateRequest()) + deliverUpdateRequest(); + }, Qt::QueuedConnection); +} + +// Should be called whenever we commit a buffer (directly through wl_surface.commit or indirectly +// with eglSwapBuffers) to know when it's time to commit the next one. +// Can be called from the render thread (without locking anything) so make sure to not make races in this method. +void QWaylandWindow::handleUpdate() +{ + qCDebug(lcWaylandBackingstore) << "handleUpdate" << QThread::currentThread(); + + // TODO: Should sync subsurfaces avoid requesting frame callbacks? + QReadLocker lock(&mSurfaceLock); + if (!mSurface) + return; + + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; + + struct ::wl_surface *wrappedSurface = reinterpret_cast(wl_proxy_create_wrapper(mSurface->object())); + wl_proxy_set_queue(reinterpret_cast(wrappedSurface), mDisplay->frameEventQueue()); + mFrameCallback = wl_surface_frame(wrappedSurface); + wl_proxy_wrapper_destroy(wrappedSurface); + wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this); + mWaitingForFrameCallback = true; + mWaitingForUpdate = false; + + // Start a timer for handling the case when the compositor stops sending frame callbacks. + if (mFrameCallbackTimeout > 0) { + QMetaObject::invokeMethod(this, [this] { + QMutexLocker locker(&mFrameSyncMutex); + + if (mWaitingForFrameCallback) { + if (mFrameCallbackCheckIntervalTimerId < 0) + mFrameCallbackCheckIntervalTimerId = startTimer(mFrameCallbackTimeout); + mFrameCallbackElapsedTimer.start(); + } + }, Qt::QueuedConnection); + } +} + +void QWaylandWindow::deliverUpdateRequest() +{ + qCDebug(lcWaylandBackingstore) << "deliverUpdateRequest"; + mWaitingForUpdate = true; + QPlatformWindow::deliverUpdateRequest(); +} + +void QWaylandWindow::addAttachOffset(const QPoint point) +{ + mOffset += point; +} + +void QWaylandWindow::propagateSizeHints() +{ + if (mShellSurface) + mShellSurface->propagateSizeHints(); +} + +bool QWaylandWindow::startSystemResize(Qt::Edges edges) +{ + if (auto *seat = display()->lastInputDevice()) { + bool rc = mShellSurface && mShellSurface->resize(seat, edges); + seat->handleEndDrag(); + return rc; + } + return false; +} + +bool QtWaylandClient::QWaylandWindow::startSystemMove() +{ + if (auto seat = display()->lastInputDevice()) { + bool rc = mShellSurface && mShellSurface->move(seat); + seat->handleEndDrag(); + return rc; + } + return false; +} + +bool QWaylandWindow::isOpaque() const +{ + return window()->requestedFormat().alphaBufferSize() <= 0; +} + +void QWaylandWindow::setOpaqueArea(const QRegion &opaqueArea) +{ + const QRegion translatedOpaqueArea = opaqueArea.translated(clientSideMargins().left(), clientSideMargins().top()); + + if (translatedOpaqueArea == mOpaqueArea || !mSurface) + return; + + mOpaqueArea = translatedOpaqueArea; + + struct ::wl_region *region = mDisplay->createRegion(translatedOpaqueArea); + mSurface->set_opaque_region(region); + wl_region_destroy(region); +} + +void QWaylandWindow::requestXdgActivationToken(uint serial) +{ + if (!mShellSurface) { + qCWarning(lcQpaWayland) << "requestXdgActivationToken is called with no surface role created, emitting synthetic signal"; + Q_EMIT xdgActivationTokenCreated({}); + return; + } + mShellSurface->requestXdgActivationToken(serial); +} + +void QWaylandWindow::setXdgActivationToken(const QString &token) +{ + if (mShellSurface) + mShellSurface->setXdgActivationToken(token); + else + qCWarning(lcQpaWayland) << "setXdgActivationToken is called with no surface role created, token" << token << "discarded"; +} + +void QWaylandWindow::addChildPopup(QWaylandWindow *child) +{ + if (mShellSurface) + mShellSurface->attachPopup(child->shellSurface()); + mChildPopups.append(child); +} + +void QWaylandWindow::removeChildPopup(QWaylandWindow *child) +{ + if (mShellSurface) + mShellSurface->detachPopup(child->shellSurface()); + mChildPopups.removeAll(child); +} + +void QWaylandWindow::closeChildPopups() { + while (!mChildPopups.isEmpty()) { + auto popup = mChildPopups.takeLast(); + popup->resetSurfaceRole(); + } +} + +void QWaylandWindow::reinit() +{ + if (window()->isVisible()) { + initWindow(); + if (hasPendingUpdateRequest()) + deliverUpdateRequest(); + } +} + +bool QWaylandWindow::windowEvent(QEvent *event) +{ + if (event->type() == QEvent::ApplicationPaletteChange + || event->type() == QEvent::ApplicationFontChange) { + if (mWindowDecorationEnabled && window()->isVisible()) + mWindowDecoration->update(); + } + + return QPlatformWindow::windowEvent(event); +} + +QSurfaceFormat QWaylandWindow::format() const +{ + return mSurfaceFormat; +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylandwindow_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylandwindow_p.h b/src/plugins/platforms/wayland/qwaylandwindow_p.h new file mode 100644 index 00000000000..06d8dbed38b --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandwindow_p.h @@ -0,0 +1,411 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDWINDOW_H +#define QWAYLANDWINDOW_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 // for QVariantMap + +#include +#include + +#include +#include +#include +#include + +#include + +struct wl_egl_window; + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +Q_DECLARE_LOGGING_CATEGORY(lcWaylandBackingstore) + +class QWaylandDisplay; +class QWaylandBuffer; +class QWaylandShellSurface; +class QWaylandSubSurface; +class QWaylandAbstractDecoration; +class QWaylandInputDevice; +class QWaylandScreen; +class QWaylandShellIntegration; +class QWaylandShmBackingStore; +class QWaylandPointerEvent; +class QWaylandPointerGestureSwipeEvent; +class QWaylandPointerGesturePinchEvent; +class QWaylandSurface; +class QWaylandFractionalScale; +class QWaylandViewport; +class ColorManagementSurface; +class ImageDescription; + +class Q_WAYLANDCLIENT_EXPORT QWaylandWindow : public QNativeInterface::Private::QWaylandWindow, + public QPlatformWindow +{ + Q_OBJECT +public: + enum WindowType { + Shm, + Egl, + Vulkan + }; + + enum ToplevelWindowTilingState { + WindowNoState = 0, + WindowTiledLeft = 1, + WindowTiledRight = 2, + WindowTiledTop = 4, + WindowTiledBottom = 8 + }; + Q_DECLARE_FLAGS(ToplevelWindowTilingStates, ToplevelWindowTilingState) + + QWaylandWindow(QWindow *window, QWaylandDisplay *display); + ~QWaylandWindow() override; + + // Keep Toplevels position on the top left corner of their screen + static inline bool fixedToplevelPositions = true; + + virtual WindowType windowType() const = 0; + virtual void ensureSize(); + WId winId() const override; + void setVisible(bool visible) override; + void setParent(const QPlatformWindow *parent) override; + + QString windowTitle() const override; + void setWindowTitle(const QString &title) override; + + inline QIcon windowIcon() const; + void setWindowIcon(const QIcon &icon) override; + + void setGeometry(const QRect &rect) override; + + bool allowsIndependentThreadedRendering() const override; + + void resizeFromApplyConfigure(const QSize &sizeWithMargins, const QPoint &offset = {0, 0}); + void repositionFromApplyConfigure(const QPoint &position); + void setGeometryFromApplyConfigure(const QPoint &globalPosition, const QSize &sizeWithMargins); + + void applyConfigureWhenPossible(); //rename to possible? + + void attach(QWaylandBuffer *buffer, int x, int y); + void attachOffset(QWaylandBuffer *buffer); + QPoint attachOffset() const; + + void damage(const QRect &rect); + + void safeCommit(QWaylandBuffer *buffer, const QRegion &damage); + void commit(QWaylandBuffer *buffer, const QRegion &damage); + + void commit(); + + bool waitForFrameSync(int timeout); + + QMargins frameMargins() const override; + QMargins clientSideMargins() const; + void setCustomMargins(const QMargins &margins) override; + QSize surfaceSize() const; + QMargins windowContentMargins() const; + QRect windowContentGeometry() const; + QPointF mapFromWlSurface(const QPointF &surfacePosition) const; + + QWaylandSurface *waylandSurface() const { return mSurface.data(); } + ::wl_surface *wlSurface() const; + ::wl_surface *surface() const override + { + return wlSurface(); + } + static QWaylandWindow *fromWlSurface(::wl_surface *surface); + + QWaylandDisplay *display() const { return mDisplay; } + QWaylandShellSurface *shellSurface() const; + std::any _surfaceRole() const override; + QWaylandSubSurface *subSurfaceWindow() const; + QWaylandScreen *waylandScreen() const; + + void handleContentOrientationChange(Qt::ScreenOrientation orientation) override; + void updateBufferTransform(); + void setOrientationMask(Qt::ScreenOrientations mask); + + ToplevelWindowTilingStates toplevelWindowTilingStates() const; + void handleToplevelWindowTilingStatesChanged(ToplevelWindowTilingStates states); + + Qt::WindowStates windowStates() const; + void setWindowState(Qt::WindowStates states) override; + void setWindowFlags(Qt::WindowFlags flags) override; + void handleWindowStatesChanged(Qt::WindowStates states); + + void raise() override; + void lower() override; + + void setMask(const QRegion ®ion) override; + + void setAlertState(bool enabled) override; + bool isAlertState() const override; + + qreal scale() const; + qreal devicePixelRatio() const override; + + void requestActivateWindow() override; + bool isExposed() const override; + bool isActive() const override; + + QWaylandAbstractDecoration *decoration() const; + + void handleMouse(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e); +#ifndef QT_NO_GESTURES + void handleSwipeGesture(QWaylandInputDevice *inputDevice, + const QWaylandPointerGestureSwipeEvent &e); + void handlePinchGesture(QWaylandInputDevice *inputDevice, + const QWaylandPointerGesturePinchEvent &e); +#endif + + bool touchDragDecoration(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, + QEventPoint::State state, Qt::KeyboardModifiers mods); + bool handleTabletEventDecoration(QWaylandInputDevice *inputDevice, const QPointF &local, + const QPointF &global, Qt::MouseButtons buttons, + Qt::KeyboardModifiers modifiers); + + bool createDecoration(); + +#if QT_CONFIG(cursor) + void setMouseCursor(QWaylandInputDevice *device, const QCursor &cursor); + void restoreMouseCursor(QWaylandInputDevice *device); +#endif + + QWaylandWindow *transientParent() const; + + bool setMouseGrabEnabled(bool grab) override; + static QWaylandWindow *mouseGrab() { return mMouseGrab; } + + void sendProperty(const QString &name, const QVariant &value); + void setProperty(const QString &name, const QVariant &value); + + QVariantMap properties() const; + QVariant property(const QString &name); + QVariant property(const QString &name, const QVariant &defaultValue); + +#ifdef QT_PLATFORM_WINDOW_HAS_VIRTUAL_SET_BACKING_STORE + void setBackingStore(QPlatformBackingStore *store) override; +#else + void setBackingStore(QWaylandShmBackingStore *backingStore) { mBackingStore = backingStore; } +#endif + QWaylandShmBackingStore *backingStore() const { return mBackingStore; } + + void setShellIntegration(QWaylandShellIntegration *shellIntegration); + QWaylandShellIntegration *shellIntegration() const { return mShellIntegration; } + + bool setKeyboardGrabEnabled(bool) override { return false; } + void propagateSizeHints() override; + void addAttachOffset(const QPoint point); + + bool startSystemResize(Qt::Edges edges) override; + bool startSystemMove() override; + + void timerEvent(QTimerEvent *event) override; + void requestUpdate() override; + void handleUpdate(); + void deliverUpdateRequest() override; + + void setXdgActivationToken(const QString &token); + void requestXdgActivationToken(uint serial) override; + + void beginFrame(); + void endFrame(); + + void closeChildPopups(); + + // should be invoked whenever a property that potentially affects + // exposure changes + void updateExposure(); + + virtual void reinit(); + void reset(); + void initializeWlSurface(); + + bool windowEvent(QEvent *event) override; + + QSurfaceFormat format() const override; + +public Q_SLOTS: + void applyConfigure(); + +Q_SIGNALS: + void wlSurfaceCreated(); + void wlSurfaceDestroyed(); + +protected: + virtual void doHandleFrameCallback(); + virtual QRect defaultGeometry() const; + void setFormat(const QSurfaceFormat &format); + + // this should be called directly for buffer size changes only + // use updateExposure for anything affecting the on/off state + void sendExposeEvent(const QRect &rect); + + QWaylandDisplay *mDisplay = nullptr; + + // mSurface can be written by the main thread. Other threads should claim a read lock for access + mutable QReadWriteLock mSurfaceLock; + QScopedPointer mSurface; + QScopedPointer mFractionalScale; + QScopedPointer mViewport; + + QWaylandShellIntegration *mShellIntegration = nullptr; + QWaylandShellSurface *mShellSurface = nullptr; + QWaylandSubSurface *mSubSurfaceWindow = nullptr; + QList mChildren; + + std::unique_ptr mWindowDecoration; + bool mWindowDecorationEnabled = false; + bool mMouseEventsInContentArea = false; + Qt::MouseButtons mMousePressedInContentArea = Qt::NoButton; + +#ifndef QT_NO_GESTURES + enum GestureState { + GestureNotActive, + GestureActiveInContentArea, + GestureActiveInDecoration + }; + + // We want gestures started in the decoration area to be completely ignored even if the mouse + // pointer is later moved to content area. Likewise, gestures started in the content area should + // keep sending events even if the mouse pointer is moved over the decoration (consider that + // the events for that gesture will be sent to us even if it's moved outside the window). + // So we track the gesture state and accept or ignore events based on that. Note that + // concurrent gestures of different types are not allowed in the protocol, so single state is + // enough + GestureState mGestureState = GestureNotActive; +#endif + + std::atomic_bool mFrameCallbackTimedOut = false; // Whether the frame callback has timed out + int mFrameCallbackCheckIntervalTimerId = -1; + QAtomicInt mWaitingForUpdateDelivery = false; + + bool mWaitingForFrameCallback = false; // Protected by mFrameSyncMutex + QElapsedTimer mFrameCallbackElapsedTimer; // Protected by mFrameSyncMutex + struct ::wl_callback *mFrameCallback = nullptr; // Protected by mFrameSyncMutex + QMutex mFrameSyncMutex; + QWaitCondition mFrameSyncWait; + + // True when we have called deliverRequestUpdate, but the client has not yet attached a new buffer + std::atomic_bool mWaitingForUpdate = false; + bool mExposed = false; + + // written from the main thread, read by the render thread + std::atomic_bool mWaitingToApplyConfigure = false; + // written from the render thread, read by the main thread + std::atomic_bool mInFrameRender = false; + + int mFrameCallbackTimeout = 100; + QVariantMap m_properties; + + bool mSentInitialResize = false; + QPoint mOffset; + std::optional mScale = std::nullopt; + + QString mWindowTitle; + QIcon mWindowIcon; + + Qt::WindowFlags mFlags; + QRegion mMask; + + // Empty QRegion maps to "infinite" input region, needs a dedicated "deliberately empty" state. + QRegion mInputRegion; + bool mTransparentInputRegion = false; + + QRegion mOpaqueArea; + Qt::WindowStates mLastReportedWindowStates = Qt::WindowNoState; + ToplevelWindowTilingStates mLastReportedToplevelWindowTilingStates = WindowNoState; + + QWaylandShmBackingStore *mBackingStore = nullptr; + + QMargins mCustomMargins; + + QPointer mTransientParent; + QList> mChildPopups; + + Qt::ScreenOrientation mLastReportedContentOrientation = Qt::PrimaryOrientation; + + std::unique_ptr mColorManagementSurface; + QSurfaceFormat mSurfaceFormat; + +private: + void setGeometry_helper(const QRect &rect); + void initWindow(); + bool shouldCreateShellSurface() const; + bool shouldCreateSubSurface() const; + void resetSurfaceRole(); + void resetFrameCallback(); + QPlatformScreen *calculateScreenFromSurfaceEvents() const; + void setOpaqueArea(const QRegion &opaqueArea); + bool isOpaque() const; + void updateInputRegion(); + void updateViewport(); + bool calculateExposure() const; + void setPendingImageDescription(); + + void handleMouseEventWithDecoration(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e); + void handleScreensChanged(); + void updateScale(); + void setScale(qreal newScale); + + QWaylandWindow *guessTransientParent() const; + void addChildPopup(QWaylandWindow *child); + void removeChildPopup(QWaylandWindow *child); + + bool mInResizeFromApplyConfigure = false; + bool lastVisible = false; + QRect mLastExposeGeometry; + std::unique_ptr mPendingImageDescription; + + static const wl_callback_listener callbackListener; + void handleFrameCallback(struct ::wl_callback* callback); + const QPlatformWindow *lastParent = nullptr; + + static QWaylandWindow *mMouseGrab; + static QWaylandWindow *mTopPopup; + + friend class QWaylandSubSurface; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QWaylandWindow::ToplevelWindowTilingStates) + +inline QIcon QWaylandWindow::windowIcon() const +{ + return mWindowIcon; +} + +inline QPoint QWaylandWindow::attachOffset() const +{ + return mOffset; +} + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDWINDOW_H diff --git a/src/plugins/platforms/wayland/qwaylandwindowmanagerintegration.cpp b/src/plugins/platforms/wayland/qwaylandwindowmanagerintegration.cpp new file mode 100644 index 00000000000..9668491d2f3 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandwindowmanagerintegration.cpp @@ -0,0 +1,69 @@ +// Copyright (C) 2016 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 + +#include "qwaylandwindowmanagerintegration_p.h" +#include "qwaylandscreen_p.h" +#include "qwaylandwindow_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandshellsurface_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandWindowManagerIntegration::QWaylandWindowManagerIntegration(QWaylandDisplay *waylandDisplay, + uint id, uint version) + : QtWayland::qt_windowmanager(waylandDisplay->object(), id, version) +{ +} + +QWaylandWindowManagerIntegration::~QWaylandWindowManagerIntegration() +{ + qt_windowmanager_destroy(object()); +} + +bool QWaylandWindowManagerIntegration::showIsFullScreen() const +{ + return m_showIsFullScreen; +} + +void QWaylandWindowManagerIntegration::windowmanager_hints(int32_t showIsFullScreen) +{ + m_showIsFullScreen = showIsFullScreen; +} + +void QWaylandWindowManagerIntegration::windowmanager_quit() +{ + QGuiApplication::quit(); +} + +void QWaylandWindowManagerIntegration::openUrl(const QUrl &url) +{ + QString data = url.toString(); + static const int chunkSize = 128; + while (!data.isEmpty()) { + QString chunk = data.left(chunkSize); + data = data.mid(chunkSize); + if (chunk.at(chunk.size() - 1).isHighSurrogate() && !data.isEmpty()) { + chunk.append(data.at(0)); + data = data.mid(1); + } + open_url(!data.isEmpty(), chunk); + } +} +} + +QT_END_NAMESPACE + +#include "moc_qwaylandwindowmanagerintegration_p.cpp" diff --git a/src/plugins/platforms/wayland/qwaylandwindowmanagerintegration_p.h b/src/plugins/platforms/wayland/qwaylandwindowmanagerintegration_p.h new file mode 100644 index 00000000000..be06d68ee57 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandwindowmanagerintegration_p.h @@ -0,0 +1,54 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDWINDOWMANAGERINTEGRATION_H +#define QWAYLANDWINDOWMANAGERINTEGRATION_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class QWaylandWindowManagerIntegrationPrivate; + +class Q_WAYLANDCLIENT_EXPORT QWaylandWindowManagerIntegration : public QtWayland::qt_windowmanager +{ + +public: + explicit QWaylandWindowManagerIntegration(QWaylandDisplay *waylandDisplay, uint id, + uint version); + ~QWaylandWindowManagerIntegration(); + + void openUrl(const QUrl &url); + + bool showIsFullScreen() const; + +private: + void windowmanager_hints(int32_t showIsFullScreen) override; + void windowmanager_quit() override; + + bool m_showIsFullScreen = false; +}; + +QT_END_NAMESPACE + +} + +#endif // QWAYLANDWINDOWMANAGERINTEGRATION_H diff --git a/src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder.cpp b/src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder.cpp new file mode 100644 index 00000000000..fc422ef04aa --- /dev/null +++ b/src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder.cpp @@ -0,0 +1,332 @@ +// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandinputmethodeventbuilder_p.h" + +#include +#include +#include +#include +#include + +#ifdef QT_BUILD_WAYLANDCOMPOSITOR_LIB +#include +#include +#else +#include +#include +#endif + +QT_BEGIN_NAMESPACE + +QWaylandInputMethodEventBuilder::~QWaylandInputMethodEventBuilder() +{ +} + +void QWaylandInputMethodEventBuilder::reset() +{ + m_anchor = 0; + m_cursor = 0; + m_deleteBefore = 0; + m_deleteAfter = 0; + m_preeditCursor = 0; + m_preeditStyles.clear(); +} + +void QWaylandInputMethodEventBuilder::setCursorPosition(int32_t index, int32_t anchor) +{ + m_cursor = index; + m_anchor = anchor; +} + +void QWaylandInputMethodEventBuilder::setDeleteSurroundingText(uint32_t beforeLength, uint32_t afterLength) +{ + m_deleteBefore = beforeLength; + m_deleteAfter = afterLength; +} + +void QWaylandInputMethodEventBuilder::addPreeditStyling(uint32_t index, uint32_t length, uint32_t style) +{ + QTextCharFormat format; + + switch (style) { + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_NONE: + break; + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_DEFAULT: + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_UNDERLINE: + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); + break; + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_ACTIVE: + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_INACTIVE: + format.setFontWeight(QFont::Bold); + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); + break; + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_HIGHLIGHT: + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_SELECTION: + { + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + QPalette palette = qApp->palette(); + format.setBackground(QBrush(palette.color(QPalette::Active, QPalette::Highlight))); + format.setForeground(QBrush(palette.color(QPalette::Active, QPalette::HighlightedText))); + m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); + } + break; + case ZWP_TEXT_INPUT_V2_PREEDIT_STYLE_INCORRECT: + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::WaveUnderline); + format.setUnderlineColor(QColor(Qt::red)); + m_preeditStyles.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, index, length, format)); + break; + default: + break; + } +} + +void QWaylandInputMethodEventBuilder::setPreeditCursor(int32_t index) +{ + m_preeditCursor = index; +} + +QInputMethodEvent *QWaylandInputMethodEventBuilder::buildCommit(const QString &text) +{ + QList attributes; + + const QPair replacement = replacementForDeleteSurrounding(); + + if (m_cursor != 0 || m_anchor != 0) { + QString surrounding = QInputMethod::queryFocusObject(Qt::ImSurroundingText, QVariant()).toString(); + const int cursor = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant()).toInt(); + const int anchor = QInputMethod::queryFocusObject(Qt::ImAnchorPosition, QVariant()).toInt(); + const int absoluteCursor = QInputMethod::queryFocusObject(Qt::ImAbsolutePosition, QVariant()).toInt(); + + const int absoluteOffset = absoluteCursor - cursor; + + const int cursorAfterCommit = qMin(anchor, cursor) + replacement.first + text.size(); + surrounding.replace(qMin(anchor, cursor) + replacement.first, + qAbs(anchor - cursor) + replacement.second, text); + + attributes.push_back(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, + indexFromWayland(surrounding, m_cursor, cursorAfterCommit) + absoluteOffset, + indexFromWayland(surrounding, m_anchor, cursorAfterCommit) + absoluteOffset, + QVariant())); + } + + QInputMethodEvent *event = new QInputMethodEvent(QString(), attributes); + event->setCommitString(text, replacement.first, replacement.second); + + return event; +} + +QInputMethodEvent *QWaylandInputMethodEventBuilder::buildPreedit(const QString &text) +{ + QList attributes; + + if (m_preeditCursor < 0) { + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant())); + } else { + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, indexFromWayland(text, m_preeditCursor), 1, QVariant())); + } + + for (const QInputMethodEvent::Attribute &attr : std::as_const(m_preeditStyles)) { + int start = indexFromWayland(text, attr.start); + int length = indexFromWayland(text, attr.start + attr.length) - start; + attributes.append(QInputMethodEvent::Attribute(attr.type, start, length, attr.value)); + } + + QInputMethodEvent *event = new QInputMethodEvent(text, attributes); + + const QPair replacement = replacementForDeleteSurrounding(); + event->setCommitString(QString(), replacement.first, replacement.second); + + return event; +} + +QPair QWaylandInputMethodEventBuilder::replacementForDeleteSurrounding() +{ + if (m_deleteBefore == 0 && m_deleteAfter == 0) + return QPair(0, 0); + + const QString &surrounding = QInputMethod::queryFocusObject(Qt::ImSurroundingText, QVariant()).toString(); + const int cursor = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant()).toInt(); + const int anchor = QInputMethod::queryFocusObject(Qt::ImAnchorPosition, QVariant()).toInt(); + + const int selectionStart = qMin(cursor, anchor); + const int selectionEnd = qMax(cursor, anchor); + + const int deleteBefore = selectionStart - indexFromWayland(surrounding, -m_deleteBefore, selectionStart); + const int deleteAfter = indexFromWayland(surrounding, m_deleteAfter, selectionEnd) - selectionEnd; + + return QPair(-deleteBefore, deleteBefore + deleteAfter); +} + +QWaylandInputMethodContentType QWaylandInputMethodContentType::convert(Qt::InputMethodHints hints) +{ + uint32_t hint = ZWP_TEXT_INPUT_V2_CONTENT_HINT_NONE; + uint32_t purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NORMAL; + + if (hints & Qt::ImhHiddenText) + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_HIDDEN_TEXT; + if (hints & Qt::ImhSensitiveData) + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_SENSITIVE_DATA; + if ((hints & Qt::ImhNoAutoUppercase) == 0) + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_CAPITALIZATION; + if (hints & Qt::ImhPreferNumbers) { + // Nothing yet + } + if (hints & Qt::ImhPreferUppercase) + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_UPPERCASE; + if (hints & Qt::ImhPreferLowercase) + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LOWERCASE; + if ((hints & Qt::ImhNoPredictiveText) == 0) { + hint |= (ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_COMPLETION + | ZWP_TEXT_INPUT_V2_CONTENT_HINT_AUTO_CORRECTION); + } + + if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime) == 0) + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DATE; + else if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime)) + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DATETIME; + else if ((hints & Qt::ImhDate) == 0 && (hints & Qt::ImhTime)) + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_TIME; + + if (hints & Qt::ImhPreferLatin) + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LATIN; + if (hints & Qt::ImhMultiLine) + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_MULTILINE; + if (hints & Qt::ImhDigitsOnly) + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_DIGITS; + if (hints & Qt::ImhFormattedNumbersOnly) + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_NUMBER; + if (hints & Qt::ImhUppercaseOnly) + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_UPPERCASE; + if (hints & Qt::ImhLowercaseOnly) + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LOWERCASE; + if (hints & Qt::ImhDialableCharactersOnly) + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_PHONE; + if (hints & Qt::ImhEmailCharactersOnly) + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_EMAIL; + if (hints & Qt::ImhUrlCharactersOnly) + purpose = ZWP_TEXT_INPUT_V2_CONTENT_PURPOSE_URL; + if (hints & Qt::ImhLatinOnly) + hint |= ZWP_TEXT_INPUT_V2_CONTENT_HINT_LATIN; + + return QWaylandInputMethodContentType{hint, purpose}; +} + +QWaylandInputMethodContentType QWaylandInputMethodContentType::convertV3(Qt::InputMethodHints hints) +{ + uint32_t hint = ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE; + uint32_t purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL; + + if (hints & Qt::ImhHiddenText) + hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT; + if (hints & Qt::ImhSensitiveData) + hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA; + if ((hints & Qt::ImhNoAutoUppercase) == 0) + hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION; + if (hints & Qt::ImhPreferNumbers) { + // Nothing yet + } + if (hints & Qt::ImhPreferUppercase) + hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE; + if (hints & Qt::ImhPreferLowercase) + hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE; + if ((hints & Qt::ImhNoPredictiveText) == 0) { + hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION + | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK); + } + + if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime) == 0) + purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE; + else if ((hints & Qt::ImhDate) && (hints & Qt::ImhTime)) + purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME; + else if ((hints & Qt::ImhDate) == 0 && (hints & Qt::ImhTime)) + purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME; + if (hints & Qt::ImhPreferLatin) + hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN; + if (hints & Qt::ImhMultiLine) + hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE; + if (hints & Qt::ImhDigitsOnly) + purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS; + if (hints & Qt::ImhFormattedNumbersOnly) + purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER; + if (hints & Qt::ImhUppercaseOnly) + hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE; + if (hints & Qt::ImhLowercaseOnly) + hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE; + if (hints & Qt::ImhDialableCharactersOnly) + purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE; + if (hints & Qt::ImhEmailCharactersOnly) + purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL; + if (hints & Qt::ImhUrlCharactersOnly) + purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL; + if (hints & Qt::ImhLatinOnly) + hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN; + + return QWaylandInputMethodContentType{hint, purpose}; +} + +int QWaylandInputMethodEventBuilder::indexFromWayland(const QString &text, int length, int base) +{ + if (length == 0) + return base; + + if (length < 0) { + const QByteArray &utf8 = QStringView{text}.left(base).toUtf8(); + return QString::fromUtf8(utf8.first(qMax(utf8.size() + length, 0))).size(); + } else { + const QByteArray &utf8 = QStringView{text}.mid(base).toUtf8(); + return QString::fromUtf8(utf8.first(qMin(length, utf8.size()))).size() + base; + } +} + +int QWaylandInputMethodEventBuilder::trimmedIndexFromWayland(const QString &text, int length, int base) +{ + if (length == 0) + return base; + + if (length < 0) { + const QByteArray &utf8 = QStringView{text}.left(base).toUtf8(); + const int len = utf8.size(); + const int start = len + length; + if (start <= 0) + return 0; + + for (int i = 0; i < 4; i++) { + if (start + i >= len) + return base; + + const unsigned char ch = utf8.at(start + i); + // check if current character is a utf8's initial character. + if (ch < 0x80 || ch > 0xbf) + return QString::fromUtf8(utf8.left(start + i)).size(); + } + } else { + const QByteArray &utf8 = QStringView{text}.mid(base).toUtf8(); + const int len = utf8.size(); + const int start = length; + if (start >= len) + return base + QString::fromUtf8(utf8).size(); + + for (int i = 0; i < 4; i++) { + const unsigned char ch = utf8.at(start - i); + // check if current character is a utf8's initial character. + if (ch < 0x80 || ch > 0xbf) + return base + QString::fromUtf8(utf8.left(start - i)).size(); + } + } + return -1; +} + +int QWaylandInputMethodEventBuilder::indexToWayland(const QString &text, int length, int base) +{ + return QStringView{text}.mid(base, length).toUtf8().size(); +} + +QT_END_NAMESPACE + diff --git a/src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder_p.h b/src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder_p.h new file mode 100644 index 00000000000..4ab5c746f3d --- /dev/null +++ b/src/plugins/platforms/wayland/shared/qwaylandinputmethodeventbuilder_p.h @@ -0,0 +1,67 @@ +// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDINPUTMETHODEVENTBUILDER_H +#define QWAYLANDINPUTMETHODEVENTBUILDER_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 + +QT_BEGIN_NAMESPACE + +class QWaylandInputMethodEventBuilder +{ +public: + QWaylandInputMethodEventBuilder() = default; + ~QWaylandInputMethodEventBuilder(); + + void reset(); + + void setCursorPosition(int32_t index, int32_t anchor); + void setDeleteSurroundingText(uint32_t beforeLength, uint32_t afterLength); + + void addPreeditStyling(uint32_t index, uint32_t length, uint32_t style); + void setPreeditCursor(int32_t index); + + QInputMethodEvent *buildCommit(const QString &text); + QInputMethodEvent *buildPreedit(const QString &text); + + static int indexFromWayland(const QString &text, int length, int base = 0); + static int indexToWayland(const QString &text, int length, int base = 0); + + static int trimmedIndexFromWayland(const QString &text, int length, int base = 0); +private: + QPair replacementForDeleteSurrounding(); + + int32_t m_anchor = 0; + int32_t m_cursor = 0; + uint32_t m_deleteBefore = 0; + uint32_t m_deleteAfter = 0; + + int32_t m_preeditCursor = 0; + QList m_preeditStyles; +}; + +struct QWaylandInputMethodContentType { + uint32_t hint = 0; + uint32_t purpose = 0; + + static QWaylandInputMethodContentType convert(Qt::InputMethodHints hints); + static QWaylandInputMethodContentType convertV3(Qt::InputMethodHints hints); +}; + + +QT_END_NAMESPACE + +#endif // QWAYLANDINPUTMETHODEVENTBUILDER_H diff --git a/src/plugins/platforms/wayland/shared/qwaylandmimehelper.cpp b/src/plugins/platforms/wayland/shared/qwaylandmimehelper.cpp new file mode 100644 index 00000000000..3bbbad97b0b --- /dev/null +++ b/src/plugins/platforms/wayland/shared/qwaylandmimehelper.cpp @@ -0,0 +1,49 @@ +// Copyright (C) 2016 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 + +#include "qwaylandmimehelper_p.h" +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QByteArray QWaylandMimeHelper::getByteArray(QMimeData *mimeData, const QString &mimeType) +{ + QByteArray content; + if (mimeType == QLatin1String("text/plain")) { + content = mimeData->text().toUtf8(); + } else if (mimeData->hasImage() + && (mimeType == QLatin1String("application/x-qt-image") + || mimeType.startsWith(QLatin1String("image/")))) { + QImage image = qvariant_cast(mimeData->imageData()); + if (!image.isNull()) { + QBuffer buf; + buf.open(QIODevice::ReadWrite); + QByteArray fmt = "BMP"; + if (mimeType.startsWith(QLatin1String("image/"))) { + QByteArray imgFmt = mimeType.mid(6).toLower().toLatin1(); + if (QImageWriter::supportedImageFormats().contains(imgFmt)) + fmt = imgFmt; + } + QImageWriter wr(&buf, fmt); + wr.write(image); + content = buf.buffer(); + } + } else if (mimeType == QLatin1String("application/x-color")) { + content = qvariant_cast(mimeData->colorData()).name().toLatin1(); + } else if (mimeType == QLatin1String("text/uri-list")) { + QList urls = mimeData->urls(); + for (int i = 0; i < urls.size(); ++i) { + content.append(urls.at(i).toEncoded()); + content.append("\r\n"); + } + } else { + content = mimeData->data(mimeType); + } + return content; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/shared/qwaylandmimehelper_p.h b/src/plugins/platforms/wayland/shared/qwaylandmimehelper_p.h new file mode 100644 index 00000000000..3b13ca2cdb7 --- /dev/null +++ b/src/plugins/platforms/wayland/shared/qwaylandmimehelper_p.h @@ -0,0 +1,33 @@ +// Copyright (C) 2016 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 + +#ifndef QWAYLANDMIMEHELPER_H +#define QWAYLANDMIMEHELPER_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 + +QT_BEGIN_NAMESPACE + +class QWaylandMimeHelper +{ +public: + static QByteArray getByteArray(QMimeData *mimeData, const QString &mimeType); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wayland/shared/qwaylandsharedmemoryformathelper_p.h b/src/plugins/platforms/wayland/shared/qwaylandsharedmemoryformathelper_p.h new file mode 100644 index 00000000000..5af9b3f4f71 --- /dev/null +++ b/src/plugins/platforms/wayland/shared/qwaylandsharedmemoryformathelper_p.h @@ -0,0 +1,114 @@ +// Copyright (C) 2020 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 + +#ifndef QWAYLANDSHAREDMEMORYFORMATHELPER_H +#define QWAYLANDSHAREDMEMORYFORMATHELPER_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 + +//the correct protocol header for the wayland server or wayland client has to be +//included before this file is included + +QT_BEGIN_NAMESPACE + +class QWaylandSharedMemoryFormatHelper +{ +public: + static inline wl_shm_format fromQImageFormat(QImage::Format format); + static inline QImage::Format fromWaylandShmFormat(wl_shm_format format) + { + switch (format) { + case WL_SHM_FORMAT_XRGB8888: return QImage::Format_RGB32; + case WL_SHM_FORMAT_ARGB8888: return QImage::Format_ARGB32_Premultiplied; + case WL_SHM_FORMAT_RGB565: return QImage::Format_RGB16; + case WL_SHM_FORMAT_XRGB1555: return QImage::Format_RGB555; + case WL_SHM_FORMAT_RGB888: return QImage::Format_RGB888; + case WL_SHM_FORMAT_BGR888: return QImage::Format_BGR888; + case WL_SHM_FORMAT_XRGB4444: return QImage::Format_RGB444; + case WL_SHM_FORMAT_ARGB4444: return QImage::Format_ARGB4444_Premultiplied; + case WL_SHM_FORMAT_XBGR8888: return QImage::Format_RGBX8888; + case WL_SHM_FORMAT_ABGR8888: return QImage::Format_RGBA8888_Premultiplied; + case WL_SHM_FORMAT_XBGR2101010: return QImage::Format_BGR30; + case WL_SHM_FORMAT_ABGR2101010: return QImage::Format_A2BGR30_Premultiplied; + case WL_SHM_FORMAT_XRGB2101010: return QImage::Format_RGB30; + case WL_SHM_FORMAT_ARGB2101010: return QImage::Format_A2RGB30_Premultiplied; + case WL_SHM_FORMAT_C8: return QImage::Format_Alpha8; + default: return QImage::Format_Invalid; + } + } + +private: +//IMPLEMENTATION (which has to be inline in the header because of the include trick) + struct Array + { + Array(const size_t size, const wl_shm_format *data) + : size(size) + , data(data) + { } + const size_t size; + const wl_shm_format *data = nullptr; + }; + + static const Array getData() + { + static wl_shm_format formats_array[] = { + wl_shm_format(INT_MIN), //Format_Invalid, + wl_shm_format(INT_MIN), //Format_Mono, + wl_shm_format(INT_MIN), //Format_MonoLSB, + wl_shm_format(INT_MIN), //Format_Indexed8, + WL_SHM_FORMAT_XRGB8888, //Format_RGB32, + WL_SHM_FORMAT_ARGB8888, //Format_ARGB32, + WL_SHM_FORMAT_ARGB8888, //Format_ARGB32_Premultiplied, + WL_SHM_FORMAT_RGB565, //Format_RGB16, + wl_shm_format(INT_MIN), //Format_ARGB8565_Premultiplied, + wl_shm_format(INT_MIN), //Format_RGB666, + wl_shm_format(INT_MIN), //Format_ARGB6666_Premultiplied, + WL_SHM_FORMAT_XRGB1555, //Format_RGB555, + wl_shm_format(INT_MIN), //Format_ARGB8555_Premultiplied, + WL_SHM_FORMAT_RGB888, //Format_RGB888, + WL_SHM_FORMAT_XRGB4444, //Format_RGB444, + WL_SHM_FORMAT_ARGB4444, //Format_ARGB4444_Premultiplied, + WL_SHM_FORMAT_XBGR8888, //Format_RGBX8888, + WL_SHM_FORMAT_ABGR8888, //Format_RGBA8888, + WL_SHM_FORMAT_ABGR8888, //Format_RGBA8888_Premultiplied, + WL_SHM_FORMAT_XBGR2101010, //Format_BGR30, + WL_SHM_FORMAT_ABGR2101010, //Format_A2BGR30_Premultiplied, + WL_SHM_FORMAT_XRGB2101010, //Format_RGB30, + WL_SHM_FORMAT_ARGB2101010, //Format_A2RGB30_Premultiplied, + WL_SHM_FORMAT_C8, //Format_Alpha8, + WL_SHM_FORMAT_C8, //Format_Grayscale8, + wl_shm_format(INT_MIN), //Format_RGBX64, + wl_shm_format(INT_MIN), //Format_RGBA64, + wl_shm_format(INT_MIN), //Format_RGBA64_Premultiplied, + wl_shm_format(INT_MIN), //Format_Grayscale16, + WL_SHM_FORMAT_BGR888, //Format_BGR888 + + }; + const size_t size = sizeof(formats_array) / sizeof(*formats_array); + return Array(size, formats_array); + } +}; + +wl_shm_format QWaylandSharedMemoryFormatHelper::fromQImageFormat(QImage::Format format) +{ + Array array = getData(); + if (array.size <= size_t(format)) + return wl_shm_format(INT_MIN); + return array.data[format]; +} + +QT_END_NAMESPACE + +#endif //QWAYLANDSHAREDMEMORYFORMATHELPER_H diff --git a/src/plugins/platforms/wayland/shellintegration/qwaylandclientshellapi_p.h b/src/plugins/platforms/wayland/shellintegration/qwaylandclientshellapi_p.h new file mode 100644 index 00000000000..984435aa932 --- /dev/null +++ b/src/plugins/platforms/wayland/shellintegration/qwaylandclientshellapi_p.h @@ -0,0 +1,34 @@ +// Copyright (C) 2021 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 + +#ifndef QWAYLANDCLIENTSHELLAPI_P_H +#define QWAYLANDCLIENTSHELLAPI_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. +// + +// N O T E +// ------- +// This file provides a supported API for creating client-side shell +// extensions. Source compatibility will be preserved, but we may break +// forward and backward binary compatibility, even in patch releases. +// +// The supported API contains these classes: +// +// QtWaylandClient::QWaylandShellSurface +// QtWaylandClient::QWaylandShellIntegration +// QtWaylandClient::QWaylandShellIntegrationPlugin + +#include "QtWaylandClient/private/qwaylandshellsurface_p.h" +#include "QtWaylandClient/private/qwaylandshellintegration_p.h" +#include "QtWaylandClient/private/qwaylandshellintegrationplugin_p.h" + +#endif // QWAYLANDCLIENTSHELLAPI_P_H diff --git a/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegration.cpp b/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegration.cpp new file mode 100644 index 00000000000..0cefa8a7c25 --- /dev/null +++ b/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegration.cpp @@ -0,0 +1,21 @@ +// Copyright (C) 2021 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 +#include "qwaylandshellintegration_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandShellIntegration::~QWaylandShellIntegration() + = default; // MUST stay empty until Qt 7 (was inline in Qt < 6.9) + +wl_surface *QWaylandShellIntegration::wlSurfaceForWindow(QWaylandWindow *window) +{ + return window->wlSurface(); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegration_p.h b/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegration_p.h new file mode 100644 index 00000000000..44c8cdb444b --- /dev/null +++ b/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegration_p.h @@ -0,0 +1,102 @@ +// Copyright (C) 2016 Jolla Ltd +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDSHELLINTEGRATION_H +#define QWAYLANDSHELLINTEGRATION_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 + +struct wl_surface; +struct wl_registry; + +QT_BEGIN_NAMESPACE + +class QWindow; + +namespace QtWaylandClient { + +class QWaylandWindow; +class QWaylandDisplay; +class QWaylandShellSurface; + +class Q_WAYLANDCLIENT_EXPORT QWaylandShellIntegration +{ +public: + QWaylandShellIntegration() {} + virtual ~QWaylandShellIntegration(); + + virtual bool initialize(QWaylandDisplay *display) = 0; + virtual QWaylandShellSurface *createShellSurface(QWaylandWindow *window) = 0; + virtual void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) { + Q_UNUSED(resource); + Q_UNUSED(window); + return nullptr; + } + + static wl_surface *wlSurfaceForWindow(QWaylandWindow *window); + +}; + +template +class Q_WAYLANDCLIENT_EXPORT QWaylandShellIntegrationTemplate + : public QWaylandClientExtension, + public QWaylandShellIntegration +{ +public: + QWaylandShellIntegrationTemplate(const int ver) : + QWaylandClientExtension(ver) + { + } + + bool initialize(QWaylandDisplay *) override + { + QWaylandClientExtension::initialize(); + return isActive(); + } + + const struct wl_interface *extensionInterface() const override + { + return T::interface(); + } + + void bind(struct ::wl_registry *registry, int id, int ver) override + { + T* instance = static_cast(this); + // Make sure lowest version is used of the supplied version from the + // developer and the version specified in the protocol and also the + // compositor version. + if (this->version() > T::interface()->version) { + qWarning("Supplied protocol version to QWaylandClientExtensionTemplate is higher " + "than the version of the protocol, using protocol version instead.\n" + " interface.name: %s", + T::interface()->name); + } + int minVersion = qMin(ver, qMin(T::interface()->version, this->version())); + setVersion(minVersion); + instance->init(registry, id, minVersion); + } +}; + + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDSHELLINTEGRATION_H diff --git a/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegrationfactory.cpp b/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegrationfactory.cpp new file mode 100644 index 00000000000..feedb27c516 --- /dev/null +++ b/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegrationfactory.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2016 Jolla Ltd +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandshellintegrationfactory_p.h" +#include "qwaylandshellintegrationplugin_p.h" +#include "qwaylandshellintegration_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, qwsifLoader, + (QWaylandShellIntegrationFactoryInterface_iid, QLatin1String("/wayland-shell-integration"), Qt::CaseInsensitive)) + +QStringList QWaylandShellIntegrationFactory::keys() +{ + return qwsifLoader->keyMap().values(); +} + +QWaylandShellIntegration *QWaylandShellIntegrationFactory::create(const QString &name, QWaylandDisplay *display, const QStringList &args) +{ + std::unique_ptr integration; + integration.reset(qLoadPlugin(qwsifLoader(), name, args)); + + if (integration && !integration->initialize(display)) + return nullptr; + + return integration.release(); +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegrationfactory_p.h b/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegrationfactory_p.h new file mode 100644 index 00000000000..edc0e70c77e --- /dev/null +++ b/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegrationfactory_p.h @@ -0,0 +1,41 @@ +// Copyright (C) 2016 Jolla Ltd +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDSHELLINTEGRATIONFACTORY_H +#define QWAYLANDSHELLINTEGRATIONFACTORY_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandShellIntegration; + +class Q_WAYLANDCLIENT_EXPORT QWaylandShellIntegrationFactory +{ +public: + static QStringList keys(); + static QWaylandShellIntegration *create(const QString &name, QWaylandDisplay *display, const QStringList &args = QStringList()); +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDSHELLINTEGRATIONFACTORY_H diff --git a/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegrationplugin.cpp b/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegrationplugin.cpp new file mode 100644 index 00000000000..0e233828275 --- /dev/null +++ b/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegrationplugin.cpp @@ -0,0 +1,23 @@ +// Copyright (C) 2016 Jolla Ltd +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandshellintegrationplugin_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandShellIntegrationPlugin::QWaylandShellIntegrationPlugin(QObject *parent) + : QObject(parent) +{ +} + +QWaylandShellIntegrationPlugin::~QWaylandShellIntegrationPlugin() +{ +} + +} + +QT_END_NAMESPACE + +#include "moc_qwaylandshellintegrationplugin_p.cpp" diff --git a/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegrationplugin_p.h b/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegrationplugin_p.h new file mode 100644 index 00000000000..85339e1e7f9 --- /dev/null +++ b/src/plugins/platforms/wayland/shellintegration/qwaylandshellintegrationplugin_p.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 Jolla Ltd +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDSHELLINTEGRATIONPLUGIN_H +#define QWAYLANDSHELLINTEGRATIONPLUGIN_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 + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandShellIntegration; + +#define QWaylandShellIntegrationFactoryInterface_iid "org.qt-project.Qt.WaylandClient.QWaylandShellIntegrationFactoryInterface.5.3" + +class Q_WAYLANDCLIENT_EXPORT QWaylandShellIntegrationPlugin : public QObject +{ + Q_OBJECT +public: + explicit QWaylandShellIntegrationPlugin(QObject *parent = nullptr); + ~QWaylandShellIntegrationPlugin() override; + + virtual QWaylandShellIntegration *create(const QString &key, const QStringList ¶mList) = 0; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDSHELLINTEGRATIONPLUGIN_H diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index df0d2c7016b..1ff52f8a84f 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -31,3 +31,7 @@ endif() if(QT_FEATURE_windeployqt) add_subdirectory(windeployqt) endif() + +if(QT_FEATURE_qtwaylandscanner) + add_subdirectory(qtwaylandscanner) +endif() diff --git a/src/tools/configure.cmake b/src/tools/configure.cmake index 618dc09f3b5..46d538726b3 100644 --- a/src/tools/configure.cmake +++ b/src/tools/configure.cmake @@ -1,6 +1,10 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause +if(LINUX OR QT_FIND_ALL_PACKAGES_ALWAYS) + qt_find_package(WaylandScanner PROVIDED_TARGETS Wayland::Scanner) +endif() + qt_feature("androiddeployqt" PRIVATE SECTION "Deployment" LABEL "Android deployment tool" @@ -26,9 +30,17 @@ qt_feature("qmake" PRIVATE CONDITION QT_FEATURE_settings AND QT_FEATURE_cborstreamwriter AND QT_FEATURE_datestring AND QT_FEATURE_regularexpression AND QT_FEATURE_temporaryfile) +qt_feature("qtwaylandscanner" PRIVATE + CONDITION TARGET Wayland::Scanner AND TARGET Wayland::Client +) + qt_configure_add_summary_section(NAME "Core tools") qt_configure_add_summary_entry(ARGS "androiddeployqt") qt_configure_add_summary_entry(ARGS "macdeployqt") qt_configure_add_summary_entry(ARGS "windeployqt") qt_configure_add_summary_entry(ARGS "qmake") qt_configure_end_summary_section() + +qt_configure_add_summary_section(NAME "Wayland tools") +qt_configure_add_summary_entry(ARGS "qtwaylandscanner") +qt_configure_end_summary_section() diff --git a/src/tools/qtwaylandscanner/CMakeLists.txt b/src/tools/qtwaylandscanner/CMakeLists.txt new file mode 100644 index 00000000000..95f4b1f961e --- /dev/null +++ b/src/tools/qtwaylandscanner/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from qtwaylandscanner.pro. + +##################################################################### +## qtwaylandscanner Tool: +##################################################################### + +qt_get_tool_target_name(target_name qtwaylandscanner) +qt_internal_add_tool(${target_name} + TOOLS_TARGET WaylandScanner # special case + INSTALL_DIR "${INSTALL_LIBEXECDIR}" + SOURCES + qtwaylandscanner.cpp + EXTRA_CMAKE_FILES + "${CMAKE_CURRENT_SOURCE_DIR}/Qt6WaylandClientMacros.cmake" + "${CMAKE_CURRENT_SOURCE_DIR}/Qt6WaylandCompositorMacros.cmake" + EXTRA_CMAKE_INCLUDES + "Qt6WaylandClientMacros.cmake" + "Qt6WaylandCompositorMacros.cmake" +) +qt_internal_return_unless_building_tools() + +#### Keys ignored in scope 1:.:.:qtwaylandscanner.pro:: +# _OPTION = "host_build" + +# special case begin +# Abuse the function to make sure the package providing qtwaylandscanner calls +# find_package(WaylandScanner), aka the non-qt provided package. +qt_record_extra_package_dependency(qtwaylandscanner WaylandScanner "") +# special case end diff --git a/src/tools/qtwaylandscanner/Qt6WaylandClientMacros.cmake b/src/tools/qtwaylandscanner/Qt6WaylandClientMacros.cmake new file mode 100644 index 00000000000..b4266c5581e --- /dev/null +++ b/src/tools/qtwaylandscanner/Qt6WaylandClientMacros.cmake @@ -0,0 +1,119 @@ + +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +function(qt6_generate_wayland_protocol_client_sources target) + cmake_parse_arguments(arg + "NO_INCLUDE_CORE_ONLY;PRIVATE_CODE;PUBLIC_CODE" + "__QT_INTERNAL_WAYLAND_INCLUDE_DIR" + "FILES" + ${ARGN}) + + if(DEFINED arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown arguments were passed to qt6_generate_wayland_protocol_client_sources: (${arg_UNPARSED_ARGUMENTS}).") + endif() + + get_target_property(target_binary_dir ${target} BINARY_DIR) + + if(NOT TARGET Wayland::Scanner) + message(FATAL_ERROR "Wayland::Scanner target not found. You might be missing the WaylandScanner CMake package.") + endif() + + if(NOT TARGET Qt6::qtwaylandscanner) + message(FATAL_ERROR "qtwaylandscanner executable not found. Most likely there is an issue with your Qt installation.") + endif() + + string(TOUPPER "${target}" module_define_infix) + string(REPLACE "-" "_" module_define_infix "${module_define_infix}") + string(REPLACE "." "_" module_define_infix "${module_define_infix}") + set(build_macro "QT_BUILD_${module_define_infix}_LIB") + + foreach(protocol_file IN LISTS arg_FILES) + get_filename_component(protocol_name "${protocol_file}" NAME_WLE) + + set(waylandscanner_header_output "${target_binary_dir}/wayland-${protocol_name}-client-protocol.h") + set(waylandscanner_code_output "${target_binary_dir}/wayland-${protocol_name}-protocol.c") + # TODO: Maybe add "client" prefix or suffix to these in Qt6? + set(qtwaylandscanner_header_output "${target_binary_dir}/qwayland-${protocol_name}.h") + set(qtwaylandscanner_code_output "${target_binary_dir}/qwayland-${protocol_name}.cpp") + + if (NOT arg_NO_INCLUDE_CORE_ONLY) + set(waylandscanner_extra_args "--include-core-only") + endif() + + + if (arg_PRIVATE_CODE) + set(wayland_scanner_code_option "private-code") + else() + set(wayland_scanner_code_option "public-code") + endif() + + add_custom_command( + OUTPUT "${waylandscanner_header_output}" + #TODO: Maybe put the files in ${CMAKE_CURRENT_BINARY_DIR/wayland_generated instead? + COMMAND Wayland::Scanner ${waylandscanner_extra_args} client-header < "${protocol_file}" > "${waylandscanner_header_output}" + DEPENDS ${protocol_file} Wayland::Scanner + ) + + add_custom_command( + OUTPUT "${waylandscanner_code_output}" + COMMAND Wayland::Scanner ${waylandscanner_extra_args} ${wayland_scanner_code_option} < "${protocol_file}" > "${waylandscanner_code_output}" + DEPENDS ${protocol_file} Wayland::Scanner + ) + + set(wayland_include_dir "") + if(arg___QT_INTERNAL_WAYLAND_INCLUDE_DIR) + set(wayland_include_dir "${arg___QT_INTERNAL_WAYLAND_INCLUDE_DIR}") + else() + get_target_property(qt_module ${target} _qt_module_interface_name) + get_target_property(is_for_module "${target}" _qt_module_has_headers) + if (qt_module) + set(wayland_include_dir "Qt${qt_module}/private") + elseif (is_for_module) + set(wayland_include_dir "QtWaylandClient/private") + endif() + endif() + + add_custom_command( + OUTPUT "${qtwaylandscanner_header_output}" + COMMAND Qt6::qtwaylandscanner client-header + "${protocol_file}" + --build-macro=${build_macro} + --header-path="${wayland_include_dir}" + > "${qtwaylandscanner_header_output}" + DEPENDS ${protocol_file} Qt6::qtwaylandscanner + ) + + set(qtwaylandscanner_code_include "") + if (is_for_module) + set(qtwaylandscanner_code_include "") + endif() + + add_custom_command( + OUTPUT "${qtwaylandscanner_code_output}" + COMMAND Qt6::qtwaylandscanner client-code + "${protocol_file}" + --build-macro=${build_macro} + --header-path='${wayland_include_dir}' + --add-include='${qtwaylandscanner_code_include}' + > "${qtwaylandscanner_code_output}" + DEPENDS ${protocol_file} Qt6::qtwaylandscanner + ) + + set(sources "${waylandscanner_header_output}" + "${waylandscanner_code_output}" + "${qtwaylandscanner_header_output}" + "${qtwaylandscanner_code_output}") + + target_sources(${target} PRIVATE ${sources}) + + set_source_files_properties(${sources} PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) + endforeach() + target_include_directories(${target} PRIVATE ${target_binary_dir}) +endfunction() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + function(qt_generate_wayland_protocol_client_sources) + qt6_generate_wayland_protocol_client_sources(${ARGV}) + endfunction() +endif() diff --git a/src/tools/qtwaylandscanner/Qt6WaylandCompositorMacros.cmake b/src/tools/qtwaylandscanner/Qt6WaylandCompositorMacros.cmake new file mode 100644 index 00000000000..bf23bb727c0 --- /dev/null +++ b/src/tools/qtwaylandscanner/Qt6WaylandCompositorMacros.cmake @@ -0,0 +1,98 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +function(qt6_generate_wayland_protocol_server_sources target) + cmake_parse_arguments(arg "PUBLIC_CODE;PRIVATE_CODE" "__QT_INTERNAL_WAYLAND_INCLUDE_DIR" "FILES" ${ARGN}) + if(DEFINED arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown arguments were passed to qt6_generate_wayland_protocol_server_sources: (${arg_UNPARSED_ARGUMENTS}).") + endif() + + get_target_property(target_binary_dir ${target} BINARY_DIR) + + if(NOT TARGET Wayland::Scanner) + message(FATAL_ERROR "Wayland::Scanner target not found. You might be missing the WaylandScanner CMake package.") + endif() + + if(NOT TARGET Qt6::qtwaylandscanner) + message(FATAL_ERROR "qtwaylandscanner executable not found. Most likely there is an issue with your Qt installation.") + endif() + + string(TOUPPER "${target}" module_define_infix) + string(REPLACE "-" "_" module_define_infix "${module_define_infix}") + string(REPLACE "." "_" module_define_infix "${module_define_infix}") + set(build_macro "QT_BUILD_${module_define_infix}_LIB") + + if (arg_PRIVATE_CODE) + set(wayland_scanner_code_option "private-code") + else() + set(wayland_scanner_code_option "public-code") + endif() + + foreach(protocol_file IN LISTS arg_FILES) + get_filename_component(protocol_name "${protocol_file}" NAME_WLE) + + set(waylandscanner_header_output "${target_binary_dir}/wayland-${protocol_name}-server-protocol.h") + set(waylandscanner_code_output "${target_binary_dir}/wayland-${protocol_name}-protocol.c") + set(qtwaylandscanner_header_output "${target_binary_dir}/qwayland-server-${protocol_name}.h") + set(qtwaylandscanner_code_output "${target_binary_dir}/qwayland-server-${protocol_name}.cpp") + + add_custom_command( + OUTPUT "${waylandscanner_header_output}" + #TODO: Maybe put the files in ${CMAKE_CURRENT_BINARY_DIR/wayland_generated instead? + COMMAND Wayland::Scanner --include-core-only server-header < "${protocol_file}" > "${waylandscanner_header_output}" + DEPENDS ${protocol_file} Wayland::Scanner + ) + add_custom_command( + OUTPUT "${waylandscanner_code_output}" + COMMAND Wayland::Scanner --include-core-only ${wayland_scanner_code_option} < "${protocol_file}" > "${waylandscanner_code_output}" + DEPENDS ${protocol_file} Wayland::Scanner + ) + + set(wayland_include_dir "") + if(arg___QT_INTERNAL_WAYLAND_INCLUDE_DIR) + set(wayland_include_dir "${arg___QT_INTERNAL_WAYLAND_INCLUDE_DIR}") + else() + get_target_property(qt_module ${target} _qt_module_interface_name) + get_target_property(is_for_module "${target}" _qt_module_has_headers) + if (qt_module) + set(wayland_include_dir "Qt${qt_module}/private") + elseif (is_for_module) + set(wayland_include_dir "QtWaylandCompositor/private") + endif() + endif() + + add_custom_command( + OUTPUT "${qtwaylandscanner_header_output}" + COMMAND Qt6::qtwaylandscanner server-header + "${protocol_file}" + --build-macro=${build_macro} + --header-path='${wayland_include_dir}' + > "${qtwaylandscanner_header_output}" + DEPENDS ${protocol_file} Qt6::qtwaylandscanner + ) + + add_custom_command( + OUTPUT "${qtwaylandscanner_code_output}" + COMMAND Qt6::qtwaylandscanner server-code + "${protocol_file}" + --build-macro=${build_macro} + --header-path='${wayland_include_dir}' + > "${qtwaylandscanner_code_output}" + DEPENDS ${protocol_file} Qt6::qtwaylandscanner + ) + + target_sources(${target} PRIVATE + "${waylandscanner_header_output}" + "${waylandscanner_code_output}" + "${qtwaylandscanner_header_output}" + "${qtwaylandscanner_code_output}" + ) + endforeach() + target_include_directories(${target} PRIVATE ${target_binary_dir}) +endfunction() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + function(qt_generate_wayland_protocol_server_sources) + qt6_generate_wayland_protocol_server_sources(${ARGV}) + endfunction() +endif() diff --git a/src/tools/qtwaylandscanner/qtwaylandscanner.cpp b/src/tools/qtwaylandscanner/qtwaylandscanner.cpp new file mode 100644 index 00000000000..7f2769fa951 --- /dev/null +++ b/src/tools/qtwaylandscanner/qtwaylandscanner.cpp @@ -0,0 +1,1327 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include +#include +#include + +#include + +class Scanner +{ +public: + explicit Scanner() {} + ~Scanner() { delete m_xml; } + + bool parseArguments(int argc, char **argv); + void printUsage(); + bool process(); + void printErrors(); + +private: + struct WaylandEnumEntry { + QByteArray name; + QByteArray value; + QByteArray summary; + }; + + struct WaylandEnum { + QByteArray name; + + std::vector entries; + }; + + struct WaylandArgument { + QByteArray name; + QByteArray type; + QByteArray interface; + QByteArray summary; + bool allowNull; + }; + + struct WaylandEvent { + bool request; + QByteArray name; + QByteArray type; + std::vector arguments; + }; + + struct WaylandInterface { + QByteArray name; + int version; + + std::vector enums; + std::vector events; + std::vector requests; + }; + + bool isServerSide(); + bool parseOption(const QByteArray &str); + + QByteArray byteArrayValue(const QXmlStreamReader &xml, const char *name); + int intValue(const QXmlStreamReader &xml, const char *name, int defaultValue = 0); + bool boolValue(const QXmlStreamReader &xml, const char *name); + WaylandEvent readEvent(QXmlStreamReader &xml, bool request); + Scanner::WaylandEnum readEnum(QXmlStreamReader &xml); + Scanner::WaylandInterface readInterface(QXmlStreamReader &xml); + QByteArray waylandToCType(const QByteArray &waylandType, const QByteArray &interface); + QByteArray waylandToQtType(const QByteArray &waylandType, const QByteArray &interface, bool cStyleArray); + const Scanner::WaylandArgument *newIdArgument(const std::vector &arguments); + + void printEvent(const WaylandEvent &e, bool omitNames = false, bool withResource = false); + void printEventHandlerSignature(const WaylandEvent &e, const char *interfaceName, bool deepIndent = true); + void printEnums(const std::vector &enums); + + QByteArray stripInterfaceName(const QByteArray &name); + bool ignoreInterface(const QByteArray &name); + + enum Option { + ClientHeader, + ServerHeader, + ClientCode, + ServerCode + } m_option; + + QByteArray m_protocolName; + QByteArray m_protocolFilePath; + QByteArray m_scannerName; + QByteArray m_headerPath; + QByteArray m_prefix; + QByteArray m_buildMacro; + QList m_includes; + QXmlStreamReader *m_xml = nullptr; +}; + +bool Scanner::parseArguments(int argc, char **argv) +{ + QList args; + args.reserve(argc); + for (int i = 0; i < argc; ++i) + args << QByteArray(argv[i]); + + m_scannerName = args[0]; + + if (argc <= 2 || !parseOption(args[1])) + return false; + + m_protocolFilePath = args[2]; + + if (argc > 3 && !args[3].startsWith('-')) { + // legacy positional arguments + m_headerPath = args[3]; + if (argc == 5) + m_prefix = args[4]; + } else { + // --header-path= (14 characters) + // --prefix= (9 characters) + // --add-include= (14 characters) + for (int pos = 3; pos < argc; pos++) { + const QByteArray &option = args[pos]; + if (option.startsWith("--header-path=")) { + m_headerPath = option.mid(14); + } else if (option.startsWith("--prefix=")) { + m_prefix = option.mid(10); + } else if (option.startsWith("--build-macro=")) { + m_buildMacro = option.mid(14); + } else if (option.startsWith("--add-include=")) { + auto include = option.mid(14); + if (!include.isEmpty()) + m_includes << include; + } else { + return false; + } + } + } + + return true; +} + +void Scanner::printUsage() +{ + fprintf(stderr, "Usage: %s [client-header|server-header|client-code|server-code] specfile [--header-path=] [--prefix=] [--add-include=]\n", m_scannerName.constData()); +} + +bool Scanner::isServerSide() +{ + return m_option == ServerHeader || m_option == ServerCode; +} + +bool Scanner::parseOption(const QByteArray &str) +{ + if (str == "client-header") + m_option = ClientHeader; + else if (str == "server-header") + m_option = ServerHeader; + else if (str == "client-code") + m_option = ClientCode; + else if (str == "server-code") + m_option = ServerCode; + else + return false; + + return true; +} + +QByteArray Scanner::byteArrayValue(const QXmlStreamReader &xml, const char *name) +{ + if (xml.attributes().hasAttribute(name)) + return xml.attributes().value(name).toUtf8(); + return QByteArray(); +} + +int Scanner::intValue(const QXmlStreamReader &xml, const char *name, int defaultValue) +{ + bool ok; + int result = byteArrayValue(xml, name).toInt(&ok); + return ok ? result : defaultValue; +} + +bool Scanner::boolValue(const QXmlStreamReader &xml, const char *name) +{ + return byteArrayValue(xml, name) == "true"; +} + +Scanner::WaylandEvent Scanner::readEvent(QXmlStreamReader &xml, bool request) +{ + WaylandEvent event = { + .request = request, + .name = byteArrayValue(xml, "name"), + .type = byteArrayValue(xml, "type"), + .arguments = {}, + }; + while (xml.readNextStartElement()) { + if (xml.name() == u"arg") { + WaylandArgument argument = { + .name = byteArrayValue(xml, "name"), + .type = byteArrayValue(xml, "type"), + .interface = byteArrayValue(xml, "interface"), + .summary = byteArrayValue(xml, "summary"), + .allowNull = boolValue(xml, "allow-null"), + }; + event.arguments.push_back(std::move(argument)); + } + + xml.skipCurrentElement(); + } + return event; +} + +Scanner::WaylandEnum Scanner::readEnum(QXmlStreamReader &xml) +{ + WaylandEnum result = { + .name = byteArrayValue(xml, "name"), + .entries = {}, + }; + + while (xml.readNextStartElement()) { + if (xml.name() == u"entry") { + WaylandEnumEntry entry = { + .name = byteArrayValue(xml, "name"), + .value = byteArrayValue(xml, "value"), + .summary = byteArrayValue(xml, "summary"), + }; + result.entries.push_back(std::move(entry)); + } + + xml.skipCurrentElement(); + } + + return result; +} + +Scanner::WaylandInterface Scanner::readInterface(QXmlStreamReader &xml) +{ + WaylandInterface interface = { + .name = byteArrayValue(xml, "name"), + .version = intValue(xml, "version", 1), + .enums = {}, + .events = {}, + .requests = {}, + }; + + while (xml.readNextStartElement()) { + if (xml.name() == u"event") + interface.events.push_back(readEvent(xml, false)); + else if (xml.name() == u"request") + interface.requests.push_back(readEvent(xml, true)); + else if (xml.name() == u"enum") + interface.enums.push_back(readEnum(xml)); + else + xml.skipCurrentElement(); + } + + return interface; +} + +QByteArray Scanner::waylandToCType(const QByteArray &waylandType, const QByteArray &interface) +{ + if (waylandType == "string") + return "const char *"; + else if (waylandType == "int") + return "int32_t"; + else if (waylandType == "uint") + return "uint32_t"; + else if (waylandType == "fixed") + return "wl_fixed_t"; + else if (waylandType == "fd") + return "int32_t"; + else if (waylandType == "array") + return "wl_array *"; + else if (waylandType == "object" || waylandType == "new_id") { + if (isServerSide()) + return "struct ::wl_resource *"; + if (interface.isEmpty()) + return "struct ::wl_object *"; + return "struct ::" + interface + " *"; + } + return waylandType; +} + +QByteArray Scanner::waylandToQtType(const QByteArray &waylandType, const QByteArray &interface, bool cStyleArray) +{ + if (waylandType == "string") + return "const QString &"; + else if (waylandType == "array") + return cStyleArray ? "wl_array *" : "const QByteArray &"; + else + return waylandToCType(waylandType, interface); +} + +const Scanner::WaylandArgument *Scanner::newIdArgument(const std::vector &arguments) +{ + for (const WaylandArgument &a : arguments) { + if (a.type == "new_id") + return &a; + } + return nullptr; +} + +void Scanner::printEvent(const WaylandEvent &e, bool omitNames, bool withResource) +{ + printf("%s(", e.name.constData()); + bool needsComma = false; + if (isServerSide()) { + if (e.request) { + printf("Resource *%s", omitNames ? "" : "resource"); + needsComma = true; + } else if (withResource) { + printf("struct ::wl_resource *%s", omitNames ? "" : "resource"); + needsComma = true; + } + } + for (const WaylandArgument &a : e.arguments) { + bool isNewId = a.type == "new_id"; + if (isNewId && !isServerSide() && (a.interface.isEmpty() != e.request)) + continue; + if (needsComma) + printf(", "); + needsComma = true; + if (isNewId) { + if (isServerSide()) { + if (e.request) { + printf("uint32_t"); + if (!omitNames) + printf(" %s", a.name.constData()); + continue; + } + } else { + if (e.request) { + printf("const struct ::wl_interface *%s, uint32_t%s", omitNames ? "" : "interface", omitNames ? "" : " version"); + continue; + } + } + } + + QByteArray qtType = waylandToQtType(a.type, a.interface, e.request == isServerSide()); + printf("%s%s%s", qtType.constData(), qtType.endsWith("&") || qtType.endsWith("*") ? "" : " ", omitNames ? "" : a.name.constData()); + } + printf(")"); +} + +void Scanner::printEventHandlerSignature(const WaylandEvent &e, const char *interfaceName, bool deepIndent) +{ + const char *indent = deepIndent ? " " : ""; + printf("handle_%s(\n", e.name.constData()); + if (isServerSide()) { + printf(" %s::wl_client *client,\n", indent); + printf(" %sstruct wl_resource *resource", indent); + } else { + printf(" %svoid *data,\n", indent); + printf(" %sstruct ::%s *object", indent, interfaceName); + } + for (const WaylandArgument &a : e.arguments) { + printf(",\n"); + bool isNewId = a.type == "new_id"; + if (isServerSide() && isNewId) { + printf(" %suint32_t %s", indent, a.name.constData()); + } else { + QByteArray cType = waylandToCType(a.type, a.interface); + printf(" %s%s%s%s", indent, cType.constData(), cType.endsWith("*") ? "" : " ", a.name.constData()); + } + } + printf(")"); +} + +void Scanner::printEnums(const std::vector &enums) +{ + for (const WaylandEnum &e : enums) { + printf("\n"); + printf(" enum %s {\n", e.name.constData()); + for (const WaylandEnumEntry &entry : e.entries) { + printf(" %s_%s = %s,", e.name.constData(), entry.name.constData(), entry.value.constData()); + if (!entry.summary.isNull()) + printf(" // %s", entry.summary.constData()); + printf("\n"); + } + printf(" };\n"); + } +} + +QByteArray Scanner::stripInterfaceName(const QByteArray &name) +{ + if (!m_prefix.isEmpty() && name.startsWith(m_prefix)) + return name.mid(m_prefix.size()); + if (name.startsWith("qt_") || name.startsWith("wl_")) + return name.mid(3); + + return name; +} + +bool Scanner::ignoreInterface(const QByteArray &name) +{ + return name == "wl_display" + || (isServerSide() && name == "wl_registry"); +} + +bool Scanner::process() +{ + QFile file(m_protocolFilePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + fprintf(stderr, "Unable to open file %s\n", m_protocolFilePath.constData()); + return false; + } + + m_xml = new QXmlStreamReader(&file); + if (!m_xml->readNextStartElement()) + return false; + + if (m_xml->name() != u"protocol") { + m_xml->raiseError(QStringLiteral("The file is not a wayland protocol file.")); + return false; + } + + m_protocolName = byteArrayValue(*m_xml, "name"); + + if (m_protocolName.isEmpty()) { + m_xml->raiseError(QStringLiteral("Missing protocol name.")); + return false; + } + + //We should convert - to _ so that the preprocessor wont generate code which will lead to unexpected behavior + //However, the wayland-scanner doesn't do so we will do the same for now + //QByteArray preProcessorProtocolName = QByteArray(m_protocolName).replace('-', '_').toUpper(); + QByteArray preProcessorProtocolName = QByteArray(m_protocolName).toUpper(); + + const QByteArray fileBaseName = QFileInfo(file).completeBaseName().toLocal8Bit(); + + std::vector interfaces; + + while (m_xml->readNextStartElement()) { + if (m_xml->name() == u"interface") + interfaces.push_back(readInterface(*m_xml)); + else + m_xml->skipCurrentElement(); + } + + if (m_xml->hasError()) + return false; + + printf("// This file was generated by qtwaylandscanner\n"); + printf("// source file is %s\n\n", qPrintable(QFileInfo(file).fileName())); + + for (auto b : std::as_const(m_includes)) + printf("#include %s\n", b.constData()); + + auto printExportMacro = [this](const char *prefix, const QByteArray &preProcessorProtocolName) { + QByteArray exportMacro = prefix + preProcessorProtocolName + "_EXPORT"; + printf("#if !defined(%s)\n", exportMacro.constData()); + printf("# if defined(QT_SHARED) && !defined(QT_STATIC)\n"); + if (m_buildMacro.isEmpty()) { + printf("# define %s Q_DECL_EXPORT\n", exportMacro.constData()); + } else { + printf("# if defined(%s)\n", m_buildMacro.constData()); + printf("# define %s Q_DECL_EXPORT\n", exportMacro.constData()); + printf("# else\n"); + printf("# define %s Q_DECL_IMPORT\n", exportMacro.constData()); + printf("# endif\n"); + } + printf("# else\n"); + printf("# define %s\n", exportMacro.constData()); + printf("# endif\n"); + printf("#endif\n"); + return exportMacro; + }; + + if (m_option == ServerHeader) { + QByteArray inclusionGuard = QByteArray("QT_WAYLAND_SERVER_") + preProcessorProtocolName.constData(); + printf("#ifndef %s\n", inclusionGuard.constData()); + printf("#define %s\n", inclusionGuard.constData()); + printf("\n"); + printf("#include \"wayland-server-core.h\"\n"); + if (m_headerPath.isEmpty()) + printf("#include \"wayland-%s-server-protocol.h\"\n", fileBaseName.constData()); + else + printf("#include <%s/wayland-%s-server-protocol.h>\n", m_headerPath.constData(), fileBaseName.constData()); + printf("#include \n"); + printf("#include \n"); + printf("#include \n"); + + printf("\n"); + printf("#ifndef WAYLAND_VERSION_CHECK\n"); + printf("#define WAYLAND_VERSION_CHECK(major, minor, micro) \\\n"); + printf(" ((WAYLAND_VERSION_MAJOR > (major)) || \\\n"); + printf(" (WAYLAND_VERSION_MAJOR == (major) && WAYLAND_VERSION_MINOR > (minor)) || \\\n"); + printf(" (WAYLAND_VERSION_MAJOR == (major) && WAYLAND_VERSION_MINOR == (minor) && WAYLAND_VERSION_MICRO >= (micro)))\n"); + printf("#endif\n"); + + printf("\n"); + printf("QT_BEGIN_NAMESPACE\n"); + printf("QT_WARNING_PUSH\n"); + printf("QT_WARNING_DISABLE_GCC(\"-Wmissing-field-initializers\")\n"); + printf("QT_WARNING_DISABLE_CLANG(\"-Wmissing-field-initializers\")\n"); + QByteArray serverExport; + if (m_headerPath.size()) + serverExport = printExportMacro("Q_WAYLAND_SERVER_", preProcessorProtocolName); + printf("\n"); + printf("namespace QtWaylandServer {\n"); + + bool needsNewLine = false; + for (const WaylandInterface &interface : interfaces) { + + if (ignoreInterface(interface.name)) + continue; + + if (needsNewLine) + printf("\n"); + needsNewLine = true; + + const char *interfaceName = interface.name.constData(); + + QByteArray stripped = stripInterfaceName(interface.name); + const char *interfaceNameStripped = stripped.constData(); + + printf(" class %s %s\n {\n", serverExport.constData(), interfaceName); + printf(" public:\n"); + printf(" %s(struct ::wl_client *client, uint32_t id, int version);\n", interfaceName); + printf(" %s(struct ::wl_display *display, int version);\n", interfaceName); + printf(" %s(struct ::wl_resource *resource);\n", interfaceName); + printf(" %s();\n", interfaceName); + printf("\n"); + printf(" virtual ~%s();\n", interfaceName); + printf("\n"); + printf(" class Resource\n"); + printf(" {\n"); + printf(" public:\n"); + printf(" Resource() : %s_object(nullptr), handle(nullptr) {}\n", interfaceNameStripped); + printf(" virtual ~Resource() {}\n"); + printf("\n"); + printf(" %s *%s_object;\n", interfaceName, interfaceNameStripped); + printf(" %s *object() { return %s_object; } \n", interfaceName, interfaceNameStripped); + printf(" struct ::wl_resource *handle;\n"); + printf("\n"); + printf(" struct ::wl_client *client() const { return wl_resource_get_client(handle); }\n"); + printf(" int version() const { return wl_resource_get_version(handle); }\n"); + printf("\n"); + printf(" static Resource *fromResource(struct ::wl_resource *resource);\n"); + printf(" };\n"); + printf("\n"); + printf(" void init(struct ::wl_client *client, uint32_t id, int version);\n"); + printf(" void init(struct ::wl_display *display, int version);\n"); + printf(" void init(struct ::wl_resource *resource);\n"); + printf("\n"); + printf(" Resource *add(struct ::wl_client *client, int version);\n"); + printf(" Resource *add(struct ::wl_client *client, uint32_t id, int version);\n"); + printf(" Resource *add(struct wl_list *resource_list, struct ::wl_client *client, uint32_t id, int version);\n"); + printf("\n"); + printf(" Resource *resource() { return m_resource; }\n"); + printf(" const Resource *resource() const { return m_resource; }\n"); + printf("\n"); + printf(" QMultiMap resourceMap() { return m_resource_map; }\n"); + printf(" const QMultiMap resourceMap() const { return m_resource_map; }\n"); + printf("\n"); + printf(" bool isGlobal() const { return m_global != nullptr; }\n"); + printf(" bool isResource() const { return m_resource != nullptr; }\n"); + printf("\n"); + printf(" static const struct ::wl_interface *interface();\n"); + printf(" static QByteArray interfaceName() { return interface()->name; }\n"); + printf(" static int interfaceVersion() { return interface()->version; }\n"); + printf("\n"); + + printEnums(interface.enums); + + bool hasEvents = !interface.events.empty(); + + if (hasEvents) { + printf("\n"); + for (const WaylandEvent &e : interface.events) { + printf(" void send_"); + printEvent(e); + printf(";\n"); + printf(" void send_"); + printEvent(e, false, true); + printf(";\n"); + } + } + + printf("\n"); + printf(" protected:\n"); + printf(" virtual Resource *%s_allocate();\n", interfaceNameStripped); + printf("\n"); + printf(" virtual void %s_bind_resource(Resource *resource);\n", interfaceNameStripped); + printf(" virtual void %s_destroy_resource(Resource *resource);\n", interfaceNameStripped); + + bool hasRequests = !interface.requests.empty(); + + if (hasRequests) { + printf("\n"); + for (const WaylandEvent &e : interface.requests) { + printf(" virtual void %s_", interfaceNameStripped); + printEvent(e); + printf(";\n"); + } + } + + printf("\n"); + printf(" private:\n"); + printf(" static void bind_func(struct ::wl_client *client, void *data, uint32_t version, uint32_t id);\n"); + printf(" static void destroy_func(struct ::wl_resource *client_resource);\n"); + printf(" static void display_destroy_func(struct ::wl_listener *listener, void *data);\n"); + printf("\n"); + printf(" Resource *bind(struct ::wl_client *client, uint32_t id, int version);\n"); + printf(" Resource *bind(struct ::wl_resource *handle);\n"); + + if (hasRequests) { + printf("\n"); + printf(" static const struct ::%s_interface m_%s_interface;\n", interfaceName, interfaceName); + + printf("\n"); + for (const WaylandEvent &e : interface.requests) { + printf(" static void "); + + printEventHandlerSignature(e, interfaceName); + printf(";\n"); + } + } + + printf("\n"); + printf(" QMultiMap m_resource_map;\n"); + printf(" Resource *m_resource;\n"); + printf(" struct ::wl_global *m_global;\n"); + printf(" struct DisplayDestroyedListener : ::wl_listener {\n"); + printf(" %s *parent;\n", interfaceName); + printf(" };\n"); + printf(" DisplayDestroyedListener m_displayDestroyedListener;\n"); + printf(" };\n"); + } + + printf("}\n"); + printf("\n"); + printf("QT_WARNING_POP\n"); + printf("QT_END_NAMESPACE\n"); + printf("\n"); + printf("#endif\n"); + } + + if (m_option == ServerCode) { + if (m_headerPath.isEmpty()) + printf("#include \"qwayland-server-%s.h\"\n", fileBaseName.constData()); + else + printf("#include <%s/qwayland-server-%s.h>\n", m_headerPath.constData(), fileBaseName.constData()); + printf("\n"); + printf("QT_BEGIN_NAMESPACE\n"); + printf("QT_WARNING_PUSH\n"); + printf("QT_WARNING_DISABLE_GCC(\"-Wmissing-field-initializers\")\n"); + printf("QT_WARNING_DISABLE_CLANG(\"-Wmissing-field-initializers\")\n"); + printf("\n"); + printf("namespace QtWaylandServer {\n"); + + bool needsNewLine = false; + for (const WaylandInterface &interface : interfaces) { + + if (ignoreInterface(interface.name)) + continue; + + if (needsNewLine) + printf("\n"); + + needsNewLine = true; + + const char *interfaceName = interface.name.constData(); + + QByteArray stripped = stripInterfaceName(interface.name); + const char *interfaceNameStripped = stripped.constData(); + + printf(" %s::%s(struct ::wl_client *client, uint32_t id, int version)\n", interfaceName, interfaceName); + printf(" : m_resource_map()\n"); + printf(" , m_resource(nullptr)\n"); + printf(" , m_global(nullptr)\n"); + printf(" {\n"); + printf(" init(client, id, version);\n"); + printf(" }\n"); + printf("\n"); + + printf(" %s::%s(struct ::wl_display *display, int version)\n", interfaceName, interfaceName); + printf(" : m_resource_map()\n"); + printf(" , m_resource(nullptr)\n"); + printf(" , m_global(nullptr)\n"); + printf(" {\n"); + printf(" init(display, version);\n"); + printf(" }\n"); + printf("\n"); + + printf(" %s::%s(struct ::wl_resource *resource)\n", interfaceName, interfaceName); + printf(" : m_resource_map()\n"); + printf(" , m_resource(nullptr)\n"); + printf(" , m_global(nullptr)\n"); + printf(" {\n"); + printf(" init(resource);\n"); + printf(" }\n"); + printf("\n"); + + printf(" %s::%s()\n", interfaceName, interfaceName); + printf(" : m_resource_map()\n"); + printf(" , m_resource(nullptr)\n"); + printf(" , m_global(nullptr)\n"); + printf(" {\n"); + printf(" }\n"); + printf("\n"); + + printf(" %s::~%s()\n", interfaceName, interfaceName); + printf(" {\n"); + printf(" for (auto resource : std::as_const(m_resource_map))\n"); + printf(" resource->%s_object = nullptr;\n", interfaceNameStripped); + printf("\n"); + printf(" if (m_resource)\n"); + printf(" m_resource->%s_object = nullptr;\n", interfaceNameStripped); + printf("\n"); + printf(" if (m_global) {\n"); + printf(" wl_global_destroy(m_global);\n"); + printf(" wl_list_remove(&m_displayDestroyedListener.link);\n"); + printf(" }\n"); + printf(" }\n"); + printf("\n"); + + printf(" void %s::init(struct ::wl_client *client, uint32_t id, int version)\n", interfaceName); + printf(" {\n"); + printf(" m_resource = bind(client, id, version);\n"); + printf(" }\n"); + printf("\n"); + + printf(" void %s::init(struct ::wl_resource *resource)\n", interfaceName); + printf(" {\n"); + printf(" m_resource = bind(resource);\n"); + printf(" }\n"); + printf("\n"); + + printf(" %s::Resource *%s::add(struct ::wl_client *client, int version)\n", interfaceName, interfaceName); + printf(" {\n"); + printf(" Resource *resource = bind(client, 0, version);\n"); + printf(" m_resource_map.insert(client, resource);\n"); + printf(" return resource;\n"); + printf(" }\n"); + printf("\n"); + + printf(" %s::Resource *%s::add(struct ::wl_client *client, uint32_t id, int version)\n", interfaceName, interfaceName); + printf(" {\n"); + printf(" Resource *resource = bind(client, id, version);\n"); + printf(" m_resource_map.insert(client, resource);\n"); + printf(" return resource;\n"); + printf(" }\n"); + printf("\n"); + + printf(" void %s::init(struct ::wl_display *display, int version)\n", interfaceName); + printf(" {\n"); + printf(" m_global = wl_global_create(display, &::%s_interface, version, this, bind_func);\n", interfaceName); + printf(" m_displayDestroyedListener.notify = %s::display_destroy_func;\n", interfaceName); + printf(" m_displayDestroyedListener.parent = this;\n"); + printf(" wl_display_add_destroy_listener(display, &m_displayDestroyedListener);\n"); + printf(" }\n"); + printf("\n"); + + printf(" const struct wl_interface *%s::interface()\n", interfaceName); + printf(" {\n"); + printf(" return &::%s_interface;\n", interfaceName); + printf(" }\n"); + printf("\n"); + + printf(" %s::Resource *%s::%s_allocate()\n", interfaceName, interfaceName, interfaceNameStripped); + printf(" {\n"); + printf(" return new Resource;\n"); + printf(" }\n"); + printf("\n"); + + printf(" void %s::%s_bind_resource(Resource *)\n", interfaceName, interfaceNameStripped); + printf(" {\n"); + printf(" }\n"); + printf("\n"); + + printf(" void %s::%s_destroy_resource(Resource *)\n", interfaceName, interfaceNameStripped); + printf(" {\n"); + printf(" }\n"); + printf("\n"); + + printf(" void %s::bind_func(struct ::wl_client *client, void *data, uint32_t version, uint32_t id)\n", interfaceName); + printf(" {\n"); + printf(" %s *that = static_cast<%s *>(data);\n", interfaceName, interfaceName); + printf(" that->add(client, id, version);\n"); + printf(" }\n"); + printf("\n"); + + printf(" void %s::display_destroy_func(struct ::wl_listener *listener, void *data)\n", interfaceName); + printf(" {\n"); + printf(" Q_UNUSED(data);\n"); + printf(" %s *that = static_cast<%s::DisplayDestroyedListener *>(listener)->parent;\n", interfaceName, interfaceName); + printf(" that->m_global = nullptr;\n"); + printf(" }\n"); + printf("\n"); + + printf(" void %s::destroy_func(struct ::wl_resource *client_resource)\n", interfaceName); + printf(" {\n"); + printf(" Resource *resource = Resource::fromResource(client_resource);\n"); + printf(" Q_ASSERT(resource);\n"); + printf(" %s *that = resource->%s_object;\n", interfaceName, interfaceNameStripped); + printf(" if (Q_LIKELY(that)) {\n"); + printf(" that->m_resource_map.remove(resource->client(), resource);\n"); + printf(" that->%s_destroy_resource(resource);\n", interfaceNameStripped); + printf("\n"); + printf(" that = resource->%s_object;\n", interfaceNameStripped); + printf(" if (that && that->m_resource == resource)\n"); + printf(" that->m_resource = nullptr;\n"); + printf(" }\n"); + printf(" delete resource;\n"); + printf(" }\n"); + printf("\n"); + + bool hasRequests = !interface.requests.empty(); + + QByteArray interfaceMember = hasRequests ? "&m_" + interface.name + "_interface" : QByteArray("nullptr"); + + //We should consider changing bind so that it doesn't special case id == 0 + //and use function overloading instead. Jan do you have a lot of code dependent on this behavior? + printf(" %s::Resource *%s::bind(struct ::wl_client *client, uint32_t id, int version)\n", interfaceName, interfaceName); + printf(" {\n"); + printf(" Q_ASSERT_X(!wl_client_get_object(client, id), \"QWaylandObject bind\", QStringLiteral(\"binding to object %%1 more than once\").arg(id).toLocal8Bit().constData());\n"); + printf(" struct ::wl_resource *handle = wl_resource_create(client, &::%s_interface, version, id);\n", interfaceName); + printf(" return bind(handle);\n"); + printf(" }\n"); + printf("\n"); + + printf(" %s::Resource *%s::bind(struct ::wl_resource *handle)\n", interfaceName, interfaceName); + printf(" {\n"); + printf(" Resource *resource = %s_allocate();\n", interfaceNameStripped); + printf(" resource->%s_object = this;\n", interfaceNameStripped); + printf("\n"); + printf(" wl_resource_set_implementation(handle, %s, resource, destroy_func);", interfaceMember.constData()); + printf("\n"); + printf(" resource->handle = handle;\n"); + printf(" %s_bind_resource(resource);\n", interfaceNameStripped); + printf(" return resource;\n"); + printf(" }\n"); + + printf(" %s::Resource *%s::Resource::fromResource(struct ::wl_resource *resource)\n", interfaceName, interfaceName); + printf(" {\n"); + printf(" if (Q_UNLIKELY(!resource))\n"); + printf(" return nullptr;\n"); + printf(" if (wl_resource_instance_of(resource, &::%s_interface, %s))\n", interfaceName, interfaceMember.constData()); + printf(" return static_cast(wl_resource_get_user_data(resource));\n"); + printf(" return nullptr;\n"); + printf(" }\n"); + + if (hasRequests) { + printf("\n"); + printf(" const struct ::%s_interface %s::m_%s_interface = {", interfaceName, interfaceName, interfaceName); + bool needsComma = false; + for (const WaylandEvent &e : interface.requests) { + if (needsComma) + printf(","); + needsComma = true; + printf("\n"); + printf(" %s::handle_%s", interfaceName, e.name.constData()); + } + printf("\n"); + printf(" };\n"); + + for (const WaylandEvent &e : interface.requests) { + printf("\n"); + printf(" void %s::%s_", interfaceName, interfaceNameStripped); + printEvent(e, true); + printf("\n"); + printf(" {\n"); + printf(" }\n"); + } + printf("\n"); + + for (const WaylandEvent &e : interface.requests) { + printf("\n"); + printf(" void %s::", interfaceName); + + printEventHandlerSignature(e, interfaceName, false); + + printf("\n"); + printf(" {\n"); + printf(" Q_UNUSED(client);\n"); + printf(" Resource *r = Resource::fromResource(resource);\n"); + printf(" if (Q_UNLIKELY(!r->%s_object)) {\n", interfaceNameStripped); + if (e.type == "destructor") + printf(" wl_resource_destroy(resource);\n"); + printf(" return;\n"); + printf(" }\n"); + printf(" static_cast<%s *>(r->%s_object)->%s_%s(\n", interfaceName, interfaceNameStripped, interfaceNameStripped, e.name.constData()); + printf(" r"); + for (const WaylandArgument &a : e.arguments) { + printf(",\n"); + QByteArray cType = waylandToCType(a.type, a.interface); + QByteArray qtType = waylandToQtType(a.type, a.interface, e.request); + const char *argumentName = a.name.constData(); + if (cType == qtType) + printf(" %s", argumentName); + else if (a.type == "string") + printf(" QString::fromUtf8(%s)", argumentName); + } + printf(");\n"); + printf(" }\n"); + } + } + + for (const WaylandEvent &e : interface.events) { + printf("\n"); + printf(" void %s::send_", interfaceName); + printEvent(e); + printf("\n"); + printf(" {\n"); + printf(" Q_ASSERT_X(m_resource, \"%s::%s\", \"Uninitialised resource\");\n", interfaceName, e.name.constData()); + printf(" if (Q_UNLIKELY(!m_resource)) {\n"); + printf(" qWarning(\"could not call %s::%s as it's not initialised\");\n", interfaceName, e.name.constData()); + printf(" return;\n"); + printf(" }\n"); + printf(" send_%s(\n", e.name.constData()); + printf(" m_resource->handle"); + for (const WaylandArgument &a : e.arguments) { + printf(",\n"); + printf(" %s", a.name.constData()); + } + printf(");\n"); + printf(" }\n"); + printf("\n"); + + printf(" void %s::send_", interfaceName); + printEvent(e, false, true); + printf("\n"); + printf(" {\n"); + + for (const WaylandArgument &a : e.arguments) { + if (a.type != "array") + continue; + QByteArray array = a.name + "_data"; + const char *arrayName = array.constData(); + const char *variableName = a.name.constData(); + printf(" struct wl_array %s;\n", arrayName); + printf(" %s.size = %s.size();\n", arrayName, variableName); + printf(" %s.data = static_cast(const_cast(%s.constData()));\n", arrayName, variableName); + printf(" %s.alloc = 0;\n", arrayName); + printf("\n"); + } + + printf(" %s_send_%s(\n", interfaceName, e.name.constData()); + printf(" resource"); + + for (const WaylandArgument &a : e.arguments) { + printf(",\n"); + QByteArray cType = waylandToCType(a.type, a.interface); + QByteArray qtType = waylandToQtType(a.type, a.interface, e.request); + if (a.type == "string") { + printf(" "); + if (a.allowNull) + printf("%s.isNull() ? nullptr : ", a.name.constData()); + printf("%s.toUtf8().constData()", a.name.constData()); + } else if (a.type == "array") + printf(" &%s_data", a.name.constData()); + else if (cType == qtType) + printf(" %s", a.name.constData()); + } + + printf(");\n"); + printf(" }\n"); + printf("\n"); + } + } + printf("}\n"); + printf("\n"); + printf("QT_WARNING_POP\n"); + printf("QT_END_NAMESPACE\n"); + } + + if (m_option == ClientHeader) { + QByteArray inclusionGuard = QByteArray("QT_WAYLAND_") + preProcessorProtocolName.constData(); + printf("#ifndef %s\n", inclusionGuard.constData()); + printf("#define %s\n", inclusionGuard.constData()); + printf("\n"); + if (m_headerPath.isEmpty()) + printf("#include \"wayland-%s-client-protocol.h\"\n", fileBaseName.constData()); + else + printf("#include <%s/wayland-%s-client-protocol.h>\n", m_headerPath.constData(), fileBaseName.constData()); + printf("#include \n"); + printf("#include \n"); + printf("\n"); + printf("struct wl_registry;\n"); + printf("\n"); + printf("QT_BEGIN_NAMESPACE\n"); + printf("QT_WARNING_PUSH\n"); + printf("QT_WARNING_DISABLE_GCC(\"-Wmissing-field-initializers\")\n"); + printf("QT_WARNING_DISABLE_CLANG(\"-Wmissing-field-initializers\")\n"); + + QByteArray clientExport; + if (m_headerPath.size()) + clientExport = printExportMacro("Q_WAYLAND_CLIENT_", preProcessorProtocolName); + + printf("\n"); + printf("namespace QtWayland {\n"); + + bool needsNewLine = false; + for (const WaylandInterface &interface : interfaces) { + + if (ignoreInterface(interface.name)) + continue; + + if (needsNewLine) + printf("\n"); + needsNewLine = true; + + const char *interfaceName = interface.name.constData(); + + QByteArray stripped = stripInterfaceName(interface.name); + const char *interfaceNameStripped = stripped.constData(); + + printf(" class %s %s\n {\n", clientExport.constData(), interfaceName); + printf(" public:\n"); + printf(" %s(struct ::wl_registry *registry, uint32_t id, int version);\n", interfaceName); + printf(" %s(struct ::%s *object);\n", interfaceName, interfaceName); + printf(" %s();\n", interfaceName); + printf("\n"); + printf(" virtual ~%s();\n", interfaceName); + printf("\n"); + printf(" void init(struct ::wl_registry *registry, uint32_t id, int version);\n"); + printf(" void init(struct ::%s *object);\n", interfaceName); + printf("\n"); + printf(" struct ::%s *object() { return m_%s; }\n", interfaceName, interfaceName); + printf(" const struct ::%s *object() const { return m_%s; }\n", interfaceName, interfaceName); + printf(" static %s *fromObject(struct ::%s *object);\n", interfaceName, interfaceName); + printf("\n"); + printf(" bool isInitialized() const;\n"); + printf("\n"); + printf(" uint32_t version() const;"); + printf("\n"); + printf(" static const struct ::wl_interface *interface();\n"); + + printEnums(interface.enums); + + if (!interface.requests.empty()) { + printf("\n"); + for (const WaylandEvent &e : interface.requests) { + const WaylandArgument *new_id = newIdArgument(e.arguments); + QByteArray new_id_str = "void "; + if (new_id) { + if (new_id->interface.isEmpty()) + new_id_str = "void *"; + else + new_id_str = "struct ::" + new_id->interface + " *"; + } + printf(" %s", new_id_str.constData()); + printEvent(e); + printf(";\n"); + } + } + + bool hasEvents = !interface.events.empty(); + + if (hasEvents) { + printf("\n"); + printf(" protected:\n"); + for (const WaylandEvent &e : interface.events) { + printf(" virtual void %s_", interfaceNameStripped); + printEvent(e); + printf(";\n"); + } + } + + printf("\n"); + printf(" private:\n"); + if (hasEvents) { + printf(" void init_listener();\n"); + printf(" static const struct %s_listener m_%s_listener;\n", interfaceName, interfaceName); + for (const WaylandEvent &e : interface.events) { + printf(" static void "); + + printEventHandlerSignature(e, interfaceName); + printf(";\n"); + } + } + printf(" struct ::%s *m_%s;\n", interfaceName, interfaceName); + printf(" };\n"); + } + printf("}\n"); + printf("\n"); + printf("QT_WARNING_POP\n"); + printf("QT_END_NAMESPACE\n"); + printf("\n"); + printf("#endif\n"); + } + + if (m_option == ClientCode) { + if (m_headerPath.isEmpty()) + printf("#include \"qwayland-%s.h\"\n", fileBaseName.constData()); + else + printf("#include <%s/qwayland-%s.h>\n", m_headerPath.constData(), fileBaseName.constData()); + printf("\n"); + printf("QT_BEGIN_NAMESPACE\n"); + printf("QT_WARNING_PUSH\n"); + printf("QT_WARNING_DISABLE_GCC(\"-Wmissing-field-initializers\")\n"); + printf("QT_WARNING_DISABLE_CLANG(\"-Wmissing-field-initializers\")\n"); + printf("\n"); + printf("namespace QtWayland {\n"); + printf("\n"); + + // wl_registry_bind is part of the protocol, so we can't use that... instead we use core + // libwayland API to do the same thing a wayland-scanner generated wl_registry_bind would. + printf("static inline void *wlRegistryBind(struct ::wl_registry *registry, uint32_t name, const struct ::wl_interface *interface, uint32_t version)\n"); + printf("{\n"); + printf(" const uint32_t bindOpCode = 0;\n"); + printf(" return (void *) wl_proxy_marshal_constructor_versioned((struct wl_proxy *) registry,\n"); + printf(" bindOpCode, interface, version, name, interface->name, version, nullptr);\n"); + printf("}\n"); + printf("\n"); + + bool needsNewLine = false; + for (const WaylandInterface &interface : interfaces) { + + if (ignoreInterface(interface.name)) + continue; + + if (needsNewLine) + printf("\n"); + needsNewLine = true; + + const char *interfaceName = interface.name.constData(); + + QByteArray stripped = stripInterfaceName(interface.name); + const char *interfaceNameStripped = stripped.constData(); + + bool hasEvents = !interface.events.empty(); + + printf(" %s::%s(struct ::wl_registry *registry, uint32_t id, int version)\n", interfaceName, interfaceName); + printf(" {\n"); + printf(" init(registry, id, version);\n"); + printf(" }\n"); + printf("\n"); + + printf(" %s::%s(struct ::%s *obj)\n", interfaceName, interfaceName, interfaceName); + printf(" : m_%s(obj)\n", interfaceName); + printf(" {\n"); + if (hasEvents) + printf(" init_listener();\n"); + printf(" }\n"); + printf("\n"); + + printf(" %s::%s()\n", interfaceName, interfaceName); + printf(" : m_%s(nullptr)\n", interfaceName); + printf(" {\n"); + printf(" }\n"); + printf("\n"); + + printf(" %s::~%s()\n", interfaceName, interfaceName); + printf(" {\n"); + printf(" }\n"); + printf("\n"); + + printf(" void %s::init(struct ::wl_registry *registry, uint32_t id, int version)\n", interfaceName); + printf(" {\n"); + printf(" m_%s = static_cast(wlRegistryBind(registry, id, &%s_interface, version));\n", interfaceName, interfaceName, interfaceName); + if (hasEvents) + printf(" init_listener();\n"); + printf(" }\n"); + printf("\n"); + + printf(" void %s::init(struct ::%s *obj)\n", interfaceName, interfaceName); + printf(" {\n"); + printf(" m_%s = obj;\n", interfaceName); + if (hasEvents) + printf(" init_listener();\n"); + printf(" }\n"); + printf("\n"); + + printf(" %s *%s::fromObject(struct ::%s *object)\n", interfaceName, interfaceName, interfaceName); + printf(" {\n"); + if (hasEvents) { + printf(" if (wl_proxy_get_listener((struct ::wl_proxy *)object) != (void *)&m_%s_listener)\n", interfaceName); + printf(" return nullptr;\n"); + } + printf(" return static_cast<%s *>(%s_get_user_data(object));\n", interfaceName, interfaceName); + printf(" }\n"); + printf("\n"); + + printf(" bool %s::isInitialized() const\n", interfaceName); + printf(" {\n"); + printf(" return m_%s != nullptr;\n", interfaceName); + printf(" }\n"); + printf("\n"); + + printf(" uint32_t %s::version() const\n", interfaceName); + printf(" {\n"); + printf(" return wl_proxy_get_version(reinterpret_cast(m_%s));\n", interfaceName); + printf(" }\n"); + printf("\n"); + + printf(" const struct wl_interface *%s::interface()\n", interfaceName); + printf(" {\n"); + printf(" return &::%s_interface;\n", interfaceName); + printf(" }\n"); + + for (const WaylandEvent &e : interface.requests) { + printf("\n"); + const WaylandArgument *new_id = newIdArgument(e.arguments); + QByteArray new_id_str = "void "; + if (new_id) { + if (new_id->interface.isEmpty()) + new_id_str = "void *"; + else + new_id_str = "struct ::" + new_id->interface + " *"; + } + printf(" %s%s::", new_id_str.constData(), interfaceName); + printEvent(e); + printf("\n"); + printf(" {\n"); + for (const WaylandArgument &a : e.arguments) { + if (a.type != "array") + continue; + QByteArray array = a.name + "_data"; + const char *arrayName = array.constData(); + const char *variableName = a.name.constData(); + printf(" struct wl_array %s;\n", arrayName); + printf(" %s.size = %s.size();\n", arrayName, variableName); + printf(" %s.data = static_cast(const_cast(%s.constData()));\n", arrayName, variableName); + printf(" %s.alloc = 0;\n", arrayName); + printf("\n"); + } + int actualArgumentCount = new_id ? int(e.arguments.size()) - 1 : int(e.arguments.size()); + printf(" %s::%s_%s(\n", new_id ? "return " : "", interfaceName, e.name.constData()); + printf(" m_%s%s", interfaceName, actualArgumentCount > 0 ? "," : ""); + bool needsComma = false; + for (const WaylandArgument &a : e.arguments) { + bool isNewId = a.type == "new_id"; + if (isNewId && !a.interface.isEmpty()) + continue; + if (needsComma) + printf(","); + needsComma = true; + printf("\n"); + if (isNewId) { + printf(" interface,\n"); + printf(" version"); + } else { + QByteArray cType = waylandToCType(a.type, a.interface); + QByteArray qtType = waylandToQtType(a.type, a.interface, e.request); + if (a.type == "string") { + printf(" "); + if (a.allowNull) + printf("%s.isNull() ? nullptr : ", a.name.constData()); + printf("%s.toUtf8().constData()", a.name.constData()); + } else if (a.type == "array") + printf(" &%s_data", a.name.constData()); + else if (cType == qtType) + printf(" %s", a.name.constData()); + } + } + printf(");\n"); + if (e.type == "destructor") + printf(" m_%s = nullptr;\n", interfaceName); + printf(" }\n"); + } + + if (hasEvents) { + printf("\n"); + for (const WaylandEvent &e : interface.events) { + printf(" void %s::%s_", interfaceName, interfaceNameStripped); + printEvent(e, true); + printf("\n"); + printf(" {\n"); + printf(" }\n"); + printf("\n"); + printf(" void %s::", interfaceName); + printEventHandlerSignature(e, interfaceName, false); + printf("\n"); + printf(" {\n"); + printf(" Q_UNUSED(object);\n"); + printf(" static_cast<%s *>(data)->%s_%s(", interfaceName, interfaceNameStripped, e.name.constData()); + bool needsComma = false; + for (const WaylandArgument &a : e.arguments) { + if (needsComma) + printf(","); + needsComma = true; + printf("\n"); + const char *argumentName = a.name.constData(); + if (a.type == "string") + printf(" QString::fromUtf8(%s)", argumentName); + else + printf(" %s", argumentName); + } + printf(");\n"); + + printf(" }\n"); + printf("\n"); + } + printf(" const struct %s_listener %s::m_%s_listener = {\n", interfaceName, interfaceName, interfaceName); + for (const WaylandEvent &e : interface.events) { + printf(" %s::handle_%s,\n", interfaceName, e.name.constData()); + } + printf(" };\n"); + printf("\n"); + + printf(" void %s::init_listener()\n", interfaceName); + printf(" {\n"); + printf(" %s_add_listener(m_%s, &m_%s_listener, this);\n", interfaceName, interfaceName, interfaceName); + printf(" }\n"); + } + } + printf("}\n"); + printf("\n"); + printf("QT_WARNING_POP\n"); + printf("QT_END_NAMESPACE\n"); + } + + return true; +} + +void Scanner::printErrors() +{ + if (m_xml->hasError()) + fprintf(stderr, "XML error: %s\nLine %lld, column %lld\n", m_xml->errorString().toLocal8Bit().constData(), m_xml->lineNumber(), m_xml->columnNumber()); +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + Scanner scanner; + + if (!scanner.parseArguments(argc, argv)) { + scanner.printUsage(); + return EXIT_FAILURE; + } + + if (!scanner.process()) { + scanner.printErrors(); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index 6cb99b4bfea..cc53bc1b549 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -134,3 +134,7 @@ if (TARGET Qt::Xml) endif() # add_subdirectory(installed_cmake) ## FIXME: Does this still make sense in this form? add_subdirectory(other) +if(QT_FEATURE_wayland) + add_subdirectory(wayland) +endif() + diff --git a/tests/auto/cmake/test_waylandclient/CMakeLists.txt b/tests/auto/cmake/test_waylandclient/CMakeLists.txt new file mode 100644 index 00000000000..1cb8d53a2b2 --- /dev/null +++ b/tests/auto/cmake/test_waylandclient/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +project(test_plugins) + +cmake_minimum_required(VERSION 3.16) +cmake_policy(SET CMP0056 NEW) + +find_package(Qt6WaylandClientPrivate REQUIRED) +add_executable(test_waylandclient_exe main.cpp) +target_link_libraries(test_waylandclient_exe Qt6::WaylandClientPrivate) diff --git a/tests/auto/cmake/test_waylandclient/main.cpp b/tests/auto/cmake/test_waylandclient/main.cpp new file mode 100644 index 00000000000..33f4470bb5f --- /dev/null +++ b/tests/auto/cmake/test_waylandclient/main.cpp @@ -0,0 +1,7 @@ +#include + +int main() +{ + // use symbol + QtWaylandClient::QWaylandCursor cursor(nullptr); +} diff --git a/tests/auto/wayland/CMakeLists.txt b/tests/auto/wayland/CMakeLists.txt new file mode 100644 index 00000000000..eba98552527 --- /dev/null +++ b/tests/auto/wayland/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from client.pro. + +add_subdirectory(shared) + +# webOS has a modified version of QtWayland and does not support e.g. multiple window creation +# in a single client, attempting to do so will cause a segmentation fault +if (NOT WEBOS) + add_subdirectory(client) + add_subdirectory(clientextension) + add_subdirectory(cursor) + add_subdirectory(datadevicev1) + add_subdirectory(fullscreenshellv1) + add_subdirectory(nooutput) + add_subdirectory(output) + add_subdirectory(primaryselectionv1) + add_subdirectory(reconnect) + add_subdirectory(seatv4) + add_subdirectory(seatv7) + add_subdirectory(seat) + add_subdirectory(surface) + add_subdirectory(tabletv2) + add_subdirectory(wl_connect) + add_subdirectory(xdgdecorationv1) + add_subdirectory(xdgoutput) + add_subdirectory(xdgshell) + add_subdirectory(scaling) +endif() +add_subdirectory(multithreaded) + +if(QT_FEATURE_im) + add_subdirectory(inputcontext) +endif() diff --git a/tests/auto/wayland/client/CMakeLists.txt b/tests/auto/wayland/client/CMakeLists.txt new file mode 100644 index 00000000000..c6495eb6bb7 --- /dev/null +++ b/tests/auto/wayland/client/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from client.pro. + +##################################################################### +## tst_client Test: +##################################################################### + +qt_internal_add_test(tst_client + SOURCES + tst_client.cpp + LIBRARIES + SharedClientTest +) + +#### Keys ignored in scope 1:.:.:client.pro:: +# check.commands = "$(TESTRUNNER)" "$${PWD}/run-with-all-shells.sh" "$(TESTARGS)" diff --git a/tests/auto/wayland/client/run-with-all-shells.sh b/tests/auto/wayland/client/run-with-all-shells.sh new file mode 100755 index 00000000000..97b348aff4c --- /dev/null +++ b/tests/auto/wayland/client/run-with-all-shells.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Copyright (C) 2018 The Qt Company Ltd +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +set -ex +$@ +env QT_WAYLAND_SHELL_INTEGRATION=wl-shell $@ +env QT_WAYLAND_SHELL_INTEGRATION=ivi-shell $@ diff --git a/tests/auto/wayland/client/tst_client.cpp b/tests/auto/wayland/client/tst_client.cpp new file mode 100644 index 00000000000..fa4a81e195f --- /dev/null +++ b/tests/auto/wayland/client/tst_client.cpp @@ -0,0 +1,612 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_CONFIG(opengl) +#include +#endif + +#include +#include +#include + +using namespace MockCompositor; + +constexpr int dataDeviceVersion = 1; + +class TestCompositor : public WlShellCompositor { +public: + explicit TestCompositor() + { + exec([this] { + m_config.autoConfigure = true; + add(dataDeviceVersion); + }); + } + DataDevice *dataDevice() { return get()->deviceFor(get()); } +}; + +class TestWindow : public QWindow +{ +public: + TestWindow() + { + setSurfaceType(QSurface::RasterSurface); + setGeometry(0, 0, 32, 32); + create(); + } + + void focusInEvent(QFocusEvent *) override + { + ++focusInEventCount; + } + + void focusOutEvent(QFocusEvent *) override + { + ++focusOutEventCount; + } + + void keyPressEvent(QKeyEvent *event) override + { + ++keyPressEventCount; + keyCode = event->nativeScanCode(); + } + + void keyReleaseEvent(QKeyEvent *event) override + { + ++keyReleaseEventCount; + keyCode = event->nativeScanCode(); + } + + void mousePressEvent(QMouseEvent *event) override + { + ++mousePressEventCount; + mousePressPos = event->position().toPoint(); + } + + void mouseReleaseEvent(QMouseEvent *) override + { + ++mouseReleaseEventCount; + } + + void touchEvent(QTouchEvent *event) override + { + Q_UNUSED(event); + ++touchEventCount; + } + + QPoint frameOffset() const { return QPoint(frameMargins().left(), frameMargins().top()); } + + int focusInEventCount = 0; + int focusOutEventCount = 0; + int keyPressEventCount = 0; + int keyReleaseEventCount = 0; + int mousePressEventCount = 0; + int mouseReleaseEventCount = 0; + int touchEventCount = 0; + + uint keyCode = 0; + QPoint mousePressPos; +}; + +#if QT_CONFIG(opengl) +class TestGlWindow : public QOpenGLWindow +{ + Q_OBJECT + +public: + TestGlWindow(); + int paintGLCalled = 0; + +public slots: + void hideShow(); + +protected: + void paintGL() override; +}; + +TestGlWindow::TestGlWindow() +{} + +void TestGlWindow::hideShow() +{ + setVisible(false); + setVisible(true); +} + +void TestGlWindow::paintGL() +{ + glClear(GL_COLOR_BUFFER_BIT); + ++paintGLCalled; +} +#endif // QT_CONFIG(opengl) + +class tst_WaylandClient : public QObject, private TestCompositor +{ + Q_OBJECT + +private slots: + void cleanup() { + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); + } + void createDestroyWindow(); + void activeWindowFollowsKeyboardFocus(); + void events(); + void backingStore(); + void touchDrag(); + void mouseDrag(); + void dontCrashOnMultipleCommits(); + void hiddenTransientParent(); + void hiddenPopupParent(); +#if QT_CONFIG(opengl) + void glWindow(); +#endif // QT_CONFIG(opengl) + void longWindowTitle(); + void longWindowTitleWithUtf16Characters(); +}; + +void tst_WaylandClient::createDestroyWindow() +{ + TestWindow window; + window.show(); + + QCOMPOSITOR_TRY_VERIFY(surface()); + + window.destroy(); + QCOMPOSITOR_TRY_VERIFY(!surface()); +} + +void tst_WaylandClient::activeWindowFollowsKeyboardFocus() +{ + TestWindow window; + window.show(); + + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); + + QCOMPOSITOR_TRY_VERIFY(window.isExposed()); + + QCOMPARE(window.focusInEventCount, 0); + exec([&] { + keyboard()->sendEnter(s); + }); + QTRY_COMPARE(window.focusInEventCount, 1); + QCOMPARE(QGuiApplication::focusWindow(), &window); + + QCOMPARE(window.focusOutEventCount, 0); + exec([&] { + keyboard()->sendLeave(s); // or implement setFocus in Keyboard + }); + QTRY_COMPARE(window.focusOutEventCount, 1); + QCOMPARE(QGuiApplication::focusWindow(), static_cast(nullptr)); +} + +void tst_WaylandClient::events() +{ + TestWindow window; + window.show(); + + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); + + QCOMPOSITOR_TRY_VERIFY(window.isExposed()); + + QCOMPARE(window.focusInEventCount, 0); + + exec([&] { + keyboard()->sendEnter(s); + }); + QTRY_COMPARE(window.focusInEventCount, 1); + QCOMPARE(QGuiApplication::focusWindow(), &window); + + // See also https://wayland.app/protocols/wayland#wl_keyboard:enum:keymap_format + // wl_keyboard::keymap_format + // keymap_format { no_keymap, xkb_v1 } + // Argument Value Description + // no_keymap 0 no keymap; client must understand how to interpret the raw keycode + // xkb_v1 1 libxkbcommon compatible; to determine the xkb keycode, clients must add 8 to the key event keycode + uint keyCode = 80; // arbitrarily chosen + QCOMPARE(window.keyPressEventCount, 0); + exec([&] { + keyboard()->sendKey(client(), keyCode - 8, Keyboard::key_state_pressed); // related with native scan code + }); + QTRY_COMPARE(window.keyPressEventCount, 1); + QCOMPARE(window.keyCode, keyCode); + + QCOMPARE(window.keyReleaseEventCount, 0); + exec([&] { + keyboard()->sendKey(client(), keyCode - 8, Keyboard::key_state_released); // related with native scan code + }); + QTRY_COMPARE(window.keyReleaseEventCount, 1); + QCOMPARE(window.keyCode, keyCode); + + const int touchId = 0; + exec([&] { + touch()->sendDown(s, window.frameOffset() + QPoint(10, 10), touchId); + }); + // Note: wl_touch.frame should not be the last event in a test until QTBUG-66563 is fixed. + // See also: QTBUG-66537 + exec([&] { + touch()->sendFrame(client()); + }); + QTRY_COMPARE(window.touchEventCount, 1); + + exec([&] { + touch()->sendUp(client(), touchId); + touch()->sendFrame(client()); + }); + QTRY_COMPARE(window.touchEventCount, 2); + + QPoint mousePressPos(16, 16); + QCOMPARE(window.mousePressEventCount, 0); + exec([&] { + pointer()->sendEnter(s, window.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendMotion(client(), window.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + pointer()->sendFrame(client()); + }); + QTRY_COMPARE(window.mousePressEventCount, 1); + QTRY_COMPARE(window.mousePressPos, mousePressPos); + + QCOMPARE(window.mouseReleaseEventCount, 0); + exec([&] { + pointer()->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + pointer()->sendFrame(client()); + }); + QTRY_COMPARE(window.mouseReleaseEventCount, 1); +} + +void tst_WaylandClient::backingStore() +{ + TestWindow window; + window.show(); + + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); + + QRect rect(QPoint(), window.size()); + + QBackingStore backingStore(&window); + backingStore.resize(rect.size()); + + backingStore.beginPaint(rect); + + QColor color = Qt::magenta; + + QPainter p(backingStore.paintDevice()); + p.fillRect(rect, color); + p.end(); + + backingStore.endPaint(); + + QVERIFY(s->m_image.isNull()); + + backingStore.flush(rect); + + QTRY_COMPARE(s->m_image.size(), window.frameGeometry().size()); + QTRY_COMPARE(s->m_image.pixel(window.frameMargins().left(), window.frameMargins().top()), color.rgba()); + + window.hide(); + + // hiding the window should destroy the surface + QCOMPOSITOR_TRY_VERIFY(!surface()); +} + +class DndWindow : public QWindow +{ + Q_OBJECT + +public: + DndWindow(QWindow *parent = nullptr) + : QWindow(parent) + { + QImage cursorImage(64,64,QImage::Format_ARGB32); + cursorImage.fill(Qt::blue); + m_dragIcon = QPixmap::fromImage(cursorImage); + } + ~DndWindow() override{} + QPoint frameOffset() const { return QPoint(frameMargins().left(), frameMargins().top()); } + bool dragStarted = false; + +protected: + void mousePressEvent(QMouseEvent *event) override + { + Q_UNUSED(event); + if (dragStarted) + return; + dragStarted = true; + + QByteArray dataBytes; + QMimeData *mimeData = new QMimeData; + mimeData->setData("application/x-dnditemdata", dataBytes); + QDrag *drag = new QDrag(this); + drag->setMimeData(mimeData); + drag->setPixmap(m_dragIcon); + drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction); + } +private: + QPixmap m_dragIcon; +}; + +class DNDTest : public QObject +{ + Q_OBJECT + +public: + DNDTest(QObject *parent = nullptr) + : QObject(parent) {} + + Surface *m_surface = nullptr; + TestCompositor *m_compositor = nullptr; + QPoint m_frameOffset; + +public slots: + void finishMouseDrag(); + void touchDrag(); +}; + +void DNDTest::finishMouseDrag() +{ + m_compositor->exec([&] { + m_compositor->dataDevice()->sendDrop(m_surface); + m_compositor->dataDevice()->sendLeave(m_surface); + }); +} + +void DNDTest::touchDrag() +{ + m_compositor->exec([&] { + m_compositor->dataDevice()->sendDataOffer(m_surface->resource()->client()); + m_compositor->dataDevice()->sendEnter(m_surface, m_frameOffset + QPoint(20, 20)); + m_compositor->dataDevice()->sendMotion(m_surface, m_frameOffset + QPoint(21, 21)); + m_compositor->dataDevice()->sendDrop(m_surface); + m_compositor->dataDevice()->sendLeave(m_surface); + }); +} + +void tst_WaylandClient::touchDrag() +{ + DndWindow window; + window.show(); + + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); + + DNDTest test; + test.m_surface = s; + test.m_compositor = this; + test.m_frameOffset = window.frameOffset(); + + exec([&] { + QObject::connect(dataDevice(), &DataDevice::dragStarted, + &test, &DNDTest::touchDrag); + }); + + exec([&] { + keyboard()->sendEnter(s); + }); + QTRY_COMPARE(QGuiApplication::focusWindow(), &window); + + const int touchId = 0; + exec([&] { + touch()->sendDown(s, window.frameOffset() + QPoint(10, 10), touchId); + touch()->sendFrame(client()); + touch()->sendMotion(client(), window.frameOffset() + QPoint(20, 20), touchId); + touch()->sendFrame(client()); + }); + + QTRY_VERIFY(window.dragStarted); +} + +void tst_WaylandClient::mouseDrag() +{ + DndWindow window; + window.show(); + + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); + + DNDTest test; + test.m_surface = s; + test.m_compositor = this; + + exec([&] { + QObject::connect(dataDevice(), &DataDevice::dragStarted, + &test, &DNDTest::finishMouseDrag); + }); + + exec([&] { + keyboard()->sendEnter(s); + }); + QTRY_COMPARE(QGuiApplication::focusWindow(), &window); + + QPoint mousePressPos(16, 16); + exec([&] { + pointer()->sendEnter(s, window.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendMotion(client(), window.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + pointer()->sendFrame(client()); + + dataDevice()->sendDataOffer(s->resource()->client()); + dataDevice()->sendEnter(s, window.frameOffset() + QPoint(20, 20)); + dataDevice()->sendMotion(s, window.frameOffset() + QPoint(21, 21)); + }); + + QTRY_VERIFY(window.dragStarted); +} + +void tst_WaylandClient::dontCrashOnMultipleCommits() +{ + auto window = new TestWindow(); + window->show(); + + QRect rect(QPoint(), window->size()); + + { + QBackingStore backingStore(window); + backingStore.resize(rect.size()); + backingStore.beginPaint(rect); + QPainter p(backingStore.paintDevice()); + p.fillRect(rect, Qt::magenta); + p.end(); + backingStore.endPaint(); + + backingStore.flush(rect); + backingStore.flush(rect); + backingStore.flush(rect); + + QCOMPOSITOR_TRY_VERIFY(surface()); + } + + delete window; + QCOMPOSITOR_TRY_VERIFY(!surface()); +} + +void tst_WaylandClient::hiddenTransientParent() +{ + QWindow parent; + QWindow transient; + + transient.setTransientParent(&parent); + + parent.show(); + QCOMPOSITOR_TRY_VERIFY(surface()); + + parent.hide(); + QCOMPOSITOR_TRY_VERIFY(!surface()); + + transient.show(); + QCOMPOSITOR_TRY_VERIFY(surface()); +} +void tst_WaylandClient::hiddenPopupParent() +{ + TestWindow toplevel; + toplevel.show(); + + // wl_shell relies on a mouse event in order to send a serial and seat + // with the set_popup request. + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); + QCOMPOSITOR_TRY_VERIFY(toplevel.isExposed()); + + QPoint mousePressPos(16, 16); + QCOMPARE(toplevel.mousePressEventCount, 0); + exec([&] { + pointer()->sendEnter(s, toplevel.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendMotion(client(), toplevel.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + pointer()->sendFrame(client()); + }); + QTRY_COMPARE(toplevel.mousePressEventCount, 1); + + QWindow popup; + popup.setTransientParent(&toplevel); + popup.setFlag(Qt::Popup, true); + + toplevel.hide(); + QCOMPOSITOR_TRY_VERIFY(!surface()); + + popup.show(); + QCOMPOSITOR_TRY_VERIFY(surface()); +} + +#if QT_CONFIG(opengl) +void tst_WaylandClient::glWindow() +{ + QSKIP("Skipping GL tests, as not supported by all CI systems: See https://bugreports.qt.io/browse/QTBUG-65802"); + + QScopedPointer testWindow(new TestGlWindow); + testWindow->show(); + Surface *s = nullptr; + QCOMPOSITOR_TRY_VERIFY(s = surface()); + exec([&] { + sendShellSurfaceConfigure(s); + }); + + QTRY_COMPARE(testWindow->paintGLCalled, 1); + + //QTBUG-63411 + QMetaObject::invokeMethod(testWindow.data(), "hideShow", Qt::QueuedConnection); + testWindow->requestUpdate(); + QTRY_COMPARE(testWindow->paintGLCalled, 2); + + testWindow->requestUpdate(); + QTRY_COMPARE(testWindow->paintGLCalled, 3); + + //confirm we don't crash when we delete an already hidden GL window + //QTBUG-65553 + testWindow->setVisible(false); + QCOMPOSITOR_TRY_VERIFY(!surface()); +} +#endif // QT_CONFIG(opengl) + +void tst_WaylandClient::longWindowTitle() +{ + // See QTBUG-68715 + QWindow window; + QString absurdlyLongTitle(10000, QLatin1Char('z')); + window.setTitle(absurdlyLongTitle); + window.show(); + QCOMPOSITOR_TRY_VERIFY(surface()); +} + +void tst_WaylandClient::longWindowTitleWithUtf16Characters() +{ + QWindow window; + QString absurdlyLongTitle = QString("三").repeated(10000); + Q_ASSERT(absurdlyLongTitle.size() == 10000); // just making sure the test isn't broken + window.setTitle(absurdlyLongTitle); + window.show(); + QCOMPOSITOR_TRY_VERIFY(surface()); +} + +int main(int argc, char **argv) +{ + QTemporaryDir tmpRuntimeDir; + setenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit(), 1); + setenv("QT_QPA_PLATFORM", "wayland", 1); // force QGuiApplication to use wayland plugin + QString shell = QString::fromLocal8Bit(qgetenv("QT_WAYLAND_SHELL_INTEGRATION")); + if (shell.isEmpty()) + setenv("QT_WAYLAND_SHELL_INTEGRATION", "wl-shell", 1); + + tst_WaylandClient tc; + QGuiApplication app(argc, argv); + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&tc, argc, argv); +} + +#include + diff --git a/tests/auto/wayland/clientextension/CMakeLists.txt b/tests/auto/wayland/clientextension/CMakeLists.txt new file mode 100644 index 00000000000..063f3f4740b --- /dev/null +++ b/tests/auto/wayland/clientextension/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_clientextension + SOURCES + tst_clientextension.cpp + LIBRARIES + SharedClientTest +) + +qt6_generate_wayland_protocol_client_sources(tst_clientextension + PRIVATE_CODE + FILES ${CMAKE_CURRENT_SOURCE_DIR}/test.xml +) + +qt6_generate_wayland_protocol_server_sources(tst_clientextension + PRIVATE_CODE + FILES ${CMAKE_CURRENT_SOURCE_DIR}/test.xml +) diff --git a/tests/auto/wayland/clientextension/test.xml b/tests/auto/wayland/clientextension/test.xml new file mode 100644 index 00000000000..f8d5b4eac4e --- /dev/null +++ b/tests/auto/wayland/clientextension/test.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/auto/wayland/clientextension/tst_clientextension.cpp b/tests/auto/wayland/clientextension/tst_clientextension.cpp new file mode 100644 index 00000000000..8dd4e0d98f6 --- /dev/null +++ b/tests/auto/wayland/clientextension/tst_clientextension.cpp @@ -0,0 +1,134 @@ +// Copyright (C) 2021 David Redondo +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include "mockcompositor.h" +#include "coreprotocol.h" + +using namespace MockCompositor; + +class TestExtension + : public QWaylandClientExtensionTemplate, + public QtWayland::test_interface +{ +public: + TestExtension() : QWaylandClientExtensionTemplate(1){}; + void initialize() { QWaylandClientExtension::initialize(); } +}; + +class TestGlobal : public Global, public QtWaylandServer::test_interface +{ + Q_OBJECT +public: + explicit TestGlobal(CoreCompositor *compositor) + : QtWaylandServer::test_interface(compositor->m_display, 1) + { + } +}; + +class tst_clientextension : public QObject, private CoreCompositor +{ + Q_OBJECT +private: + QtWaylandClient::QWaylandDisplay *display() + { + return static_cast( + QGuiApplicationPrivate::platformIntegration()) + ->display(); + } +private slots: + void cleanup() + { + display()->flushRequests(); + dispatch(); + exec([this] { removeAll(); }); + QTRY_COMPARE(display()->globals().size(), 0); + } + void createWithoutGlobal(); + void createWithGlobalAutomatic(); + void createWithGlobalManual(); + void globalBecomesAvailable(); + void globalRemoved(); +}; + +void tst_clientextension::createWithoutGlobal() +{ + TestExtension extension; + QSignalSpy spy(&extension, &QWaylandClientExtension::activeChanged); + QVERIFY(spy.isValid()); + QVERIFY(!extension.isActive()); + QCOMPARE(spy.size(), 0); + extension.initialize(); + QVERIFY(!extension.isActive()); + QCOMPARE(spy.size(), 0); +} + +void tst_clientextension::createWithGlobalAutomatic() +{ + exec([this] { add(); }); + QTRY_COMPARE(display()->globals().size(), 1); + TestExtension extension; + QSignalSpy spy(&extension, &QWaylandClientExtension::activeChanged); + QVERIFY(spy.isValid()); + QTRY_VERIFY(extension.isActive()); + QCOMPARE(spy.size(), 1); +} + +void tst_clientextension::createWithGlobalManual() +{ + exec([this] { add(); }); + QTRY_COMPARE(display()->globals().size(), 1); + // Wait for the display to have the global + TestExtension extension; + QSignalSpy spy(&extension, &QWaylandClientExtension::activeChanged); + QVERIFY(spy.isValid()); + extension.initialize(); + QVERIFY(extension.isActive()); + QCOMPARE(spy.size(), 1); +} + +void tst_clientextension::globalBecomesAvailable() +{ + TestExtension extension; + QSignalSpy spy(&extension, &QWaylandClientExtension::activeChanged); + QVERIFY(spy.isValid()); + exec([this] { add(); }); + QTRY_VERIFY(extension.isActive()); + QCOMPARE(spy.size(), 1); +} + +void tst_clientextension::globalRemoved() +{ + exec([this] { add(); }); + TestExtension extension; + QTRY_VERIFY(extension.isActive()); + QSignalSpy spy(&extension, &QWaylandClientExtension::activeChanged); + QVERIFY(spy.isValid()); + QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().size(), 1); + + exec([this] { removeAll(); }); + QTRY_VERIFY(!extension.isActive()); + QCOMPARE(spy.size(), 1); +} + +int main(int argc, char **argv) +{ + QTemporaryDir tmpRuntimeDir; + setenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit(), 1); + setenv("QT_QPA_PLATFORM", "wayland", 1); + setenv("QT_WAYLAND_DONT_CHECK_SHELL_INTEGRATION", "1", 1); + + tst_clientextension tc; + QGuiApplication app(argc, argv); + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_clientextension.moc" diff --git a/tests/auto/wayland/cursor/CMakeLists.txt b/tests/auto/wayland/cursor/CMakeLists.txt new file mode 100644 index 00000000000..a7814a2c2b9 --- /dev/null +++ b/tests/auto/wayland/cursor/CMakeLists.txt @@ -0,0 +1,11 @@ +##################################################################### +## tst_cursor Test: +##################################################################### + +qt_internal_add_test(tst_wayland_cursor + SOURCES + tst_cursor.cpp + cursorshapev1.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/cursor/cursorshapev1.cpp b/tests/auto/wayland/cursor/cursorshapev1.cpp new file mode 100644 index 00000000000..93750df9501 --- /dev/null +++ b/tests/auto/wayland/cursor/cursorshapev1.cpp @@ -0,0 +1,47 @@ +// Copyright (C) 2023 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "cursorshapev1.h" + +namespace MockCompositor { + +CursorShapeManager::CursorShapeManager(CoreCompositor *compositor, int version) + : QtWaylandServer::wp_cursor_shape_manager_v1(compositor->m_display, version) +{ +} + +void CursorShapeManager::wp_cursor_shape_manager_v1_get_pointer(Resource *resource, uint32_t id, wl_resource *pointer) +{ + auto *p = fromResource(pointer); + auto *cursorShape = new CursorShapeDevice(p, resource->client(), id, resource->version()); + connect(cursorShape, &QObject::destroyed, this, [this, cursorShape]() { + m_cursorDevices.removeOne(cursorShape); + }); + m_cursorDevices << cursorShape; +} + +CursorShapeDevice::CursorShapeDevice(Pointer *pointer, wl_client *client, int id, int version) + : QtWaylandServer::wp_cursor_shape_device_v1(client, id, version) + , m_pointer(pointer) +{ +} + +void CursorShapeDevice::wp_cursor_shape_device_v1_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + delete this; +} + +void CursorShapeDevice::wp_cursor_shape_device_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void CursorShapeDevice::wp_cursor_shape_device_v1_set_shape(Resource *resource, uint32_t serial, uint32_t shape) +{ + Q_UNUSED(resource); + m_currentShape = static_cast(shape); + emit setCursor(serial); +} + +} diff --git a/tests/auto/wayland/cursor/cursorshapev1.h b/tests/auto/wayland/cursor/cursorshapev1.h new file mode 100644 index 00000000000..0befc322318 --- /dev/null +++ b/tests/auto/wayland/cursor/cursorshapev1.h @@ -0,0 +1,44 @@ +// Copyright (C) 2023 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_CURSORSHAPE_H +#define MOCKCOMPOSITOR_CURSORSHAPE_H + +#include "coreprotocol.h" +#include + +namespace MockCompositor { + +class CursorShapeDevice; + +class CursorShapeManager : public Global, public QtWaylandServer::wp_cursor_shape_manager_v1 +{ + Q_OBJECT +public: + explicit CursorShapeManager(CoreCompositor *compositor, int version = 1); + QList m_cursorDevices; + +protected: + void wp_cursor_shape_manager_v1_get_pointer(Resource *resource, uint32_t id, wl_resource *pointer) override; +}; + +class CursorShapeDevice : public QObject, public QtWaylandServer::wp_cursor_shape_device_v1 +{ + Q_OBJECT +public: + explicit CursorShapeDevice(Pointer *pointer, wl_client *client, int id, int version); + Pointer *m_pointer; + shape m_currentShape = shape_default; + +Q_SIGNALS: + void setCursor(uint serial); + +protected: + void wp_cursor_shape_device_v1_destroy_resource(Resource *resource) override; + void wp_cursor_shape_device_v1_destroy(Resource *resource) override; + void wp_cursor_shape_device_v1_set_shape(Resource *resource, uint32_t serial, uint32_t shape) override; +}; + +} + +#endif diff --git a/tests/auto/wayland/cursor/tst_cursor.cpp b/tests/auto/wayland/cursor/tst_cursor.cpp new file mode 100644 index 00000000000..070e062f6e3 --- /dev/null +++ b/tests/auto/wayland/cursor/tst_cursor.cpp @@ -0,0 +1,103 @@ +// Copyright (C) 2023 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include +#include +#include +#include + +#include "cursorshapev1.h" + +using namespace MockCompositor; + +class tst_cursor : public QObject, private DefaultCompositor +{ + Q_OBJECT +public: + tst_cursor(); + CursorShapeDevice* cursorShape(); +private slots: + void init(); + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void setCursor(); +}; + +tst_cursor::tst_cursor() +{ + exec([this] { + m_config.autoConfigure = true; + add(1); + }); +} + +CursorShapeDevice* tst_cursor::cursorShape() +{ + auto manager = get(); + if (!manager->m_cursorDevices.count()) + return nullptr; + return manager->m_cursorDevices[0]; +} + +void tst_cursor::init() +{ + setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1); +} + +void tst_cursor::setCursor() +{ + QCOMPOSITOR_TRY_VERIFY(cursorShape()); + QSignalSpy setCursorSpy(exec([&] { return pointer(); }), &Pointer::setCursor); + QSignalSpy setCursorShapeSpy(exec([&] { return cursorShape(); }), &CursorShapeDevice::setCursor); + + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + uint enterSerial = exec([&] { + return pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); + }); + setCursorShapeSpy.wait(); + // verify we got given a cursor on enter + QCOMPOSITOR_COMPARE(cursorShape()->m_currentShape, CursorShapeDevice::shape_default); + QVERIFY(setCursorSpy.isEmpty()); + QCOMPARE(setCursorShapeSpy.takeFirst().at(0).toUInt(), enterSerial); + + // client sets a different shape + window.setCursor(QCursor(Qt::WaitCursor)); + QVERIFY(setCursorShapeSpy.wait()); + QCOMPOSITOR_COMPARE(cursorShape()->m_currentShape, CursorShapeDevice::shape_wait); + + setCursorShapeSpy.clear(); + + // client hides the cursor + // CursorShape will not be used, instead, it uses the old path + window.setCursor(QCursor(Qt::BlankCursor)); + QVERIFY(setCursorSpy.wait()); + QVERIFY(setCursorShapeSpy.isEmpty()); + QCOMPOSITOR_VERIFY(!pointer()->cursorSurface()); + + // same for bitmaps + QPixmap myCustomPixmap(10, 10); + myCustomPixmap.fill(Qt::red); + window.setCursor(QCursor(myCustomPixmap)); + QVERIFY(setCursorSpy.wait()); + QVERIFY(setCursorShapeSpy.isEmpty()); + + // set a shape again + window.setCursor(QCursor(Qt::BusyCursor)); + QVERIFY(setCursorShapeSpy.wait()); + QCOMPOSITOR_COMPARE(cursorShape()->m_currentShape, CursorShapeDevice::shape_progress); + + setCursorShapeSpy.clear(); + + // set the same bitmap again, make sure switching from new to old path works + // even if the bitmap cursor's properties haven't changed + window.setCursor(QCursor(myCustomPixmap)); + QVERIFY(setCursorSpy.wait()); + QVERIFY(setCursorShapeSpy.isEmpty()); +} + +QCOMPOSITOR_TEST_MAIN(tst_cursor) +#include "tst_cursor.moc" diff --git a/tests/auto/wayland/datadevicev1/CMakeLists.txt b/tests/auto/wayland/datadevicev1/CMakeLists.txt new file mode 100644 index 00000000000..cfc2f5beb91 --- /dev/null +++ b/tests/auto/wayland/datadevicev1/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from datadevicev1.pro. + +##################################################################### +## tst_datadevicev1 Test: +##################################################################### + +qt_internal_add_test(tst_datadevicev1 + SOURCES + tst_datadevicev1.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/datadevicev1/tst_datadevicev1.cpp b/tests/auto/wayland/datadevicev1/tst_datadevicev1.cpp new file mode 100644 index 00000000000..d225f7374f5 --- /dev/null +++ b/tests/auto/wayland/datadevicev1/tst_datadevicev1.cpp @@ -0,0 +1,323 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include +#include +#include + +using namespace MockCompositor; + +constexpr int dataDeviceVersion = 3; + +class DataDeviceCompositor : public DefaultCompositor { +public: + explicit DataDeviceCompositor() + { + exec([this] { + m_config.autoConfigure = true; + add(dataDeviceVersion); + }); + } + DataDevice *dataDevice() { return get()->deviceFor(get()); } +}; + +class tst_datadevicev1 : public QObject, private DataDeviceCompositor +{ + Q_OBJECT +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void initTestCase(); + void pasteAscii(); + void pasteUtf8(); + void pasteMozUrl(); + void pasteSingleUtf8MozUrl(); + void destroysPreviousSelection(); + void destroysSelectionWithSurface(); + void destroysSelectionOnLeave(); + void dragWithoutFocus(); +}; + +void tst_datadevicev1::initTestCase() +{ + QCOMPOSITOR_TRY_VERIFY(pointer()); + QCOMPOSITOR_TRY_VERIFY(keyboard()); + + QCOMPOSITOR_TRY_VERIFY(dataDevice()); + QCOMPOSITOR_TRY_VERIFY(dataDevice()->resourceMap().contains(client())); + QCOMPOSITOR_TRY_COMPARE(dataDevice()->resourceMap().value(client())->version(), dataDeviceVersion); +} + +void tst_datadevicev1::pasteAscii() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *) override { m_text = QGuiApplication::clipboard()->text(); } + QString m_text; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *client = xdgSurface()->resource()->client(); + auto *offer = dataDevice()->sendDataOffer(client, {"text/plain"}); + connect(offer, &DataOffer::receive, offer, [](QString mimeType, int fd) { + QFile file; + QVERIFY(file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle)); + QCOMPARE(mimeType, "text/plain"); + file.write(QByteArray("normal ascii")); + file.close(); + }, Qt::DirectConnection); + dataDevice()->sendSelection(offer); + + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 1); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 0); + pointer()->sendFrame(client); + }); + QTRY_COMPARE(window.m_text, "normal ascii"); +} + +void tst_datadevicev1::pasteUtf8() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *) override { m_text = QGuiApplication::clipboard()->text(); } + QString m_text; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *client = xdgSurface()->resource()->client(); + auto *offer = dataDevice()->sendDataOffer(client, {"text/plain", "text/plain;charset=utf-8"}); + connect(offer, &DataOffer::receive, offer, [](QString mimeType, int fd) { + QFile file; + QVERIFY(file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle)); + QCOMPARE(mimeType, "text/plain;charset=utf-8"); + file.write(QByteArray("face with tears of joy: 😂")); + file.close(); + }, Qt::DirectConnection); + dataDevice()->sendSelection(offer); + + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 1); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 0); + pointer()->sendFrame(client); + }); + QTRY_COMPARE(window.m_text, "face with tears of joy: 😂"); +} + +void tst_datadevicev1::pasteMozUrl() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *) override { m_urls = QGuiApplication::clipboard()->mimeData()->urls(); } + QList m_urls; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *client = xdgSurface()->resource()->client(); + auto *offer = dataDevice()->sendDataOffer(client, {"text/x-moz-url"}); + connect(offer, &DataOffer::receive, offer, [](QString mimeType, int fd) { + QFile file; + QVERIFY(file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle)); + QCOMPARE(mimeType, "text/x-moz-url"); + const QString content("https://www.qt.io/\nQt\nhttps://www.example.com/\nExample Website"); + // Need UTF-16. + file.write(reinterpret_cast(content.data()), content.size() * 2); + file.close(); + }, Qt::DirectConnection); + dataDevice()->sendSelection(offer); + + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 1); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 0); + pointer()->sendFrame(client); + }); + + QTRY_COMPARE(window.m_urls.count(), 2); + QCOMPARE(window.m_urls.at(0), QUrl("https://www.qt.io/")); + QCOMPARE(window.m_urls.at(1), QUrl("https://www.example.com/")); +} + +void tst_datadevicev1::pasteSingleUtf8MozUrl() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *) override { m_urls = QGuiApplication::clipboard()->mimeData()->urls(); } + QList m_urls; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *client = xdgSurface()->resource()->client(); + auto *offer = dataDevice()->sendDataOffer(client, {"text/x-moz-url"}); + connect(offer, &DataOffer::receive, offer, [](QString mimeType, int fd) { + QFile file; + QVERIFY(file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle)); + QCOMPARE(mimeType, "text/x-moz-url"); + const QString content("https://www.qt.io/"); + file.write(content.toUtf8()); + file.close(); + }, Qt::DirectConnection); + dataDevice()->sendSelection(offer); + + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 1); + pointer()->sendFrame(client); + pointer()->sendButton(client, BTN_LEFT, 0); + pointer()->sendFrame(client); + }); + + QTRY_COMPARE(window.m_urls.count(), 1); + QCOMPARE(window.m_urls.at(0), QUrl("https://www.qt.io/")); +} + +void tst_datadevicev1::destroysPreviousSelection() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + // When the client receives a selection event, it is required to destroy the previous offer + exec([&] { + QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 0); + auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"}); + dataDevice()->sendSelection(offer); + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 1); + }); + + exec([&] { + auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"}); + dataDevice()->sendSelection(offer); + QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 2); + }); + + // Verify the first offer gets destroyed + QCOMPOSITOR_TRY_COMPARE(dataDevice()->m_sentSelectionOffers.size(), 1); + + exec([&] { + auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"}); + dataDevice()->sendSelection(offer); + auto *surface = xdgSurface()->m_surface; + keyboard()->sendLeave(surface); + }); + + // Clients are required to destroy their offer when losing keyboard focus + QCOMPOSITOR_TRY_COMPARE(dataDevice()->m_sentSelectionOffers.size(), 0); +} + +void tst_datadevicev1::destroysSelectionWithSurface() +{ + auto *window = new QRasterWindow; + window->resize(64, 64); + window->show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + // When the client receives a selection event, it is required to destroy the previous offer + exec([&] { + QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 0); + auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"}); + dataDevice()->sendSelection(offer); + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 1); + }); + + // Ping to make sure we receive the wl_keyboard enter and leave events, before destroying the + // surface. Otherwise, the client will receive enter and leave events with a destroyed (null) + // surface, which is not what we are trying to test for here. + xdgPingAndWaitForPong(); + window->destroy(); + + QCOMPOSITOR_TRY_COMPARE(dataDevice()->m_sentSelectionOffers.size(), 0); +} + +void tst_datadevicev1::destroysSelectionOnLeave() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"}); + dataDevice()->sendSelection(offer); + + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + }); + + QTRY_VERIFY(QGuiApplication::clipboard()->mimeData(QClipboard::Clipboard)); + QTRY_VERIFY(QGuiApplication::clipboard()->mimeData(QClipboard::Clipboard)->hasText()); + + QSignalSpy dataChangedSpy(QGuiApplication::clipboard(), &QClipboard::dataChanged); + + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendLeave(surface); + }); + + QTRY_COMPARE(dataChangedSpy.size(), 1); + QVERIFY(!QGuiApplication::clipboard()->mimeData(QClipboard::Clipboard)->hasText()); +} + +// The application should not crash if it attempts to start a drag operation +// when it doesn't have input focus (QTBUG-76368) +void tst_datadevicev1::dragWithoutFocus() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + auto *mimeData = new QMimeData; + const QByteArray data("testData"); + mimeData->setData("text/plain", data); + QDrag drag(&window); + drag.setMimeData(mimeData); + drag.exec(); +} + +QCOMPOSITOR_TEST_MAIN(tst_datadevicev1) +#include "tst_datadevicev1.moc" diff --git a/tests/auto/wayland/fullscreenshellv1/CMakeLists.txt b/tests/auto/wayland/fullscreenshellv1/CMakeLists.txt new file mode 100644 index 00000000000..7bc14c50d04 --- /dev/null +++ b/tests/auto/wayland/fullscreenshellv1/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from fullscreenshellv1.pro. + +##################################################################### +## tst_client_fullscreenshellv1 Test: +##################################################################### + +qt_internal_add_test(tst_client_fullscreenshellv1 + SOURCES + tst_fullscreenshellv1.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/fullscreenshellv1/tst_fullscreenshellv1.cpp b/tests/auto/wayland/fullscreenshellv1/tst_fullscreenshellv1.cpp new file mode 100644 index 00000000000..ba897d53f21 --- /dev/null +++ b/tests/auto/wayland/fullscreenshellv1/tst_fullscreenshellv1.cpp @@ -0,0 +1,49 @@ +// Copyright (C) 2021 David Edmundson +// Copyright (C) 2018 Pier Luigi Fiorini +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include + +#include + +using namespace MockCompositor; + +class tst_WaylandClientFullScreenShellV1 : public QObject, private DefaultCompositor +{ + Q_OBJECT + +private slots: + void createDestroyWindow(); +}; + +void tst_WaylandClientFullScreenShellV1::createDestroyWindow() +{ + QRasterWindow window; + window.resize(800, 600); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(fullScreenShellV1()->surfaces().size() == 1); + QCOMPOSITOR_VERIFY(surface(0)); + + window.destroy(); + QCOMPOSITOR_TRY_VERIFY(!surface(0)); +} + +int main(int argc, char **argv) +{ + QTemporaryDir tmpRuntimeDir; + setenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit(), 1); + setenv("QT_QPA_PLATFORM", "wayland", 1); // force QGuiApplication to use wayland plugin + setenv("QT_WAYLAND_SHELL_INTEGRATION", "fullscreen-shell-v1", 1); + setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1); // window decorations don't make much sense here + + tst_WaylandClientFullScreenShellV1 tc; + QGuiApplication app(argc, argv); + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&tc, argc, argv); +} + +#include diff --git a/tests/auto/wayland/inputcontext/CMakeLists.txt b/tests/auto/wayland/inputcontext/CMakeLists.txt new file mode 100644 index 00000000000..66e5ca825fc --- /dev/null +++ b/tests/auto/wayland/inputcontext/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from inputcontext.pro. + +##################################################################### +## tst_inputcontext Test: +##################################################################### + +qt_internal_add_test(tst_inputcontext + SOURCES + tst_inputcontext.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/inputcontext/tst_inputcontext.cpp b/tests/auto/wayland/inputcontext/tst_inputcontext.cpp new file mode 100644 index 00000000000..47a453bb5ee --- /dev/null +++ b/tests/auto/wayland/inputcontext/tst_inputcontext.cpp @@ -0,0 +1,230 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include "textinput.h" +#include "qttextinput.h" + +#include +#include + +#include +#include +#include +#include + +#include + +using namespace MockCompositor; + +class tst_inputcontext : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void initTestCase(); + void selectingInputContext_data(); + void selectingInputContext(); + void selectingTextInputProtocol_data(); + void selectingTextInputProtocol(); + void inputContextReconfigurationWhenTogglingTextInputExtension(); + +private: + QByteArray inputContextName() const; + + template + void ensurePresentOnCompositor() + { + exec([&] { + QList extensions = getAll(); + if (extensions.size() > 1) + QFAIL("Requested type is a singleton, hence there should not be more then one object returned"); + if (extensions.size() == 0) + add(); + }); + } + + template + void ensureNotPresentOnCompositor() + { + exec([&] { + QList extensions = getAll(); + if (extensions.size() > 1) + QFAIL("Requested type is a singleton, hence there should not be more then one object returned"); + if (extensions.size() == 1) + remove(extensions.first()); + }); + } + + QByteArray mComposeModule = QByteArray("QComposeInputContext"); // default input context + QByteArray mIbusModule = QByteArray("QIBusPlatformInputContext"); + QByteArray mTextInputModule = QByteArray("QtWaylandClient::QWaylandInputContext"); + QByteArray mQtTextInputModule = QByteArray("QtWaylandClient::QWaylandInputMethodContext"); +}; + +void tst_inputcontext::initTestCase() +{ + // Verify that plugins are present and valid + QPlatformInputContext *context = QPlatformInputContextFactory::create(QStringLiteral("compose")); + QVERIFY(context && context->isValid()); + + context = QPlatformInputContextFactory::create(QStringLiteral("ibus")); + // The ibus plugin depends on properly configured system services, if plugin is not valid + // verify that wayland qpa plugin properly fallbacks to default input context. + if (!context || !context->isValid()) + mIbusModule = mComposeModule; +} + +QByteArray tst_inputcontext::inputContextName() const +{ + QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration(); + if (platformIntegration->inputContext()) + return platformIntegration->inputContext()->metaObject()->className(); + + return QByteArray(""); +} + +void tst_inputcontext::selectingInputContext_data() +{ + QTest::addColumn("requestedModule"); + QTest::addColumn("expectedModule"); + + // Test compositor without Text Input extension + QTest::newRow("ibus") << QByteArray("ibus") << mIbusModule; + QTest::newRow("compose") << QByteArray("compose") << mComposeModule; + QTest::newRow("empty") << QByteArray("") << mComposeModule; + QTest::newRow("null") << QByteArray() << mComposeModule; + QTest::newRow("fake") << QByteArray("fake") << mComposeModule; + + // Test compositor with Text Input extension + QTest::newRow("ibus:text-input") << QByteArray("ibus") << mIbusModule; + QTest::newRow("compose:text-input") << QByteArray("compose") << mComposeModule; + QTest::newRow("empty:text-input") << QByteArray("") << mTextInputModule; + QTest::newRow("null:text-input") << QByteArray() << mTextInputModule; + QTest::newRow("wayland:text-input") << QByteArray("wayland") << mTextInputModule; + QTest::newRow("fake:text-input") << QByteArray("fake") << mComposeModule; +} + +void tst_inputcontext::selectingInputContext() +{ + QFETCH(QByteArray, requestedModule); + QFETCH(QByteArray, expectedModule); + + if (requestedModule.isNull()) + qunsetenv("QT_IM_MODULE"); + else + qputenv("QT_IM_MODULE", requestedModule); + + const bool withTextInputAtCompositorSide = QByteArray(QTest::currentDataTag()).endsWith(":text-input"); + + if (withTextInputAtCompositorSide) + ensurePresentOnCompositor(); + else + ensureNotPresentOnCompositor(); + + int argc = 0; + QGuiApplication app(argc, nullptr); // loads the platform plugin + + QCOMPARE(inputContextName(), expectedModule); +} + +void tst_inputcontext::selectingTextInputProtocol_data() +{ + QTest::addColumn("requestQtTextInput"); + QTest::addColumn("requestTextInput"); + QTest::addColumn("clientProtocol"); + QTest::addColumn("expectedModule"); + + QTest::newRow("1-1") << true << true << QByteArray() << mQtTextInputModule; + QTest::newRow("1-2") << true << false << QByteArray() << mQtTextInputModule; + QTest::newRow("1-3") << false << true << QByteArray() << mTextInputModule; + QTest::newRow("1-4") << false << false << QByteArray() << mComposeModule; + + QTest::newRow("2-1") << true << true << QByteArray("zwp_text_input_v2") << mTextInputModule; + QTest::newRow("2-2") << true << false << QByteArray("zwp_text_input_v2") << mComposeModule; + QTest::newRow("2-3") << false << true << QByteArray("zwp_text_input_v2") << mTextInputModule; + QTest::newRow("2-4") << false << false << QByteArray("zwp_text_input_v2") << mComposeModule; + + QTest::newRow("3-1") << true << true << QByteArray("qt_text_input_method_v1") << mQtTextInputModule; + QTest::newRow("3-2") << true << false << QByteArray("qt_text_input_method_v1") << mQtTextInputModule; + QTest::newRow("3-3") << false << true << QByteArray("qt_text_input_method_v1") << mComposeModule; + QTest::newRow("3-4") << false << false << QByteArray("qt_text_input_method_v1") << mComposeModule; + + QTest::newRow("4-1") << true << true << QByteArray("qt_text_input_method_v1;zwp_text_input_v2") << mQtTextInputModule; + QTest::newRow("4-2") << true << false << QByteArray("qt_text_input_method_v1;zwp_text_input_v2") << mQtTextInputModule; + QTest::newRow("4-3") << false << true << QByteArray("qt_text_input_method_v1;zwp_text_input_v2") << mTextInputModule; + QTest::newRow("4-4") << false << false << QByteArray("qt_text_input_method_v1;zwp_text_input_v2") << mComposeModule; + + QTest::newRow("5-1") << true << true << QByteArray("zwp_text_input_v2;qt_text_input_method_v1") << mTextInputModule; + QTest::newRow("5-2") << true << false << QByteArray("zwp_text_input_v2;qt_text_input_method_v1") << mQtTextInputModule; + QTest::newRow("5-3") << false << true << QByteArray("zwp_text_input_v2;qt_text_input_method_v1") << mTextInputModule; + QTest::newRow("5-4") << false << false << QByteArray("zwp_text_input_v2;qt_text_input_method_v1") << mComposeModule; +} + +void tst_inputcontext::selectingTextInputProtocol() +{ + QFETCH(bool, requestQtTextInput); + QFETCH(bool, requestTextInput); + QFETCH(QByteArray, clientProtocol); + QFETCH(QByteArray, expectedModule); + + exec([] { + qputenv("QT_IM_MODULE", "qtvirtualkeyboard"); + }); + + qunsetenv("QT_IM_MODULE"); + + if (clientProtocol.isNull()) + qunsetenv("QT_WAYLAND_TEXT_INPUT_PROTOCOL"); + else + qputenv("QT_WAYLAND_TEXT_INPUT_PROTOCOL", clientProtocol); + + if (requestTextInput) + ensurePresentOnCompositor(); + else + ensureNotPresentOnCompositor(); + + if (requestQtTextInput) + ensurePresentOnCompositor(); + else + ensureNotPresentOnCompositor(); + + int argc = 0; + QGuiApplication app(argc, nullptr); // loads the platform plugin + + QCOMPARE(inputContextName(), expectedModule); +} + +void tst_inputcontext::inputContextReconfigurationWhenTogglingTextInputExtension() +{ + qunsetenv("QT_IM_MODULE"); + + ensurePresentOnCompositor(); + int argc = 0; + QGuiApplication app(argc, nullptr); // loads the platform plugin + QCOMPARE(inputContextName(), mTextInputModule); + + // remove text input extension after the platform plugin has been loaded + ensureNotPresentOnCompositor(); + // QTRY_* because we need to spin the event loop for wayland QPA plugin + // to handle registry_global_remove() + QTRY_COMPARE(inputContextName(), mComposeModule); + + // add text input extension after the platform plugin has been loaded + ensurePresentOnCompositor(); + // QTRY_* because we need to spin the event loop for wayland QPA plugin + // to handle registry_global() + QTRY_COMPARE(inputContextName(), mTextInputModule); +} + +int main(int argc, char *argv[]) +{ + QTemporaryDir tmpRuntimeDir; + qputenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit()); + qputenv("QT_QPA_PLATFORM", "wayland"); + + tst_inputcontext tc; + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_inputcontext.moc" diff --git a/tests/auto/wayland/multithreaded/CMakeLists.txt b/tests/auto/wayland/multithreaded/CMakeLists.txt new file mode 100644 index 00000000000..62d93590517 --- /dev/null +++ b/tests/auto/wayland/multithreaded/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from multithreaded.pro. + +##################################################################### +## tst_multithreaded Test: +##################################################################### + +qt_internal_add_test(tst_multithreaded + SOURCES + tst_multithreaded.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/multithreaded/tst_multithreaded.cpp b/tests/auto/wayland/multithreaded/tst_multithreaded.cpp new file mode 100644 index 00000000000..eb9f56205b3 --- /dev/null +++ b/tests/auto/wayland/multithreaded/tst_multithreaded.cpp @@ -0,0 +1,139 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// Copyright (C) 2020 UBports Foundataion. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include +#include + +#include + +#include +#include +#include + +#include "mockcompositor.h" + +using namespace MockCompositor; + +/* + * This class simulate a thread from another library which use poll() to wait + * for data from Wayland compositor. + */ + +class ExternalWaylandReaderThread : public QThread +{ +public: + ExternalWaylandReaderThread(struct wl_display *disp) + : QThread() + , m_disp(disp) + { + setObjectName(QStringLiteral("ExternalWaylandReader")); + } + + ~ExternalWaylandReaderThread() + { + if (m_pipefd[1] != -1 && write(m_pipefd[1], "q", 1) == -1) + qWarning("Failed to write to the pipe: %s.", strerror(errno)); + + wait(); + } + +protected: + void run() override + { + // we use this pipe to make the loop exit otherwise if we simply used a flag on the loop condition, if stop() gets + // called while poll() is blocking the thread will never quit since there are no wayland messages coming anymore. + struct Pipe + { + Pipe(int *fds) + : fds(fds) + { + if (::pipe(fds) != 0) + qWarning("Pipe creation failed. Quitting may hang."); + } + ~Pipe() + { + if (fds[0] != -1) { + close(fds[0]); + close(fds[1]); + } + } + + int *fds; + } pipe(m_pipefd); + + struct wl_event_queue *a_queue = wl_display_create_queue(m_disp); + struct pollfd fds[2] = { { wl_display_get_fd(m_disp), POLLIN, 0 }, + { m_pipefd[0], POLLIN, 0 } }; + + while (true) { + // No wl_proxy is assigned to this queue, thus guaranteed to be always empty. + Q_ASSERT(wl_display_prepare_read_queue(m_disp, a_queue) == 0); + wl_display_flush(m_disp); + + // Wakeup every 10 seconds so that if Qt blocks in _read_events(), + // it won't last forever. + poll(fds, /* nfds */ 2, 10000); + + if (fds[0].revents & POLLIN) { + wl_display_read_events(m_disp); + } else { + wl_display_cancel_read(m_disp); + } + + if (fds[1].revents & POLLIN) { + char pipeIn; + if (read(m_pipefd[0], &pipeIn, 1) == 1 && pipeIn == 'q') + break; + } + } + + wl_event_queue_destroy(a_queue); + } + +private: + struct wl_display *m_disp; + int m_pipefd[2] = { -1, -1 }; +}; + +class tst_multithreaded : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void initTestCase() + { + m_config.autoConfigure = true; + m_config.autoEnter = false; + } + void init() + { + // a test case is given new simulated thread. + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + struct wl_display *wl_dpy = + (struct wl_display *)native->nativeResourceForWindow("display", NULL); + + m_extThread.reset(new ExternalWaylandReaderThread(wl_dpy)); + m_extThread->start(); + } + void cleanup() + { + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); + } + + void mainThreadIsNotBlocked(); + +public: + QScopedPointer m_extThread; +}; + +void tst_multithreaded::mainThreadIsNotBlocked() +{ + QElapsedTimer timer; + timer.start(); + + QTest::qWait(100); + QVERIFY(timer.elapsed() < 200); +} + +QCOMPOSITOR_TEST_MAIN(tst_multithreaded) +#include "tst_multithreaded.moc" diff --git a/tests/auto/wayland/nooutput/CMakeLists.txt b/tests/auto/wayland/nooutput/CMakeLists.txt new file mode 100644 index 00000000000..eeee5790953 --- /dev/null +++ b/tests/auto/wayland/nooutput/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from nooutput.pro. + +##################################################################### +## tst_nooutput Test: +##################################################################### + +qt_internal_add_test(tst_nooutput + SOURCES + tst_nooutput.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/nooutput/tst_nooutput.cpp b/tests/auto/wayland/nooutput/tst_nooutput.cpp new file mode 100644 index 00000000000..1d8a838f34f --- /dev/null +++ b/tests/auto/wayland/nooutput/tst_nooutput.cpp @@ -0,0 +1,53 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include +#include + +using namespace MockCompositor; + +class NoOutputCompositor : public DefaultCompositor { +public: + NoOutputCompositor() + { + exec([this] { removeAll(); }); + m_config.autoConfigure = false; + } +}; + +class tst_nooutput : public QObject, private NoOutputCompositor +{ + Q_OBJECT +private slots: + void cleanup() + { + // There should be no wl_outputs in this test + QCOMPOSITOR_COMPARE(getAll().size(), 0); + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); + } + void noScreens(); +}; + +void tst_nooutput::noScreens() +{ + QRasterWindow window; + window.resize(16, 16); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QTRY_VERIFY(window.isVisible()); + // The window should not be exposed before the first xdg_surface configure event + QTRY_VERIFY(!window.isExposed()); + + exec([&] { + xdgToplevel()->sendConfigure({0, 0}, {}); // Let the window decide the size + xdgSurface()->sendConfigure(nextSerial()); + }); + + QTRY_VERIFY(window.isExposed()); +} + +QCOMPOSITOR_TEST_MAIN(tst_nooutput) +#include "tst_nooutput.moc" diff --git a/tests/auto/wayland/output/CMakeLists.txt b/tests/auto/wayland/output/CMakeLists.txt new file mode 100644 index 00000000000..a9c5cea3c74 --- /dev/null +++ b/tests/auto/wayland/output/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from output.pro. + +##################################################################### +## tst_output Test: +##################################################################### + +qt_internal_add_test(tst_output + SOURCES + tst_output.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/output/tst_output.cpp b/tests/auto/wayland/output/tst_output.cpp new file mode 100644 index 00000000000..2129e167b50 --- /dev/null +++ b/tests/auto/wayland/output/tst_output.cpp @@ -0,0 +1,249 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include +#include + +using namespace MockCompositor; + +class tst_output : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void initTestCase() + { + m_config.autoConfigure = true; + m_config.autoEnter = false; + } + void cleanup() + { + QCOMPOSITOR_COMPARE(getAll().size(), 1); // Only the default output should be left + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); + } + void primaryScreen(); + void secondaryHiDpiScreen(); + void addScreenWithGeometryChange(); + void windowScreens(); + void removePrimaryScreen(); + void screenOrder(); + void removeAllScreens(); +}; + +void tst_output::primaryScreen() +{ + // Verify that the client has bound to the output global + QCOMPOSITOR_TRY_COMPARE(output()->resourceMap().size(), 1); + QTRY_VERIFY(QGuiApplication::primaryScreen()); + QScreen *screen = QGuiApplication::primaryScreen(); + QCOMPARE(screen->manufacturer(), "Make"); + QCOMPARE(screen->model(), "Model"); + QCOMPARE(screen->size(), QSize(1920, 1080)); + QCOMPARE(screen->refreshRate(), 60); + QCOMPARE(qRound(screen->physicalDotsPerInch()), 96 / screen->devicePixelRatio()); + QCOMPARE(screen->devicePixelRatio(), 1); + QCOMPARE(screen->logicalDotsPerInch(), 96); +} + +void tst_output::secondaryHiDpiScreen() +{ + exec([&] { + OutputData d; + d.position = {1920, 0}; // in global compositor space (not pixels) + d.mode.resolution = {800, 640}; + d.physicalSize = d.mode.physicalSizeForDpi(200); + d.scale = 2; + add(d); + }); + + // Verify that the client has bound to the output global + QCOMPOSITOR_TRY_VERIFY(output(1) && output(1)->resourceMap().size() == 1); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + QScreen *screen = QGuiApplication::screens()[1]; + QCOMPARE(screen->refreshRate(), 60); + QCOMPARE(screen->devicePixelRatio(), 2); + QCOMPARE(screen->logicalDotsPerInch(), 96); + + // Dots currently means device pixels, not actual pixels (see QTBUG-62649) + QCOMPARE(qRound(screen->physicalDotsPerInch() * screen->devicePixelRatio()), 200); + + // Size is in logical pixel coordinates + QCOMPARE(screen->size(), QSize(800, 640) / 2); + QCOMPARE(screen->geometry(), QRect(QPoint(1920, 0), QSize(400, 320))); + QCOMPARE(screen->virtualGeometry(), QRect(QPoint(0, 0), QSize(1920 + 800 / 2, 1080))); + + exec([&] { remove(output(1)); }); +} + +// QTBUG-62044 +void tst_output::addScreenWithGeometryChange() +{ + const QPoint initialPosition = exec([&] { return output(0)->m_data.position; }); + + exec([&] { + auto *oldOutput = output(0); + auto *newOutput = add(); + newOutput->m_data.mode.resolution = {1280, 720}; + // Move the primary output to the right + QPoint newPosition(newOutput->m_data.mode.resolution.width(), 0); + Q_ASSERT(newPosition != initialPosition); + oldOutput->m_data.position = newPosition; + oldOutput->sendGeometry(); + oldOutput->sendDone(); + }); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + QTRY_COMPARE(QGuiApplication::primaryScreen()->geometry(), QRect(QPoint(1280, 0), QSize(1920, 1080))); + + // Remove the extra output and move the old one back + exec([&] { + remove(output(1)); + output()->m_data.position = initialPosition; + output()->sendGeometry(); + output()->sendDone(); + }); + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QTRY_COMPARE(QGuiApplication::primaryScreen()->geometry(), QRect(QPoint(0, 0), QSize(1920, 1080))); +} + +void tst_output::windowScreens() +{ + QRasterWindow window; + window.resize(400, 320); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QScreen *primaryScreen = QGuiApplication::screens().first(); + QCOMPARE(window.screen(), primaryScreen); + + exec([&] { add(); }); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + QScreen *secondaryScreen = QGuiApplication::screens().at(1); + QVERIFY(secondaryScreen); + + window.setScreen(secondaryScreen); + QCOMPARE(window.screen(), secondaryScreen); + + exec([&] { + xdgToplevel()->surface()->sendEnter(output(0)); + xdgToplevel()->surface()->sendEnter(output(1)); + }); + + QTRY_COMPARE(window.screen(), primaryScreen); + + exec([&] { + xdgToplevel()->surface()->sendLeave(output(0)); + }); + QTRY_COMPARE(window.screen(), secondaryScreen); + + exec([&] { + remove(output(1)); + }); + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QCOMPARE(window.screen(), primaryScreen); +} + +void tst_output::removePrimaryScreen() +{ + QRasterWindow window; + window.resize(400, 320); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QScreen *primaryScreen = QGuiApplication::screens().first(); + QCOMPARE(window.screen(), primaryScreen); + + // Add a clone of the primary output + exec([&] { add(output()->m_data); }); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + QTRY_COMPARE(QGuiApplication::primaryScreen()->virtualSiblings().size(), 2); + QScreen *secondaryScreen = QGuiApplication::screens().at(1); + QVERIFY(secondaryScreen); + + exec([&] { remove(output()); }); + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + + exec([&] { + auto *surface = xdgToplevel()->surface(); + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_LEFT, 1); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_LEFT, 0); + pointer()->sendFrame(client()); + }); + + // Wait to make sure mouse events dont't cause a crash now that the screen has changed + xdgPingAndWaitForPong(); +} + +// QTBUG-72828 +void tst_output::screenOrder() +{ + exec([&] { + add()->m_data.model = "Screen 1"; + add()->m_data.model = "Screen 2"; + }); + + QTRY_COMPARE(QGuiApplication::screens().size(), 3); + const auto screens = QGuiApplication::screens(); + + QCOMPARE(screens[1]->model(), "Screen 1"); + QCOMPARE(screens[2]->model(), "Screen 2"); + + exec([&] { + remove(output(2)); + remove(output(1)); + }); +} + +// This is different from tst_nooutput::noScreens because here we have a screen at platform +// integration initialization, which we then remove. +void tst_output::removeAllScreens() +{ + QRasterWindow window1; + window1.resize(400, 320); + window1.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface(0) && xdgSurface(0)->m_committedConfigureSerial); + + const QString wlOutputPrimaryScreenModel = QGuiApplication::primaryScreen()->model(); + + // Get screen info so we can restore it after + auto screenInfo = exec([&] { return output()->m_data; }); + exec([&] { remove(output()); }); + + // Make sure the wl_output is actually removed before we continue + QTRY_VERIFY(!QGuiApplication::primaryScreen() || QGuiApplication::primaryScreen()->model() != wlOutputPrimaryScreenModel); + + // Adding a window while there are no screens should also work + QRasterWindow window2; + window2.resize(400, 320); + window2.show(); + + exec([&] { add(screenInfo); }); + + // Things should be back to normal + QTRY_VERIFY(QGuiApplication::primaryScreen()); + QTRY_COMPARE(QGuiApplication::primaryScreen()->model(), wlOutputPrimaryScreenModel); + + // Test that we don't leave any fake screens around after we get a wl_output back. + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + + // Qt may choose to recreate/hide windows in response to changing screens, so give the client + // some time to potentially mess up before we verify that the windows are visible. + xdgPingAndWaitForPong(); + + // Windows should be visible after we've reconnected the screen + QCOMPOSITOR_TRY_VERIFY(xdgToplevel(0) && xdgToplevel(0)->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel(1) && xdgToplevel(1)->m_xdgSurface->m_committedConfigureSerial); + +} + +QCOMPOSITOR_TEST_MAIN(tst_output) +#include "tst_output.moc" diff --git a/tests/auto/wayland/primaryselectionv1/CMakeLists.txt b/tests/auto/wayland/primaryselectionv1/CMakeLists.txt new file mode 100644 index 00000000000..0235ae33ef5 --- /dev/null +++ b/tests/auto/wayland/primaryselectionv1/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from primaryselectionv1.pro. + +##################################################################### +## tst_primaryselectionv1 Test: +##################################################################### + +qt_internal_add_test(tst_primaryselectionv1 + SOURCES + tst_primaryselectionv1.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/primaryselectionv1/tst_primaryselectionv1.cpp b/tests/auto/wayland/primaryselectionv1/tst_primaryselectionv1.cpp new file mode 100644 index 00000000000..1ce6d0f033d --- /dev/null +++ b/tests/auto/wayland/primaryselectionv1/tst_primaryselectionv1.cpp @@ -0,0 +1,476 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include + +#include +#include +#include + +#include + +using namespace MockCompositor; + +constexpr int primarySelectionVersion = 1; // protocol VERSION, not the name suffix (_v1) + +class PrimarySelectionDeviceV1; +class PrimarySelectionDeviceManagerV1; + +class PrimarySelectionOfferV1 : public QObject, public QtWaylandServer::zwp_primary_selection_offer_v1 +{ + Q_OBJECT +public: + explicit PrimarySelectionOfferV1(PrimarySelectionDeviceV1 *device, wl_client *client, int version) + : zwp_primary_selection_offer_v1(client, 0, version) + , m_device(device) + {} + void send_offer() = delete; + void sendOffer(const QString &offer) + { + zwp_primary_selection_offer_v1::send_offer(offer); + m_mimeTypes << offer; + } + + PrimarySelectionDeviceV1 *m_device = nullptr; + QStringList m_mimeTypes; + +signals: + void receive(QString mimeType, int fd); + +protected: + void zwp_primary_selection_offer_v1_destroy_resource(Resource *resource) override + { + Q_UNUSED(resource); + delete this; + } + + void zwp_primary_selection_offer_v1_receive(Resource *resource, const QString &mime_type, int32_t fd) override + { + Q_UNUSED(resource); + QTRY_VERIFY(m_mimeTypes.contains(mime_type)); + emit receive(mime_type, fd); + } + + void zwp_primary_selection_offer_v1_destroy(Resource *resource) override; +}; + +class PrimarySelectionSourceV1 : public QObject, public QtWaylandServer::zwp_primary_selection_source_v1 +{ + Q_OBJECT +public: + explicit PrimarySelectionSourceV1(wl_client *client, int id, int version) + : zwp_primary_selection_source_v1(client, id, version) + { + } + QStringList m_offers; +protected: + void zwp_primary_selection_source_v1_destroy_resource(Resource *resource) override + { + Q_UNUSED(resource); + delete this; + } + void zwp_primary_selection_source_v1_offer(Resource *resource, const QString &mime_type) override + { + Q_UNUSED(resource); + m_offers << mime_type; + } + void zwp_primary_selection_source_v1_destroy(Resource *resource) override + { + wl_resource_destroy(resource->handle); + } +}; + +class PrimarySelectionDeviceV1 : public QObject, public QtWaylandServer::zwp_primary_selection_device_v1 +{ + Q_OBJECT +public: + explicit PrimarySelectionDeviceV1(PrimarySelectionDeviceManagerV1 *manager, Seat *seat) + : m_manager(manager) + , m_seat(seat) + {} + + void send_data_offer(::wl_resource *resource) = delete; + + PrimarySelectionOfferV1 *sendDataOffer(::wl_client *client, const QStringList &mimeTypes = {}); + + PrimarySelectionOfferV1 *sendDataOffer(const QStringList &mimeTypes = {}) // creates a new offer for the focused surface and sends it + { + Q_ASSERT(m_seat->m_capabilities & Seat::capability_keyboard); + Q_ASSERT(m_seat->m_keyboard->m_enteredSurface); + auto *client = m_seat->m_keyboard->m_enteredSurface->resource()->client(); + return sendDataOffer(client, mimeTypes); + } + + void send_selection(::wl_resource *resource) = delete; + void sendSelection(PrimarySelectionOfferV1 *offer) + { + auto *client = offer->resource()->client(); + for (auto *resource : resourceMap().values(client)) + zwp_primary_selection_device_v1::send_selection(resource->handle, offer->resource()->handle); + m_sentSelectionOffers << offer; + } + + PrimarySelectionDeviceManagerV1 *m_manager = nullptr; + Seat *m_seat = nullptr; + QList m_sentSelectionOffers; + PrimarySelectionSourceV1 *m_selectionSource = nullptr; + uint m_serial = 0; + +protected: + void zwp_primary_selection_device_v1_set_selection(Resource *resource, ::wl_resource *source, uint32_t serial) override + { + Q_UNUSED(resource); + m_selectionSource = fromResource(source); + m_serial = serial; + } + void zwp_primary_selection_device_v1_destroy(Resource *resource) override + { + wl_resource_destroy(resource->handle); + } +}; + +class PrimarySelectionDeviceManagerV1 : public Global, public QtWaylandServer::zwp_primary_selection_device_manager_v1 +{ + Q_OBJECT +public: + explicit PrimarySelectionDeviceManagerV1(CoreCompositor *compositor, int version = 1) + : QtWaylandServer::zwp_primary_selection_device_manager_v1(compositor->m_display, version) + , m_version(version) + {} + ~PrimarySelectionDeviceManagerV1() override + { + qDeleteAll(m_devices); + } + bool isClean() override + { + for (auto *device : std::as_const(m_devices)) { + // The client should not leak selection offers, i.e. if this fails, there is a missing + // zwp_primary_selection_offer_v1.destroy request + if (!device->m_sentSelectionOffers.empty()) + return false; + } + return true; + } + + PrimarySelectionDeviceV1 *deviceFor(Seat *seat) + { + Q_ASSERT(seat); + if (auto *device = m_devices.value(seat, nullptr)) + return device; + + auto *device = new PrimarySelectionDeviceV1(this, seat); + m_devices[seat] = device; + return device; + } + + int m_version = 1; // TODO: Remove on libwayland upgrade + QMap m_devices; + QList m_sources; +protected: + void zwp_primary_selection_device_manager_v1_destroy(Resource *resource) override + { + // The protocol doesn't say whether managed objects should be destroyed as well, + // so leave them alone, they'll be cleaned up in the destructor anyway + wl_resource_destroy(resource->handle); + } + + void zwp_primary_selection_device_manager_v1_create_source(Resource *resource, uint32_t id) override + { + int version = m_version; + m_sources << new PrimarySelectionSourceV1(resource->client(), id, version); + } + void zwp_primary_selection_device_manager_v1_get_device(Resource *resource, uint32_t id, ::wl_resource *seatResource) override + { + auto *seat = fromResource(seatResource); + QVERIFY(seat); + auto *device = deviceFor(seat); + device->add(resource->client(), id, resource->version()); + } +}; + +PrimarySelectionOfferV1 *PrimarySelectionDeviceV1::sendDataOffer(wl_client *client, const QStringList &mimeTypes) +{ + Q_ASSERT(client); + auto *offer = new PrimarySelectionOfferV1(this, client, m_manager->m_version); + for (auto *resource : resourceMap().values(client)) + zwp_primary_selection_device_v1::send_data_offer(resource->handle, offer->resource()->handle); + for (const auto &mimeType : mimeTypes) + offer->sendOffer(mimeType); + return offer; +} + +void PrimarySelectionOfferV1::zwp_primary_selection_offer_v1_destroy(QtWaylandServer::zwp_primary_selection_offer_v1::Resource *resource) +{ + bool removed = m_device->m_sentSelectionOffers.removeOne(this); + QVERIFY(removed); + wl_resource_destroy(resource->handle); +} + +class PrimarySelectionCompositor : public DefaultCompositor { +public: + explicit PrimarySelectionCompositor() + { + exec([this] { + m_config.autoConfigure = true; + add(primarySelectionVersion); + }); + } + PrimarySelectionDeviceV1 *primarySelectionDevice(int i = 0) { + return get()->deviceFor(get(i)); + } +}; + +class tst_primaryselectionv1 : public QObject, private PrimarySelectionCompositor +{ + Q_OBJECT +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void initTestCase(); + void bindsToManager(); + void createsPrimaryDevice(); + void createsPrimaryDeviceForNewSeats(); + void pasteAscii(); + void pasteUtf8(); + void destroysPreviousSelection(); + void destroysSelectionOnLeave(); + void copy(); +}; + +void tst_primaryselectionv1::initTestCase() +{ + QCOMPOSITOR_TRY_VERIFY(pointer()); + QCOMPOSITOR_TRY_VERIFY(keyboard()); +} + +void tst_primaryselectionv1::bindsToManager() +{ + QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().first()->version(), primarySelectionVersion); +} + +void tst_primaryselectionv1::createsPrimaryDevice() +{ + QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice()); + QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice()->resourceMap().contains(client())); + QCOMPOSITOR_TRY_COMPARE(primarySelectionDevice()->resourceMap().value(client())->version(), primarySelectionVersion); + QTRY_VERIFY(QGuiApplication::clipboard()->supportsSelection()); +} + +void tst_primaryselectionv1::createsPrimaryDeviceForNewSeats() +{ + exec([&] { add(); }); + QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice(1)); +} + +void tst_primaryselectionv1::pasteAscii() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override + { + Q_UNUSED(event); + auto *mimeData = QGuiApplication::clipboard()->mimeData(QClipboard::Selection); + m_formats = mimeData->formats(); + m_text = QGuiApplication::clipboard()->text(QClipboard::Selection); + } + QStringList m_formats; + QString m_text; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + auto *device = primarySelectionDevice(); + auto *offer = device->sendDataOffer({"text/plain"}); + connect(offer, &PrimarySelectionOfferV1::receive, offer, [](QString mimeType, int fd) { + QFile file; + QVERIFY(file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle)); + QCOMPARE(mimeType, "text/plain"); + file.write(QByteArray("normal ascii")); + file.close(); + }, Qt::DirectConnection); + device->sendSelection(offer); + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_MIDDLE, 1); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_MIDDLE, 0); + pointer()->sendFrame(client()); + }); + QTRY_COMPARE(window.m_formats, QStringList{"text/plain"}); + QTRY_COMPARE(window.m_text, "normal ascii"); +} + +void tst_primaryselectionv1::pasteUtf8() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override + { + Q_UNUSED(event); + auto *mimeData = QGuiApplication::clipboard()->mimeData(QClipboard::Selection); + m_formats = mimeData->formats(); + m_text = QGuiApplication::clipboard()->text(QClipboard::Selection); + } + QStringList m_formats; + QString m_text; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + auto *device = primarySelectionDevice(); + auto *offer = device->sendDataOffer({"text/plain", "text/plain;charset=utf-8"}); + connect(offer, &PrimarySelectionOfferV1::receive, offer, [](QString mimeType, int fd) { + QFile file; + QVERIFY(file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle)); + QCOMPARE(mimeType, "text/plain;charset=utf-8"); + file.write(QByteArray("face with tears of joy: 😂")); + file.close(); + }, Qt::DirectConnection); + device->sendSelection(offer); + + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_MIDDLE, 1); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_MIDDLE, 0); + pointer()->sendFrame(client()); + }); + QTRY_COMPARE(window.m_formats, QStringList({"text/plain"})); + QTRY_COMPARE(window.m_text, "face with tears of joy: 😂"); +} + +void tst_primaryselectionv1::destroysPreviousSelection() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + // When the client receives a selection event, it is required to destroy the previous offer + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + auto *offer = primarySelectionDevice()->sendDataOffer({"text/plain"}); + primarySelectionDevice()->sendSelection(offer); + }); + + exec([&] { + auto *offer = primarySelectionDevice()->sendDataOffer({"text/plain"}); + primarySelectionDevice()->sendSelection(offer); + QCOMPARE(primarySelectionDevice()->m_sentSelectionOffers.size(), 2); + }); + + // Verify the first offer gets destroyed + QCOMPOSITOR_TRY_COMPARE(primarySelectionDevice()->m_sentSelectionOffers.size(), 1); +} + +void tst_primaryselectionv1::destroysSelectionOnLeave() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + + auto *offer = primarySelectionDevice()->sendDataOffer({"text/plain"}); + primarySelectionDevice()->sendSelection(offer); + }); + + QTRY_VERIFY(QGuiApplication::clipboard()->mimeData(QClipboard::Selection)); + QTRY_VERIFY(QGuiApplication::clipboard()->mimeData(QClipboard::Selection)->hasText()); + + QSignalSpy selectionChangedSpy(QGuiApplication::clipboard(), &QClipboard::selectionChanged); + + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendLeave(surface); + }); + + QTRY_COMPARE(selectionChangedSpy.size(), 1); + QVERIFY(!QGuiApplication::clipboard()->mimeData(QClipboard::Selection)->hasText()); +} + +void tst_primaryselectionv1::copy() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override + { + Q_UNUSED(event); + QGuiApplication::clipboard()->setText("face with tears of joy: 😂", QClipboard::Selection); + } + QStringList m_formats; + QString m_text; + }; + + Window window; + window.resize(64, 64); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + QList mouseSerials; + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendFrame(client()); + mouseSerials << pointer()->sendButton(client(), BTN_MIDDLE, 1); + pointer()->sendFrame(client()); + mouseSerials << pointer()->sendButton(client(), BTN_MIDDLE, 0); + pointer()->sendFrame(client()); + }); + QCOMPOSITOR_TRY_VERIFY(primarySelectionDevice()->m_selectionSource); + QCOMPOSITOR_TRY_VERIFY(mouseSerials.contains(primarySelectionDevice()->m_serial)); + QVERIFY(QGuiApplication::clipboard()->ownsSelection()); + QByteArray pastedBuf; + exec([&](){ + auto *source = primarySelectionDevice()->m_selectionSource; + QCOMPARE(source->m_offers, QStringList({"text/plain", "text/plain;charset=utf-8"})); + int fd[2]; + if (pipe(fd) == -1) + QSKIP("Failed to create pipe"); + fcntl(fd[0], F_SETFL, fcntl(fd[0], F_GETFL, 0) | O_NONBLOCK); + source->send_send("text/plain;charset=utf-8", fd[1]); + auto *notifier = new QSocketNotifier(fd[0], QSocketNotifier::Read, this); + connect(notifier, &QSocketNotifier::activated, this, [&](int fd) { + exec([&]{ + static char buf[1024]; + int n = QT_READ(fd, buf, sizeof buf); + if (n <= 0) { + delete notifier; + close(fd); + } else { + pastedBuf.append(buf, n); + } + }); + }, Qt::DirectConnection); + }); + + QCOMPOSITOR_TRY_VERIFY(pastedBuf.size()); // this assumes we got everything in one read + auto pasted = QString::fromUtf8(pastedBuf); + QCOMPARE(pasted, "face with tears of joy: 😂"); +} + +QCOMPOSITOR_TEST_MAIN(tst_primaryselectionv1) +#include "tst_primaryselectionv1.moc" diff --git a/tests/auto/wayland/reconnect/CMakeLists.txt b/tests/auto/wayland/reconnect/CMakeLists.txt new file mode 100644 index 00000000000..07d4c714376 --- /dev/null +++ b/tests/auto/wayland/reconnect/CMakeLists.txt @@ -0,0 +1,11 @@ +##################################################################### +## tst_wl_reconnect Test: +##################################################################### + +qt_internal_add_test(tst_wl_reconnect + SOURCES + wl-socket.c + tst_reconnect.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/reconnect/tst_reconnect.cpp b/tests/auto/wayland/reconnect/tst_reconnect.cpp new file mode 100644 index 00000000000..5f3d49bd38d --- /dev/null +++ b/tests/auto/wayland/reconnect/tst_reconnect.cpp @@ -0,0 +1,260 @@ +// Copyright (C) 2023 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_CONFIG(opengl) +#include +#endif +#include + +#include +#include +#include + +#include "wl-socket.h" + +#include + +using namespace MockCompositor; + +class TestWindow : public QRasterWindow +{ +public: + TestWindow() + { + } + + void focusInEvent(QFocusEvent *) override + { + ++focusInEventCount; + } + + void focusOutEvent(QFocusEvent *) override + { + ++focusOutEventCount; + } + + void keyPressEvent(QKeyEvent *event) override + { + ++keyPressEventCount; + keyCode = event->nativeScanCode(); + } + + void keyReleaseEvent(QKeyEvent *event) override + { + ++keyReleaseEventCount; + keyCode = event->nativeScanCode(); + } + + void mousePressEvent(QMouseEvent *event) override + { + ++mousePressEventCount; + mousePressPos = event->position().toPoint(); + } + + void mouseReleaseEvent(QMouseEvent *) override + { + ++mouseReleaseEventCount; + } + + void touchEvent(QTouchEvent *event) override + { + Q_UNUSED(event); + ++touchEventCount; + } + + QPoint frameOffset() const { return QPoint(frameMargins().left(), frameMargins().top()); } + + int focusInEventCount = 0; + int focusOutEventCount = 0; + int keyPressEventCount = 0; + int keyReleaseEventCount = 0; + int mousePressEventCount = 0; + int mouseReleaseEventCount = 0; + int touchEventCount = 0; + + uint keyCode = 0; + QPoint mousePressPos; +}; + +class tst_WaylandReconnect : public QObject +{ + Q_OBJECT +public: + tst_WaylandReconnect(); + void triggerReconnect(); + + template + auto exec(function_type func, arg_types&&... args) -> decltype(func()) + { + return m_comp->exec(func, std::forward(args)...); + } + +private Q_SLOTS: +//core + void cleanup() { QTRY_VERIFY2(m_comp->isClean(), qPrintable(m_comp->dirtyMessage())); } + void basicWindow(); + void multipleScreens(); + +//input + void keyFocus(); + +private: + void configureWindow(); + QScopedPointer m_comp; + wl_socket *m_socket; +}; + +tst_WaylandReconnect::tst_WaylandReconnect() +{ + m_socket = wl_socket_create(); + QVERIFY(m_socket); + const int socketFd = wl_socket_get_fd(m_socket); + const QByteArray socketName = wl_socket_get_display_name(m_socket); + qputenv("WAYLAND_DISPLAY", socketName); + + m_comp.reset(new DefaultCompositor(CoreCompositor::Default, dup(socketFd))); + m_comp->m_config.autoEnter = false; +} + +void tst_WaylandReconnect::triggerReconnect() +{ + const int socketFd = wl_socket_get_fd(m_socket); + m_comp.reset(new DefaultCompositor(CoreCompositor::Default, dup(socketFd))); + m_comp->m_config.autoEnter = false; + + QTest::qWait(50); //we need to spin the main loop to actually reconnect +} + +void tst_WaylandReconnect::basicWindow() +{ + QRasterWindow window; + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel()); + m_comp->surface(0)->sendEnter(m_comp->output(0)); + + triggerReconnect(); + + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel()); +} + +void tst_WaylandReconnect::multipleScreens() +{ + + exec([this] { m_comp->add(); }); + QRasterWindow window1; + window1.resize(64, 48); + window1.show(); + QRasterWindow window2; + window2.resize(64, 48); + window2.show(); + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel(0)); + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel(1)); + QCOMPOSITOR_TRY_VERIFY(!m_comp->output(1)->resourceMap().isEmpty()); + + // ensure they are on different outputs + exec([this] { + m_comp->surface(0)->sendEnter(m_comp->output(0)); + m_comp->surface(1)->sendEnter(m_comp->output(1)); + }); + QTRY_VERIFY(window1.screen() != window2.screen()); + + auto originalScreens = QGuiApplication::screens(); + + QSignalSpy screenRemovedSpy(qGuiApp, &QGuiApplication::screenRemoved); + QVERIFY(screenRemovedSpy.isValid()); + + triggerReconnect(); + + // All screens plus temporary placeholder screen removed + QCOMPARE(screenRemovedSpy.count(), originalScreens.count() + 1); + for (const auto &screen : std::as_const(screenRemovedSpy)) { + originalScreens.removeOne(screen[0].value()); + } + QVERIFY(originalScreens.isEmpty()); + + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel(0)); + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel(1)); + QVERIFY(window1.screen()); + QVERIFY(window2.screen()); + QVERIFY(QGuiApplication::screens().contains(window1.screen())); + QVERIFY(QGuiApplication::screens().contains(window2.screen())); +} + +void tst_WaylandReconnect::keyFocus() +{ + TestWindow window; + window.resize(64, 48); + window.show(); + + configureWindow(); + QTRY_VERIFY(window.isExposed()); + exec([&] { + m_comp->keyboard()->sendEnter(m_comp->surface()); + }); + QTRY_COMPARE(window.focusInEventCount, 1); + + uint keyCode = 80; + QCOMPARE(window.keyPressEventCount, 0); + exec([&] { + m_comp->keyboard()->sendKey(m_comp->client(), keyCode - 8, Keyboard::key_state_pressed); + }); + QTRY_COMPARE(window.keyPressEventCount, 1); + QCOMPARE(QGuiApplication::focusWindow(), &window); + + triggerReconnect(); + configureWindow(); + + // on reconnect our knowledge of focus is reset to a clean slate + QCOMPARE(QGuiApplication::focusWindow(), nullptr); + QTRY_COMPARE(window.focusOutEventCount, 1); + + // fake the user explicitly focussing this window afterwards + exec([&] { + m_comp->keyboard()->sendEnter(m_comp->surface()); + }); + exec([&] { + m_comp->keyboard()->sendKey(m_comp->client(), keyCode - 8, Keyboard::key_state_pressed); + }); + QTRY_COMPARE(window.focusInEventCount, 2); + QTRY_COMPARE(window.keyPressEventCount, 2); +} + + +void tst_WaylandReconnect::configureWindow() +{ + QCOMPOSITOR_TRY_VERIFY(m_comp->xdgToplevel()); + m_comp->exec([&] { + m_comp->xdgToplevel()->sendConfigure({0, 0}, {}); + const uint serial = m_comp->nextSerial(); // Let the window decide the size + m_comp->xdgSurface()->sendConfigure(serial); + }); +} + +int main(int argc, char **argv) +{ + // Note when debugging that a failing reconnect will exit this + // test rather than fail. Making sure it finishes is important! + + QTemporaryDir tmpRuntimeDir; + setenv("QT_QPA_PLATFORM", "wayland", 1); // force QGuiApplication to use wayland plugin + setenv("QT_WAYLAND_RECONNECT", "1", 1); + setenv("XDG_CURRENT_DESKTOP", "qtwaylandtests", 1); + + tst_WaylandReconnect tc; + QGuiApplication app(argc, argv); + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_reconnect.moc" diff --git a/tests/auto/wayland/reconnect/wl-socket.c b/tests/auto/wayland/reconnect/wl-socket.c new file mode 100644 index 00000000000..3d449162251 --- /dev/null +++ b/tests/auto/wayland/reconnect/wl-socket.c @@ -0,0 +1,166 @@ +// Copyright (C) 2023 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#define _DEFAULT_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* This is the size of the char array in struct sock_addr_un. + * No Wayland socket can be created with a path longer than this, + * including the null terminator. + */ +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +#define LOCK_SUFFIX ".lock" +#define LOCK_SUFFIXLEN 5 + +struct wl_socket { + int fd; + int fd_lock; + struct sockaddr_un addr; + char lock_addr[UNIX_PATH_MAX + LOCK_SUFFIXLEN]; + char display_name[16]; +}; + +static struct wl_socket *wl_socket_alloc(void) +{ + struct wl_socket *s; + + s = malloc(sizeof *s); + if (!s) + return NULL; + + s->fd = -1; + s->fd_lock = -1; + + return s; +} + +static int wl_socket_lock(struct wl_socket *socket) +{ + struct stat socket_stat; + + snprintf(socket->lock_addr, sizeof socket->lock_addr, "%s%s", socket->addr.sun_path, LOCK_SUFFIX); + + // differening from kwin, we're back to setting CLOEXEC as we're all in process + socket->fd_lock = open(socket->lock_addr, O_CREAT | O_RDWR | O_CLOEXEC, (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); + + if (socket->fd_lock < 0) { + printf("unable to open lockfile %s check permissions\n", socket->lock_addr); + goto err; + } + + if (flock(socket->fd_lock, LOCK_EX | LOCK_NB) < 0) { + printf("unable to lock lockfile %s, maybe another compositor is running\n", socket->lock_addr); + goto err_fd; + } + + if (lstat(socket->addr.sun_path, &socket_stat) < 0) { + if (errno != ENOENT) { + printf("did not manage to stat file %s\n", socket->addr.sun_path); + goto err_fd; + } + } else if (socket_stat.st_mode & S_IWUSR || socket_stat.st_mode & S_IWGRP) { + unlink(socket->addr.sun_path); + } + + return 0; +err_fd: + close(socket->fd_lock); + socket->fd_lock = -1; +err: + *socket->lock_addr = 0; + /* we did not set this value here, but without lock the + * socket won't be created anyway. This prevents the + * wl_socket_destroy from unlinking already existing socket + * created by other compositor */ + *socket->addr.sun_path = 0; + + return -1; +} + +void wl_socket_destroy(struct wl_socket *s) +{ + if (s->addr.sun_path[0]) + unlink(s->addr.sun_path); + if (s->fd >= 0) + close(s->fd); + if (s->lock_addr[0]) + unlink(s->lock_addr); + if (s->fd_lock >= 0) + close(s->fd_lock); + + free(s); +} + +const char *wl_socket_get_display_name(struct wl_socket *s) +{ + return s->display_name; +} + +int wl_socket_get_fd(struct wl_socket *s) +{ + return s->fd; +} + +struct wl_socket *wl_socket_create() +{ + struct wl_socket *s; + int displayno = 0; + int name_size; + + /* A reasonable number of maximum default sockets. If + * you need more than this, use the explicit add_socket API. */ + const int MAX_DISPLAYNO = 32; + const char *runtime_dir = getenv("XDG_RUNTIME_DIR"); + if (!runtime_dir) { + printf("XDG_RUNTIME_DIR not set"); + return NULL; + } + + s = wl_socket_alloc(); + if (s == NULL) + return NULL; + + do { + snprintf(s->display_name, sizeof s->display_name, "wayland-%d", displayno); + s->addr.sun_family = AF_LOCAL; + name_size = snprintf(s->addr.sun_path, sizeof s->addr.sun_path, "%s/%s", runtime_dir, s->display_name) + 1; + assert(name_size > 0); + + if (name_size > (int)sizeof s->addr.sun_path) { + goto fail; + } + + if (wl_socket_lock(s) < 0) + continue; + + s->fd = socket(PF_LOCAL, SOCK_STREAM, 0); + + int size = SUN_LEN(&s->addr); + int ret = bind(s->fd, (struct sockaddr*)&s->addr, size); + if (ret < 0) { + goto fail; + } + ret = listen(s->fd, 128); + if (ret < 0) { + goto fail; + } + return s; + } while (displayno++ < MAX_DISPLAYNO); + +fail: + wl_socket_destroy(s); + return NULL; +} diff --git a/tests/auto/wayland/reconnect/wl-socket.h b/tests/auto/wayland/reconnect/wl-socket.h new file mode 100644 index 00000000000..c2e68120ffa --- /dev/null +++ b/tests/auto/wayland/reconnect/wl-socket.h @@ -0,0 +1,34 @@ +// Copyright (C) 2023 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Allocate and create a socket + * It is bound and accepted + */ +struct wl_socket *wl_socket_create(); + +/** + * Returns the file descriptor for the socket + */ +int wl_socket_get_fd(struct wl_socket *); + +/** + * Returns the name of the socket, i.e "wayland-0" + */ +char *wl_socket_get_display_name(struct wl_socket *); + +/** + * Cleanup resources and close the FD + */ +void wl_socket_destroy(struct wl_socket *socket); + +#ifdef __cplusplus +} +#endif diff --git a/tests/auto/wayland/scaling/CMakeLists.txt b/tests/auto/wayland/scaling/CMakeLists.txt new file mode 100644 index 00000000000..e5898138814 --- /dev/null +++ b/tests/auto/wayland/scaling/CMakeLists.txt @@ -0,0 +1,10 @@ +##################################################################### +## tst_scaling Test: +##################################################################### + +qt_internal_add_test(tst_scaling + SOURCES + tst_scaling.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/scaling/tst_scaling.cpp b/tests/auto/wayland/scaling/tst_scaling.cpp new file mode 100644 index 00000000000..de36d34aa1d --- /dev/null +++ b/tests/auto/wayland/scaling/tst_scaling.cpp @@ -0,0 +1,136 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include +#include +#include +#include + +using namespace MockCompositor; + +class tst_scaling : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void init(); + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void scaledWindow(); + void roundingPolicy_data(); + void roundingPolicy(); + +}; + +void tst_scaling::init() +{ + setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1); +} + +void tst_scaling::scaledWindow() +{ + QRasterWindow window; + window.resize(100, 100); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QSignalSpy configureSpy(exec([&] { return xdgSurface(); }), &XdgSurface::configureCommitted); + QSignalSpy surfaceCommitSpy(exec([&] { return surface(); }), &Surface::commit); + + const QSize configureSize(100, 100); + + exec([&] { + QVERIFY(fractionalScale()); + fractionalScale()->send_preferred_scale(1.5 * 120); + xdgToplevel()->sendCompleteConfigure(configureSize); + }); + + QTRY_COMPARE(configureSpy.count(), 1); + QCOMPARE(window.devicePixelRatio(), 1.5); + + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), QSize(150, 150)); + Viewport *vp = viewport(); + QVERIFY(vp); + QCOMPARE(vp->m_destination, QSize(100, 100)); + }); + + // resize the window + window.resize(200,200); + QCOMPARE(window.size(), QSize(200,200)); + + QVERIFY(surfaceCommitSpy.wait()); + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), QSize(300, 300)); + Viewport *vp = viewport(); + QVERIFY(vp); + QCOMPARE(vp->m_destination, QSize(200, 200)); + }); + + surfaceCommitSpy.clear(); + + // dynamic scale change + exec([&] { + QVERIFY(fractionalScale()); + fractionalScale()->send_preferred_scale(2.5 * 120); + }); + QTRY_COMPARE(window.devicePixelRatio(), 2.5); + QCOMPARE(window.size(), QSize(200,200)); + QTRY_VERIFY(surfaceCommitSpy.count()); + + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), QSize(500, 500)); + Viewport *vp = viewport(); + QVERIFY(vp); + QCOMPARE(vp->m_destination, QSize(200, 200)); + }); +} + +void tst_scaling::roundingPolicy_data() +{ + QTest::addColumn("windowSize"); + QTest::addColumn("scale"); + QTest::addColumn("expectedBufferSize"); + + QTest::newRow("1.125 - round down") << QSize(10, 10) << 1.125 << QSize(11,11); + QTest::newRow("1.25 - round up") << QSize(10, 10) << 1.25 << QSize(13,13); + QTest::newRow("1.5 - don't round") << QSize(10, 10) << 1.5 << QSize(15,15); +} + +void tst_scaling::roundingPolicy() +{ + QFETCH(QSize, windowSize); + QFETCH(qreal, scale); + QFETCH(QSize, expectedBufferSize); + + + QRasterWindow window; + window.resize(windowSize); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QSignalSpy surfaceCommitSpy(exec([&] { return surface(); }), &Surface::commit); + + exec([&] { + QVERIFY(fractionalScale()); + fractionalScale()->send_preferred_scale(scale * 120); + xdgToplevel()->sendCompleteConfigure(); + }); + + QVERIFY(surfaceCommitSpy.wait()); + + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), expectedBufferSize); + }); +} + + +QCOMPOSITOR_TEST_MAIN(tst_scaling) +#include "tst_scaling.moc" diff --git a/tests/auto/wayland/seat/CMakeLists.txt b/tests/auto/wayland/seat/CMakeLists.txt new file mode 100644 index 00000000000..0ac9ec49f9d --- /dev/null +++ b/tests/auto/wayland/seat/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from seatv5.pro. + +##################################################################### +## tst_seatv5 Test: +##################################################################### + +qt_internal_add_test(tst_seat + SOURCES + tst_seat.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/seat/tst_seat.cpp b/tests/auto/wayland/seat/tst_seat.cpp new file mode 100644 index 00000000000..45ae7251c8b --- /dev/null +++ b/tests/auto/wayland/seat/tst_seat.cpp @@ -0,0 +1,685 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include +#include + +using namespace MockCompositor; + +class SeatCompositor : public DefaultCompositor { +public: + explicit SeatCompositor() + { + exec([this] { + m_config.autoConfigure = true; + + removeAll(); + + uint capabilities = MockCompositor::Seat::capability_pointer | MockCompositor::Seat::capability_touch; + int version = 9; + add(capabilities, version); + }); + } +}; + +class tst_seat : public QObject, private SeatCompositor +{ + Q_OBJECT +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void bindsToSeat(); + + // Pointer tests + void createsPointer(); + void setsCursorOnEnter(); + void usesEnterSerial(); + void simpleAxis_data(); + void simpleAxis(); + void fingerScroll(); + void fingerScrollSlow(); + void continuousScroll(); + void highResolutionScroll(); + + // Touch tests + void createsTouch(); + void singleTap(); + void singleTapFloat(); + void multiTouch(); + void multiTouchUpAndMotionFrame(); + void tapAndMoveInSameFrame(); + void cancelTouch(); +}; + +void tst_seat::bindsToSeat() +{ + QCOMPOSITOR_COMPARE(get()->resourceMap().size(), 1); + QCOMPOSITOR_COMPARE(get()->resourceMap().first()->version(), 9); +} + +void tst_seat::createsPointer() +{ + QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 9); +} + +void tst_seat::setsCursorOnEnter() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgSurface()->m_surface; + pointer()->sendEnter(surface, {0, 0}); + pointer()->sendFrame(surface->resource()->client()); + }); + + QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface()); +} + +void tst_seat::usesEnterSerial() +{ + QSignalSpy setCursorSpy(exec([&] { return pointer(); }), &Pointer::setCursor); + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + uint enterSerial = exec([&] { + return pointer()->sendEnter(xdgSurface()->m_surface, {0, 0}); + }); + QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface()); + + QTRY_COMPARE(setCursorSpy.size(), 1); + QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial); +} + +class WheelWindow : QRasterWindow { +public: + WheelWindow() + { + resize(64, 64); + show(); + } + void wheelEvent(QWheelEvent *event) override + { + QRasterWindow::wheelEvent(event); +// qDebug() << event << "angleDelta" << event->angleDelta() << "pixelDelta" << event->pixelDelta(); + + if (event->phase() != Qt::ScrollUpdate && event->phase() != Qt::NoScrollPhase) { + // Shouldn't have deltas in the these phases + QCOMPARE(event->angleDelta(), QPoint(0, 0)); + QCOMPARE(event->pixelDelta(), QPoint(0, 0)); + } + + // We didn't press any buttons + QCOMPARE(event->buttons(), Qt::NoButton); + + m_events.append(Event{event}); + } + struct Event // Because I didn't find a convenient way to copy it entirely + { + explicit Event() = default; + explicit Event(const QWheelEvent *event) + : phase(event->phase()) + , pixelDelta(event->pixelDelta()) + , angleDelta(event->angleDelta()) + , source(event->source()) + , inverted(event->inverted()) + { + } + Qt::ScrollPhase phase{}; + QPoint pixelDelta; + QPoint angleDelta; // eights of a degree, positive is upwards, left + Qt::MouseEventSource source{}; + bool inverted = false; + }; + QList m_events; +}; + +void tst_seat::simpleAxis_data() +{ + QTest::addColumn("axis"); + QTest::addColumn("value"); + QTest::addColumn("angleDelta"); + QTest::addColumn("inverted"); + + // Directions in regular windows/linux terms (no "natural" scrolling) + QTest::newRow("down") << uint(Pointer::axis_vertical_scroll) << 1.0 << QPoint{0, -12} << false; + QTest::newRow("up") << uint(Pointer::axis_vertical_scroll) << -1.0 << QPoint{0, 12} << false; + QTest::newRow("left") << uint(Pointer::axis_horizontal_scroll) << 1.0 << QPoint{-12, 0} << false; + QTest::newRow("right") << uint(Pointer::axis_horizontal_scroll) << -1.0 << QPoint{12, 0} << false; + QTest::newRow("up big") << uint(Pointer::axis_vertical_scroll) << -10.0 << QPoint{0, 120} << false; + + // (natural) scrolling + QTest::newRow("down inverted") << uint(Pointer::axis_vertical_scroll) << 1.0 << QPoint{0, -12} << true; + QTest::newRow("up inverted") << uint(Pointer::axis_vertical_scroll) << -1.0 << QPoint{0, 12} << true; + QTest::newRow("left inverted") << uint(Pointer::axis_horizontal_scroll) << 1.0 << QPoint{-12, 0} << true; + QTest::newRow("right inverted") << uint(Pointer::axis_horizontal_scroll) << -1.0 << QPoint{12, 0} << true; + QTest::newRow("up big inverted") << uint(Pointer::axis_vertical_scroll) << -10.0 << QPoint{0, 120} << true; +} + +void tst_seat::simpleAxis() +{ + QFETCH(uint, axis); + QFETCH(qreal, value); + QFETCH(QPoint, angleDelta); + QFETCH(bool, inverted); + + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *p = pointer(); + p->sendEnter(xdgToplevel()->surface(), {32, 32}); + p->sendFrame(client()); + p->sendAxis( + client(), + Pointer::axis(axis), + value // Length of vector in surface-local space. i.e. positive is downwards + ); + auto direction = inverted ? Pointer::axis_relative_direction_inverted : Pointer::axis_relative_direction_identical; + p->sendAxisRelativeDirection(client(), Pointer::axis(axis), direction); + p->sendFrame(client()); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::NoScrollPhase); + // Pixel delta should only be set if we know it's a high-res input device (which we don't) + QCOMPARE(e.pixelDelta, QPoint(0, 0)); + // There has been no information about what created the event. + // Documentation says not synthesized is appropriate in such cases + QCOMPARE(e.source, Qt::MouseEventNotSynthesized); + QCOMPARE(e.angleDelta, angleDelta); + + QCOMPARE(e.inverted, inverted); + } + + // Sending axis_stop is not mandatory when axis source != finger +} + +void tst_seat::fingerScroll() +{ + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *p = pointer(); + auto *c = client(); + p->sendEnter(xdgToplevel()->surface(), {32, 32}); + p->sendFrame(c); + p->sendAxisSource(c, Pointer::axis_source_finger); + p->sendAxis(c, Pointer::axis_vertical_scroll, 10); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::ScrollBegin); + QCOMPARE(e.angleDelta, QPoint()); + QCOMPARE(e.pixelDelta, QPoint()); + } + + QTRY_VERIFY(!window.m_events.empty()); + // For some reason we send two ScrollBegins, one for each direction, not sure if this is really + // necessary, (could be removed from QtBase, hence the conditional below. + if (window.m_events.first().phase == Qt::ScrollBegin) { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.angleDelta, QPoint()); + QCOMPARE(e.pixelDelta, QPoint()); + } + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::ScrollUpdate); + QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll +// QCOMPARE(e.angleDelta, angleDelta); // TODO: what should this be? + QCOMPARE(e.pixelDelta, QPoint(0, -10)); + QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel + } + + QTRY_VERIFY(window.m_events.empty()); + + // Scroll horizontally as well + exec([&] { + pointer()->sendAxisSource(client(), Pointer::axis_source_finger); + pointer()->sendAxis(client(), Pointer::axis_horizontal_scroll, 10); + pointer()->sendFrame(client()); + }); + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::ScrollUpdate); + QVERIFY(qAbs(e.angleDelta.x()) > qAbs(e.angleDelta.y())); // Horizontal scroll + QCOMPARE(e.pixelDelta, QPoint(-10, 0)); + QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel + } + + // Scroll diagonally + exec([&] { + pointer()->sendAxisSource(client(), Pointer::axis_source_finger); + pointer()->sendAxis(client(), Pointer::axis_horizontal_scroll, 10); + pointer()->sendAxis(client(), Pointer::axis_vertical_scroll, 10); + pointer()->sendFrame(client()); + }); + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::ScrollUpdate); + QCOMPARE(e.pixelDelta, QPoint(-10, -10)); + QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel + } + + // For diagonal events, Qt sends an additional compatibility ScrollUpdate event + if (window.m_events.first().phase == Qt::ScrollUpdate) { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.angleDelta, QPoint()); + QCOMPARE(e.pixelDelta, QPoint()); + } + + QVERIFY(window.m_events.empty()); + + // Sending axis_stop is mandatory when axis source == finger + exec([&] { + pointer()->sendAxisStop(client(), Pointer::axis_vertical_scroll); + pointer()->sendFrame(client()); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::ScrollEnd); + } +} + + +void tst_seat::fingerScrollSlow() +{ + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *p = pointer(); + auto *c = client(); + p->sendEnter(xdgToplevel()->surface(), {32, 32}); + p->sendFrame(c); + // Send 10 really small updates + for (int i = 0; i < 10; ++i) { + p->sendAxisSource(c, Pointer::axis_source_finger); + p->sendAxis(c, Pointer::axis_vertical_scroll, 0.1); + p->sendFrame(c); + } + p->sendAxisStop(c, Pointer::axis_vertical_scroll); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + QPoint accumulated; + while (window.m_events.first().phase != Qt::ScrollEnd) { + auto e = window.m_events.takeFirst(); + accumulated += e.pixelDelta; + QTRY_VERIFY(!window.m_events.empty()); + } + QCOMPARE(accumulated.y(), -1); +} + +void tst_seat::highResolutionScroll() +{ + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *p = pointer(); + auto *c = client(); + p->sendEnter(xdgToplevel()->surface(), {32, 32}); + p->sendFrame(c); + p->sendAxisSource(c, Pointer::axis_source_wheel); + p->sendAxisValue120(c, Pointer::axis_vertical_scroll, 30); // quarter of a click + p->sendAxis(c, Pointer::axis_vertical_scroll, 3.75); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::NoScrollPhase); + QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll + QCOMPARE(e.angleDelta, QPoint(0, -30)); + // Click scrolls are not continuous and should not have a pixel delta + QCOMPARE(e.pixelDelta, QPoint(0, 0)); + } + + exec([&] { + auto *p = pointer(); + auto *c = client(); + p->sendAxisSource(c, Pointer::axis_source_wheel); + p->sendAxisValue120(c, Pointer::axis_vertical_scroll, 90); // complete the click + p->sendAxis(c, Pointer::axis_vertical_scroll, 11.25); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::NoScrollPhase); + QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll + QCOMPARE(e.angleDelta, QPoint(0, -90)); + // Click scrolls are not continuous and should not have a pixel delta + QCOMPARE(e.pixelDelta, QPoint(0, 0)); + } +} + +void tst_seat::continuousScroll() +{ + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *p = pointer(); + auto *c = client(); + p->sendEnter(xdgToplevel()->surface(), {32, 32}); + p->sendFrame(c); + p->sendAxisSource(c, Pointer::axis_source_continuous); + p->sendAxis(c, Pointer::axis_vertical_scroll, 10); + p->sendAxis(c, Pointer::axis_horizontal_scroll, -5); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::NoScrollPhase); + QCOMPARE(e.pixelDelta, QPoint(5, -10)); + QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // touchpads are not wheels + QCOMPARE(e.inverted, false); + + } + // Sending axis_stop is not mandatory when axis source != finger +} + +void tst_seat::createsTouch() +{ + QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().first()->version(), 9); +} + +class TouchWindow : public QRasterWindow { +public: + TouchWindow() + { + resize(64, 64); + show(); + } + void touchEvent(QTouchEvent *event) override + { + QRasterWindow::touchEvent(event); + m_events.append(Event{event}); + } + struct Event // Because I didn't find a convenient way to copy it entirely + { + explicit Event() = default; + explicit Event(const QTouchEvent *event) + : type(event->type()) + , touchPointStates(event->touchPointStates()) + , touchPoints(event->points()) + { + } + QEvent::Type type{}; + QEventPoint::States touchPointStates{}; + QList touchPoints; + }; + QList m_events; +}; + +void tst_seat::singleTap() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *t = touch(); + auto *c = client(); + t->sendDown(xdgToplevel()->surface(), {32, 32}, 1); + t->sendFrame(c); + t->sendUp(c, 1); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPointStates, QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints.first().position(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchEnd); + QCOMPARE(e.touchPointStates, QEventPoint::State::Released); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints.first().position(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top())); + } +} + +void tst_seat::singleTapFloat() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *t = touch(); + auto *c = client(); + t->sendDown(xdgToplevel()->surface(), {32.75, 32.25}, 1); + t->sendFrame(c); + t->sendUp(c, 1); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPointStates, QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints.first().position(), QPointF(32.75-window.frameMargins().left(), 32.25-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchEnd); + QCOMPARE(e.touchPointStates, QEventPoint::State::Released); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints.first().position(), QPointF(32.75-window.frameMargins().left(), 32.25-window.frameMargins().top())); + } +} + +void tst_seat::multiTouch() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *t = touch(); + auto *c = client(); + + t->sendDown(xdgToplevel()->surface(), {32, 32}, 0); + t->sendDown(xdgToplevel()->surface(), {48, 48}, 1); + t->sendFrame(c); + + // Compositor event order should not change the order of the QTouchEvent::points() + // See QTBUG-77014 + t->sendMotion(c, {49, 48}, 1); + t->sendMotion(c, {33, 32}, 0); + t->sendFrame(c); + + t->sendUp(c, 0); + t->sendFrame(c); + + t->sendUp(c, 1); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPointStates, QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints.size(), 2); + + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints[0].position(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top())); + + QCOMPARE(e.touchPoints[1].state(), QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints[1].position(), QPointF(48-window.frameMargins().left(), 48-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchUpdate); + QCOMPARE(e.touchPoints.size(), 2); + + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Updated); + QCOMPARE(e.touchPoints[0].position(), QPointF(33-window.frameMargins().left(), 32-window.frameMargins().top())); + + QCOMPARE(e.touchPoints[1].state(), QEventPoint::State::Updated); + QCOMPARE(e.touchPoints[1].position(), QPointF(49-window.frameMargins().left(), 48-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchUpdate); + QCOMPARE(e.touchPointStates, QEventPoint::State::Released | QEventPoint::State::Stationary); + QCOMPARE(e.touchPoints.size(), 2); + + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Released); + QCOMPARE(e.touchPoints[0].position(), QPointF(33-window.frameMargins().left(), 32-window.frameMargins().top())); + + QCOMPARE(e.touchPoints[1].state(), QEventPoint::State::Stationary); + QCOMPARE(e.touchPoints[1].position(), QPointF(49-window.frameMargins().left(), 48-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchEnd); + QCOMPARE(e.touchPointStates, QEventPoint::State::Released); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Released); + QCOMPARE(e.touchPoints[0].position(), QPointF(49-window.frameMargins().left(), 48-window.frameMargins().top())); + } +} + +void tst_seat::multiTouchUpAndMotionFrame() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *t = touch(); + auto *c = client(); + + t->sendDown(xdgToplevel()->surface(), {32, 32}, 0); + t->sendDown(xdgToplevel()->surface(), {48, 48}, 1); + t->sendFrame(c); + + // Sending an up event after a frame event, before any motion or down events used to + // unnecessarily trigger a workaround for a bug in an old version of Weston. The workaround + // would prematurely insert a fake frame event splitting the touch event up into two events. + // However, this should only be needed on the up event for the very last touch point. So in + // this test we verify that it doesn't unncecessarily break up the events. + t->sendUp(c, 0); + t->sendMotion(c, {49, 48}, 1); + t->sendFrame(c); + + t->sendUp(c, 1); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints[1].state(), QEventPoint::State::Pressed); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchUpdate); + QCOMPARE(e.touchPoints.size(), 2); + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Released); + QCOMPARE(e.touchPoints[1].state(), QEventPoint::State::Updated); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchEnd); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Released); + } + QVERIFY(window.m_events.empty()); +} + +void tst_seat::tapAndMoveInSameFrame() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *t = touch(); + auto *c = client(); + + t->sendDown(xdgToplevel()->surface(), {32, 32}, 0); + t->sendMotion(c, {33, 33}, 0); + t->sendFrame(c); + + // Don't leave touch in a weird state + t->sendUp(c, 0); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints[0].state(), QEventPoint::State::Pressed); + // Position isn't that important, we just want to make sure we actually get the pressed event + } + + // Make sure we eventually release + QTRY_VERIFY(!window.m_events.empty()); + QTRY_COMPARE(window.m_events.last().touchPoints.first().state(), QEventPoint::State::Released); +} + +void tst_seat::cancelTouch() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *t = touch(); + auto *c = client(); + t->sendDown(xdgToplevel()->surface(), {32, 32}, 1); + t->sendFrame(c); + t->sendCancel(c); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPointStates, QEventPoint::State::Pressed); + QCOMPARE(e.touchPoints.size(), 1); + QCOMPARE(e.touchPoints.first().position(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchCancel); + QCOMPARE(e.touchPoints.size(), 0); + } +} + +QCOMPOSITOR_TEST_MAIN(tst_seat) +#include "tst_seat.moc" diff --git a/tests/auto/wayland/seatv4/CMakeLists.txt b/tests/auto/wayland/seatv4/CMakeLists.txt new file mode 100644 index 00000000000..c4e3ecba1eb --- /dev/null +++ b/tests/auto/wayland/seatv4/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from seatv4.pro. + +##################################################################### +## tst_seatv4 Test: +##################################################################### + +qt_internal_add_test(tst_seatv4 + SOURCES + tst_seatv4.cpp + LIBRARIES + SharedClientTest +) + +## Scopes: +##################################################################### + +qt_internal_extend_target(tst_seatv4 CONDITION QT_FEATURE_cursor + LIBRARIES + Qt::GuiPrivate + Wayland::Cursor +) diff --git a/tests/auto/wayland/seatv4/tst_seatv4.cpp b/tests/auto/wayland/seatv4/tst_seatv4.cpp new file mode 100644 index 00000000000..cc1cf0afd2a --- /dev/null +++ b/tests/auto/wayland/seatv4/tst_seatv4.cpp @@ -0,0 +1,584 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include +#if QT_CONFIG(cursor) +#include +#include +#include +#include +#endif + +using namespace MockCompositor; + +// wl_seat version 5 was introduced in wayland 1.10, and although that's pretty old, +// there are still compositors that have yet to update their implementation to support +// the new version (most importantly our own QtWaylandCompositor). +// As long as that's the case, this test makes sure input events still works on version 4. +class SeatV4Compositor : public DefaultCompositor { +public: + explicit SeatV4Compositor() + { + exec([this] { + m_config.autoConfigure = true; + m_config.autoFrameCallback = false; // for cursor animation test + + removeAll(); + + uint capabilities = Seat::capability_pointer | Seat::capability_keyboard; + int version = 4; + add(capabilities, version); + }); + } +}; + +class tst_seatv4 : public QObject, private SeatV4Compositor +{ + Q_OBJECT +private slots: + void init(); + void cleanup(); + void bindsToSeat(); + void keyboardKeyPress(); +#if QT_CONFIG(cursor) + void createsPointer(); + void setsCursorOnEnter(); + void usesEnterSerial(); + void focusDestruction(); + void mousePress(); + void mousePressFloat(); + void simpleAxis_data(); + void simpleAxis(); + void invalidPointerEvents(); + void scaledCursor(); + void unscaledFallbackCursor(); + void bitmapCursor(); + void hidpiBitmapCursor(); + void hidpiBitmapCursorNonInt(); + void animatedCursor(); +#endif +}; + +void tst_seatv4::init() +{ + // Remove the extra outputs to clean up for the next test + exec([&] { while (auto *o = output(1)) remove(o); }); +} + +void tst_seatv4::cleanup() +{ + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); + QCOMPOSITOR_COMPARE(getAll().size(), 1); // No extra outputs left +} + +void tst_seatv4::bindsToSeat() +{ + QCOMPOSITOR_COMPARE(get()->resourceMap().size(), 1); + QCOMPOSITOR_COMPARE(get()->resourceMap().first()->version(), 4); +} + +void tst_seatv4::keyboardKeyPress() +{ + class Window : public QRasterWindow { + public: + void keyPressEvent(QKeyEvent *) override { m_pressed = true; } + bool m_pressed = false; + }; + + Window window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + uint keyCode = 80; // arbitrarily chosen + exec([&] { + auto *surface = xdgSurface()->m_surface; + keyboard()->sendEnter(surface); + keyboard()->sendKey(client(), keyCode, Keyboard::key_state_pressed); + keyboard()->sendKey(client(), keyCode, Keyboard::key_state_released); + }); + QTRY_VERIFY(window.m_pressed); +} + +#if QT_CONFIG(cursor) + +void tst_seatv4::createsPointer() +{ + QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 4); +} + +void tst_seatv4::setsCursorOnEnter() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {24, 24}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); +} + +void tst_seatv4::usesEnterSerial() +{ + QSignalSpy setCursorSpy(exec([&] { return pointer(); }), &Pointer::setCursor); + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + uint enterSerial = exec([&] { + return pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); + }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + + QTRY_COMPARE(setCursorSpy.size(), 1); + QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial); +} + +void tst_seatv4::focusDestruction() +{ + QSignalSpy setCursorSpy(exec([&] { return pointer(); }), &Pointer::setCursor); + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + // Setting a cursor now is not allowed since there has been no enter event + QCOMPARE(setCursorSpy.size(), 0); + + uint enterSerial = exec([&] { + return pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); + }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + QTRY_COMPARE(setCursorSpy.size(), 1); + QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial); + + // Destroy the focus + window.close(); + + QRasterWindow window2; + window2.resize(64, 64); + window2.show(); + window2.setCursor(Qt::WaitCursor); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + // Setting a cursor now is not allowed since there has been no enter event + xdgPingAndWaitForPong(); + QCOMPARE(setCursorSpy.size(), 0); +} + +void tst_seatv4::mousePress() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *) override { m_pressed = true; } + bool m_pressed = false; + }; + + Window window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgSurface()->m_surface; + pointer()->sendEnter(surface, {32, 32}); + pointer()->sendButton(client(), BTN_LEFT, 1); + pointer()->sendButton(client(), BTN_LEFT, 0); + }); + QTRY_VERIFY(window.m_pressed); +} + +void tst_seatv4::mousePressFloat() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *e) override { m_position = e->position(); } + QPointF m_position; + }; + + Window window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgSurface()->m_surface; + pointer()->sendEnter(surface, {32.75, 32.25}); + pointer()->sendButton(client(), BTN_LEFT, 1); + pointer()->sendButton(client(), BTN_LEFT, 0); + }); + QMargins m = window.frameMargins(); + QPointF pressedPosition(32.75 -m.left(), 32.25 - m.top()); + QTRY_COMPARE(window.m_position, pressedPosition); +} + +void tst_seatv4::simpleAxis_data() +{ + QTest::addColumn("axis"); + QTest::addColumn("value"); + QTest::addColumn("angleDelta"); + + // Directions in regular windows/linux terms (no "natural" scrolling) + QTest::newRow("down") << uint(Pointer::axis_vertical_scroll) << 1.0 << QPoint{0, -12}; + QTest::newRow("up") << uint(Pointer::axis_vertical_scroll) << -1.0 << QPoint{0, 12}; + QTest::newRow("left") << uint(Pointer::axis_horizontal_scroll) << 1.0 << QPoint{-12, 0}; + QTest::newRow("right") << uint(Pointer::axis_horizontal_scroll) << -1.0 << QPoint{12, 0}; + QTest::newRow("up big") << uint(Pointer::axis_vertical_scroll) << -10.0 << QPoint{0, 120}; +} + +void tst_seatv4::simpleAxis() +{ + QFETCH(uint, axis); + QFETCH(qreal, value); + QFETCH(QPoint, angleDelta); + + class WheelWindow : QRasterWindow { + public: + explicit WheelWindow() + { + resize(64, 64); + show(); + } + void wheelEvent(QWheelEvent *event) override + { + QRasterWindow::wheelEvent(event); + // Angle delta should always be provided (says docs) + QVERIFY(!event->angleDelta().isNull()); + + // There are now scroll phases on Wayland prior to v5 + QCOMPARE(event->phase(), Qt::NoScrollPhase); + + // Pixel delta should only be set if we know it's a high-res input device (which we don't) + QCOMPARE(event->pixelDelta(), QPoint(0, 0)); + + // The axis vector of the event is already in surface space, so there is now way to tell + // whether it is inverted or not. + QCOMPARE(event->inverted(), false); + + // We didn't press any buttons + QCOMPARE(event->buttons(), Qt::NoButton); + + // There has been no information about what created the event. + // Documentation says not synthesized is appropriate in such cases + QCOMPARE(event->source(), Qt::MouseEventNotSynthesized); + + m_events.append(Event{event->pixelDelta(), event->angleDelta()}); + } + struct Event // Because I didn't find a convenient way to copy it entirely + { + QPoint pixelDelta; + QPoint angleDelta; // eights of a degree, positive is upwards, left + }; + QList m_events; + }; + + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + Surface *surface = xdgSurface()->m_surface; + pointer()->sendEnter(surface, {32, 32}); + wl_client *client = surface->resource()->client(); + // Length of vector in surface-local space. i.e. positive is downwards + pointer()->sendAxis( + client, + Pointer::axis(axis), + value // Length of vector in surface-local space. i.e. positive is downwards + ); + }); + + QTRY_COMPARE(window.m_events.size(), 1); + auto event = window.m_events.takeFirst(); + QCOMPARE(event.angleDelta, angleDelta); +} + +void tst_seatv4::invalidPointerEvents() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { + auto *p = pointer(); + auto *c = client(); + // Purposefully send events without a wl_pointer.enter + p->sendMotion(c, {32, 32}); + p->sendButton(c, BTN_LEFT, Pointer::button_state_pressed); + p->sendAxis(c, Pointer::axis_vertical_scroll, 1.0); + }); + + // Make sure we get here without crashing + xdgPingAndWaitForPong(); +} + +static bool supportsCursorSize(uint size, wl_shm *shm) +{ + auto *theme = wl_cursor_theme_load(qgetenv("XCURSOR_THEME"), size, shm); + if (!theme) + return false; + + constexpr std::array names{"left_ptr", "default", "left_arrow", "top_left_arrow"}; + for (const char *name : names) { + if (auto *cursor = wl_cursor_theme_get_cursor(theme, name)) { + auto *image = cursor->images[0]; + return image->width == image->height && image->width == size; + } + } + return false; +} + +static bool supportsCursorSizes(const QList &sizes) +{ + auto *waylandIntegration = static_cast(QGuiApplicationPrivate::platformIntegration()); + wl_shm *shm = waylandIntegration->display()->shm()->object(); + return std::all_of(sizes.begin(), sizes.end(), [&](uint size) { + return supportsCursorSize(size, shm); + }); +} + +static uint defaultCursorSize() { + const int xCursorSize = qEnvironmentVariableIntValue("XCURSOR_SIZE"); + return xCursorSize > 0 ? uint(xCursorSize) : 24; +} + +void tst_seatv4::scaledCursor() +{ + const uint defaultSize = defaultCursorSize(); + if (!supportsCursorSizes({defaultSize, defaultSize * 2})) + QSKIP("Cursor themes with default size and 2x default size not found."); + + // Add a highdpi output + exec([&] { + OutputData d; + d.scale = 2; + d.position = {1920, 0}; + add(d); + }); + + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer); + QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.bufferScale, 1); + QSize unscaledPixelSize = exec([&] { + return cursorSurface()->m_committed.buffer->size(); + }); + + exec([&] { + auto *surface = cursorSurface(); + surface->sendEnter(getAll()[1]); + surface->sendLeave(getAll()[0]); + }); + + QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.buffer->size(), unscaledPixelSize * 2); + + // Remove the extra output to clean up for the next test + exec([&] { remove(output(1)); }); +} + +void tst_seatv4::unscaledFallbackCursor() +{ + const uint defaultSize = defaultCursorSize(); + if (!supportsCursorSizes({defaultSize})) + QSKIP("Default cursor size not supported"); + + const int screens = 4; // with scales 1, 2, 4, 8 + + exec([&] { + for (int i = 1; i < screens; ++i) { + OutputData d; + d.scale = int(qPow(2, i)); + d.position = {1920 * i, 0}; + add(d); + } + }); + + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer); + QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.bufferScale, 1); + QSize unscaledPixelSize = exec([&] { + return cursorSurface()->m_committed.buffer->size(); + }); + + QCOMPARE(unscaledPixelSize.width(), int(defaultSize)); + QCOMPARE(unscaledPixelSize.height(), int(defaultSize)); + + for (int i = 1; i < screens; ++i) { + exec([&] { + auto *surface = cursorSurface(); + surface->sendEnter(getAll()[i]); + surface->sendLeave(getAll()[i-1]); + }); + + xdgPingAndWaitForPong(); // Give the client a chance to mess up + + // Surface size (buffer size / scale) should stay constant + QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.buffer->size() / cursorSurface()->m_committed.bufferScale, unscaledPixelSize); + } + + // Remove the extra outputs to clean up for the next test + exec([&] { while (auto *o = output(1)) remove(o); }); +} + +void tst_seatv4::bitmapCursor() +{ + // Add a highdpi output + exec([&] { + OutputData d; + d.scale = 2; + d.position = {1920, 0}; + add(d); + }); + + QRasterWindow window; + window.resize(64, 64); + + QPixmap pixmap(24, 24); + pixmap.setDevicePixelRatio(1); + QPoint hotspot(12, 12); // In device pixel coordinates + QCursor cursor(pixmap, hotspot.x(), hotspot.y()); + window.setCursor(cursor); + + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(24, 24)); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 1); + QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(12, 12)); + + exec([&] { + auto *surface = cursorSurface(); + surface->sendEnter(getAll()[1]); + surface->sendLeave(getAll()[0]); + }); + + xdgPingAndWaitForPong(); + + // Everything should remain the same, the cursor still has dpr 1 + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 1); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(24, 24)); + QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(12, 12)); + + // Remove the extra output to clean up for the next test + exec([&] { remove(getAll()[1]); }); +} + +void tst_seatv4::hidpiBitmapCursor() +{ + // Add a highdpi output + exec([&] { + OutputData d; + d.scale = 2; + d.position = {1920, 0}; + add(d); + }); + + QRasterWindow window; + window.resize(64, 64); + + QPixmap pixmap(48, 48); + pixmap.setDevicePixelRatio(2); + QPoint hotspot(12, 12); // In device pixel coordinates + QCursor cursor(pixmap, hotspot.x(), hotspot.y()); + window.setCursor(cursor); + + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(48, 48)); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 2); + QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(12, 12)); + + exec([&] { + auto *surface = cursorSurface(); + surface->sendEnter(getAll()[1]); + surface->sendLeave(getAll()[0]); + }); + + xdgPingAndWaitForPong(); + + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 2); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(48, 48)); + QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(12, 12)); + + // Remove the extra output to clean up for the next test + exec([&] { remove(getAll()[1]); }); +} + +void tst_seatv4::hidpiBitmapCursorNonInt() +{ + QRasterWindow window; + window.resize(64, 64); + + QPixmap pixmap(100, 100); + pixmap.setDevicePixelRatio(2.5); // dpr width is now 100 / 2.5 = 40 + QPoint hotspot(20, 20); // In device pixel coordinates (middle of buffer) + QCursor cursor(pixmap, hotspot.x(), hotspot.y()); + window.setCursor(cursor); + + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(100, 100)); + QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 2); + // Verify that the hotspot was scaled correctly + // Surface size is now 100 / 2 = 50, so the middle should be at 25 in surface coordinates + QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(25, 25)); +} + +void tst_seatv4::animatedCursor() +{ + QRasterWindow window; + window.resize(64, 64); + window.setCursor(Qt::WaitCursor); // TODO: verify that the theme has an animated wait cursor or skip test + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([&] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); }); + QCOMPOSITOR_TRY_VERIFY(cursorSurface()); + + // We should get the first buffer without waiting for a frame callback + QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer); + QSignalSpy bufferSpy(exec([&] { return cursorSurface(); }), &Surface::bufferCommitted); + + exec([&] { + // Make sure no extra buffers have arrived + QVERIFY(bufferSpy.empty()); + + // The client should send a frame request in order to time animations correctly + QVERIFY(!cursorSurface()->m_waitingFrameCallbacks.empty()); + + // Tell the client it's time to animate + cursorSurface()->sendFrameCallbacks(); + }); + + // Verify that we get a new cursor buffer + QTRY_COMPARE(bufferSpy.size(), 1); +} + +#endif // QT_CONFIG(cursor) + +QCOMPOSITOR_TEST_MAIN(tst_seatv4) +#include "tst_seatv4.moc" diff --git a/tests/auto/wayland/seatv7/CMakeLists.txt b/tests/auto/wayland/seatv7/CMakeLists.txt new file mode 100644 index 00000000000..cf3ade8c723 --- /dev/null +++ b/tests/auto/wayland/seatv7/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_seatv7 Test: +##################################################################### + +qt_internal_add_test(tst_seatv7 + SOURCES + tst_seatv7.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/seatv7/tst_seatv7.cpp b/tests/auto/wayland/seatv7/tst_seatv7.cpp new file mode 100644 index 00000000000..f82b84b5828 --- /dev/null +++ b/tests/auto/wayland/seatv7/tst_seatv7.cpp @@ -0,0 +1,129 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include +#include + +using namespace MockCompositor; + +class SeatCompositor : public DefaultCompositor { +public: + explicit SeatCompositor() + { + exec([this] { + m_config.autoConfigure = true; + + removeAll(); + + uint capabilities = MockCompositor::Seat::capability_pointer | MockCompositor::Seat::capability_touch; + int version = 7; + add(capabilities, version); + }); + } +}; + +class tst_seatv7 : public QObject, private SeatCompositor +{ + Q_OBJECT +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void bindsToSeat(); + + // Pointer tests + void wheelDiscreteScroll_data(); + void wheelDiscreteScroll(); +}; + +void tst_seatv7::bindsToSeat() +{ + QCOMPOSITOR_COMPARE(get()->resourceMap().size(), 1); + QCOMPOSITOR_COMPARE(get()->resourceMap().first()->version(), 7); +} + +class WheelWindow : QRasterWindow { +public: + WheelWindow() + { + resize(64, 64); + show(); + } + void wheelEvent(QWheelEvent *event) override + { + QRasterWindow::wheelEvent(event); +// qDebug() << event << "angleDelta" << event->angleDelta() << "pixelDelta" << event->pixelDelta(); + + if (event->phase() != Qt::ScrollUpdate && event->phase() != Qt::NoScrollPhase) { + // Shouldn't have deltas in the these phases + QCOMPARE(event->angleDelta(), QPoint(0, 0)); + QCOMPARE(event->pixelDelta(), QPoint(0, 0)); + } + + // The axis vector of the event is already in surface space, so there is now way to tell + // whether it is inverted or not. + QCOMPARE(event->inverted(), false); + + // We didn't press any buttons + QCOMPARE(event->buttons(), Qt::NoButton); + + m_events.append(Event{event}); + } + struct Event // Because I didn't find a convenient way to copy it entirely + { + explicit Event() = default; + explicit Event(const QWheelEvent *event) + : phase(event->phase()) + , pixelDelta(event->pixelDelta()) + , angleDelta(event->angleDelta()) + , source(event->source()) + { + } + Qt::ScrollPhase phase{}; + QPoint pixelDelta; + QPoint angleDelta; // eights of a degree, positive is upwards, left + Qt::MouseEventSource source{}; + }; + QList m_events; +}; + +void tst_seatv7::wheelDiscreteScroll_data() +{ + QTest::addColumn("source"); + QTest::newRow("wheel") << uint(Pointer::axis_source_wheel); + QTest::newRow("wheel tilt") << uint(Pointer::axis_source_wheel_tilt); +} + +void tst_seatv7::wheelDiscreteScroll() +{ + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QFETCH(uint, source); + + exec([&] { + auto *p = pointer(); + auto *c = client(); + p->sendEnter(xdgToplevel()->surface(), {32, 32}); + p->sendFrame(c); + p->sendAxisSource(c, Pointer::axis_source(source)); + p->sendAxisDiscrete(c, Pointer::axis_vertical_scroll, 1); // 1 click downwards + p->sendAxis(c, Pointer::axis_vertical_scroll, 1.0); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::NoScrollPhase); + QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll + // According to the docs the angle delta is in eights of a degree and most mice have + // 1 click = 15 degrees. The angle delta should therefore be: + // 15 degrees / (1/8 eights per degrees) = 120 eights of degrees. + QCOMPARE(e.angleDelta, QPoint(0, -120)); + // Click scrolls are not continuous and should not have a pixel delta + QCOMPARE(e.pixelDelta, QPoint(0, 0)); + } +} + +QCOMPOSITOR_TEST_MAIN(tst_seatv7) +#include "tst_seatv7.moc" diff --git a/tests/auto/wayland/shared/CMakeLists.txt b/tests/auto/wayland/shared/CMakeLists.txt new file mode 100644 index 00000000000..8c54932e2a7 --- /dev/null +++ b/tests/auto/wayland/shared/CMakeLists.txt @@ -0,0 +1,70 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +##Client test shared components: +##################################################################### + +qt_manual_moc(moc_files + mockcompositor.h + coreprotocol.h + corecompositor.h + datadevice.h + fullscreenshellv1.h + fractionalscalev1.h + textinput.h + qttextinput.h + viewport.h + xdgdialog.h + xdgoutputv1.h + xdgshell.h +) + +add_library(SharedClientTest + OBJECT + corecompositor.cpp corecompositor.h + coreprotocol.cpp coreprotocol.h + datadevice.cpp datadevice.h + fullscreenshellv1.cpp fullscreenshellv1.h + fractionalscalev1.cpp fractionalscalev1.h + mockcompositor.cpp mockcompositor.h + textinput.cpp textinput.h + qttextinput.cpp qttextinput.h + xdgoutputv1.cpp xdgoutputv1.h + xdgshell.cpp xdgshell.h + xdgdialog.cpp xdgdialog.h + viewport.cpp viewport.h + ${moc_files} +) + +qt6_generate_wayland_protocol_server_sources(SharedClientTest + FILES + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/cursor-shape/cursor-shape-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/fullscreen-shell/fullscreen-shell-unstable-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/wp-primary-selection/wp-primary-selection-unstable-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/tablet/tablet-unstable-v2.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/text-input/v2/text-input-unstable-v2.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/extensions/qt-text-input-method-unstable-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/fractional-scale/fractional-scale-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/viewporter/viewporter.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/wayland/wayland.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/xdg-decoration/xdg-decoration-unstable-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/xdg-dialog/xdg-dialog-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/xdg-output/xdg-output-unstable-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/wayland/protocols/xdg-shell/xdg-shell.xml +) + +if(QT_FEATURE_opengl) + set(optional_libraries Qt::OpenGL) +endif() + +target_link_libraries(SharedClientTest + PUBLIC + Qt::Gui + ${optional_libraries} + Qt::WaylandClientPrivate + Wayland::Server + Threads::Threads # special case +) + +target_include_directories(SharedClientTest PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/tests/auto/wayland/shared/corecompositor.cpp b/tests/auto/wayland/shared/corecompositor.cpp new file mode 100644 index 00000000000..d110768ec00 --- /dev/null +++ b/tests/auto/wayland/shared/corecompositor.cpp @@ -0,0 +1,123 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "corecompositor.h" +#include + +namespace MockCompositor { + +CoreCompositor::CoreCompositor(CompositorType t, int socketFd) + : m_type(t) + , m_display(wl_display_create()) + , m_eventLoop(wl_display_get_event_loop(m_display)) + + // Start dispatching + , m_dispatchThread([this](){ + while (m_running) { + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + dispatch(); + } + }) +{ + if (socketFd == -1) { + QByteArray socketName = wl_display_add_socket_auto(m_display); + qputenv("WAYLAND_DISPLAY", socketName); + } else { + wl_display_add_socket_fd(m_display, socketFd); + } + m_timer.start(); + Q_ASSERT(isClean()); +} + +CoreCompositor::~CoreCompositor() +{ + m_running = false; + m_dispatchThread.join(); + wl_display_destroy_clients(m_display); + wl_display_destroy(m_display); + qDebug() << "cleanup"; +} + +bool CoreCompositor::isClean() +{ + Lock lock(this); + for (auto *global : std::as_const(m_globals)) { + if (!global->isClean()) + return false; + } + return true; +} + +QString CoreCompositor::dirtyMessage() +{ + Lock lock(this); + QStringList messages; + for (auto *global : std::as_const(m_globals)) { + if (!global->isClean()) + messages << (global->metaObject()->className() % QLatin1String(": ") % global->dirtyMessage()); + } + return messages.join(", "); +} + +void CoreCompositor::dispatch(int timeout) +{ + Lock lock(this); + wl_display_flush_clients(m_display); + wl_event_loop_dispatch(m_eventLoop, timeout); +} + +/*! + * \brief Adds a new global interface for the compositor + * + * Takes ownership of \a global + */ +void CoreCompositor::add(Global *global) +{ + warnIfNotLockedByThread(Q_FUNC_INFO); + m_globals.append(global); +} + +void CoreCompositor::remove(Global *global) +{ + warnIfNotLockedByThread(Q_FUNC_INFO); + m_globals.removeAll(global); + delete global; +} + +uint CoreCompositor::nextSerial() +{ + warnIfNotLockedByThread(Q_FUNC_INFO); + return wl_display_next_serial(m_display); +} + +uint CoreCompositor::currentTimeMilliseconds() +{ + warnIfNotLockedByThread(Q_FUNC_INFO); + return uint(m_timer.elapsed()); +} + +wl_client *CoreCompositor::client(int index) +{ + warnIfNotLockedByThread(Q_FUNC_INFO); + wl_list *clients = wl_display_get_client_list(m_display); + wl_client *client = nullptr; + int i = 0; + wl_client_for_each(client, clients) { + if (i++ == index) + return client; + } + return nullptr; +} + +void CoreCompositor::warnIfNotLockedByThread(const char *caller) +{ + if (!m_lock || !m_lock->isOwnedByCurrentThread()) { + qWarning() << caller << "called without locking the compositor to the current thread." + << "This means the compositor can start dispatching at any moment," + << "potentially leading to threading issues." + << "Unless you know what you are doing you should probably fix the test" + << "by locking the compositor before accessing it (see mutex())."; + } +} + +} // namespace MockCompositor diff --git a/tests/auto/wayland/shared/corecompositor.h b/tests/auto/wayland/shared/corecompositor.h new file mode 100644 index 00000000000..2df1b57589a --- /dev/null +++ b/tests/auto/wayland/shared/corecompositor.h @@ -0,0 +1,208 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_CORECOMPOSITOR_H +#define MOCKCOMPOSITOR_CORECOMPOSITOR_H + +#include + +#include + +struct wl_resource; + +namespace MockCompositor { + +class Global : public QObject +{ + Q_OBJECT +public: + virtual bool isClean() { return true; } + virtual QString dirtyMessage() { return isClean() ? "clean" : "dirty"; } +}; + +class CoreCompositor +{ +public: + enum CompositorType { + Default, + Legacy // wl-shell + }; + + CompositorType m_type = Default; + explicit CoreCompositor(CompositorType t = Default, int socketFd = -1); + + ~CoreCompositor(); + bool isClean(); + QString dirtyMessage(); + void dispatch(int timeout = 0); + + template + auto exec(function_type func, arg_types&&... args) -> decltype(func()) + { + Lock lock(this); + return func(std::forward(args)...); + } + + template + auto call(function_type func, arg_types&&... args) -> decltype(func()) + { + Lock lock(this); + auto boundFunc = std::bind(func, this); + return boundFunc(this, std::forward(args)...); + } + + // Unsafe section below, YOU are responsible that the compositor is locked or + // this is run through the mutex() method! + + void add(Global *global); + void remove(Global *global); + + /*! + * \brief Constructs and adds a new global with the given parameters + * + * Convenience function. i.e. + * + * compositor->add(new MyGlobal(compositor, version); + * + * can be written as: + * + * compositor->add(version); + * + * Returns the new global + */ + template + global_type *add(arg_types&&... args) + { + warnIfNotLockedByThread(Q_FUNC_INFO); + auto *global = new global_type(this, std::forward(args)...); + m_globals.append(global); + return global; + } + + /*! + * \brief Removes all globals of the given type + * + * Convenience function + */ + template + void removeAll() + { + const auto globals = getAll(); + for (auto global : globals) + remove(global); + } + + /*! + * \brief Returns a global with the given type, if any + */ + template + global_type *get() + { + warnIfNotLockedByThread(Q_FUNC_INFO); + for (auto *global : std::as_const(m_globals)) { + if (auto *casted = qobject_cast(global)) + return casted; + } + return nullptr; + } + + /*! + * \brief Returns the nth global with the given type, if any + */ + template + global_type *get(int index) + { + warnIfNotLockedByThread(Q_FUNC_INFO); + for (auto *global : std::as_const(m_globals)) { + if (auto *casted = qobject_cast(global)) { + if (index--) + continue; + return casted; + } + } + return nullptr; + } + + /*! + * \brief Returns all globals with the given type, if any + */ + template + QList getAll() + { + warnIfNotLockedByThread(Q_FUNC_INFO); + QList matching; + for (auto *global : std::as_const(m_globals)) { + if (auto *casted = qobject_cast(global)) + matching.append(casted); + } + return matching; + } + + uint nextSerial(); + uint currentTimeMilliseconds(); + wl_client *client(int index = 0); + void warnIfNotLockedByThread(const char* caller = "warnIfNotLockedbyThread"); + +public: + // Only use this carefully from the test thread (i.e. lock first) + wl_display *m_display = nullptr; + +protected: + class Lock { + public: + explicit Lock(CoreCompositor *compositor) + : m_compositor(compositor) + , m_threadId(std::this_thread::get_id()) + { + // Can't use a QMutexLocker here, as it's not movable + compositor->m_mutex.lock(); + Q_ASSERT(compositor->m_lock == nullptr); + compositor->m_lock = this; + } + ~Lock() + { + Q_ASSERT(m_compositor->m_lock == this); + m_compositor->m_lock = nullptr; + m_compositor->m_mutex.unlock(); + } + + // Move semantics + Lock(Lock &&) = default; + Lock &operator=(Lock &&) = default; + + // Disable copying + Lock(const Lock &) = delete; + Lock &operator=(const Lock &) = delete; + + bool isOwnedByCurrentThread() const { return m_threadId == std::this_thread::get_id(); } + private: + CoreCompositor *m_compositor = nullptr; + std::thread::id m_threadId; + }; + wl_event_loop *m_eventLoop = nullptr; + bool m_running = true; + QList m_globals; + QElapsedTimer m_timer; + +private: + Lock *m_lock = nullptr; + QMutex m_mutex; + std::thread m_dispatchThread; +}; + +template +QByteArray toByteArray(container_type container) +{ + return QByteArray(reinterpret_cast(container.data()), sizeof (container[0]) * container.size()); +} + +template +return_type *fromResource(::wl_resource *resource) { + if (auto *r = return_type::Resource::fromResource(resource)) + return static_cast(r->object()); + return nullptr; +} + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_CORECOMPOSITOR_H diff --git a/tests/auto/wayland/shared/coreprotocol.cpp b/tests/auto/wayland/shared/coreprotocol.cpp new file mode 100644 index 00000000000..aba270f13bc --- /dev/null +++ b/tests/auto/wayland/shared/coreprotocol.cpp @@ -0,0 +1,685 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "coreprotocol.h" +#include "datadevice.h" + +namespace MockCompositor { + +Surface::Surface(WlCompositor *wlCompositor, wl_client *client, int id, int version) + : QtWaylandServer::wl_surface(client, id, version) + , m_wlCompositor(wlCompositor) + , m_wlshell(wlCompositor->m_compositor->m_type == CoreCompositor::CompositorType::Legacy) +{ +} + +Surface::~Surface() +{ + // TODO: maybe make sure buffers are released? + qDeleteAll(m_commits); + if (m_wlShellSurface) + m_wlShellSurface->m_surface = nullptr; + delete m_role; +} + +void Surface::sendFrameCallbacks() +{ + uint time = m_wlCompositor->m_compositor->currentTimeMilliseconds(); + for (auto *callback : m_waitingFrameCallbacks) + callback->sendDone(time); + m_waitingFrameCallbacks.clear(); +} + +void Surface::sendEnter(Output *output) +{ + m_outputs.append(output); + const auto outputResources = output->resourceMap().values(resource()->client()); + for (auto outputResource: outputResources) + wl_surface::send_enter(resource()->handle, outputResource->handle); +} + +void Surface::sendLeave(Output *output) +{ + m_outputs.removeOne(output); + const auto outputResources = output->resourceMap().values(resource()->client()); + for (auto outputResource: outputResources) + wl_surface::send_leave(resource()->handle, outputResource->handle); +} + +void Surface::map() +{ + m_mapped = true; +} + +void Surface::unmap() +{ + m_mapped = false; +} + +void Surface::surface_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource); + for (auto *commit : m_commits) + delete commit->commitSpecific.frame; + bool removed = m_wlCompositor->m_surfaces.removeOne(this); + Q_ASSERT(removed); + delete this; +} + +void Surface::surface_destroy(Resource *resource) +{ + if (m_wlShellSurface) // on wl-shell the shell surface is automatically destroyed with the surface + wl_resource_destroy(m_wlShellSurface->resource()->handle); + Q_ASSERT(!m_wlShellSurface); + wl_resource_destroy(resource->handle); +} + +void Surface::surface_attach(Resource *resource, wl_resource *buffer, int32_t x, int32_t y) +{ + Q_UNUSED(resource); + if (m_wlshell) { + m_buffer = buffer; + if (!buffer) { + m_image = QImage(); + unmap(); + } + } else { + QPoint offset(x, y); + if (resource->version() < 5) + m_pending.commitSpecific.attachOffset = offset; + + m_pending.buffer = fromResource(buffer); + m_pending.commitSpecific.attached = true; + + emit attach(buffer, offset); + } +} + +void Surface::surface_set_buffer_scale(QtWaylandServer::wl_surface::Resource *resource, int32_t scale) +{ + Q_UNUSED(resource); + m_pending.bufferScale = scale; +} + +void Surface::surface_commit(Resource *resource) +{ + Q_UNUSED(resource); + + if (m_wlshell) { + if (m_buffer) { + struct ::wl_shm_buffer *shm_buffer = wl_shm_buffer_get(m_buffer); + if (shm_buffer) { + int stride = wl_shm_buffer_get_stride(shm_buffer); + uint format = wl_shm_buffer_get_format(shm_buffer); + Q_UNUSED(format); + void *data = wl_shm_buffer_get_data(shm_buffer); + const uchar *char_data = static_cast(data); + QImage img(char_data, wl_shm_buffer_get_width(shm_buffer), wl_shm_buffer_get_height(shm_buffer), stride, QImage::Format_ARGB32_Premultiplied); + m_image = img; + } + } + + for (wl_resource *frameCallback : std::exchange(m_frameCallbackList, {})) { + auto time = m_wlCompositor->m_compositor->currentTimeMilliseconds(); + wl_callback_send_done(frameCallback, time); + wl_resource_destroy(frameCallback); + } + } else { + m_committed = m_pending; + m_commits.append(new DoubleBufferedState(m_committed)); + + if (auto *frame = m_pending.commitSpecific.frame) + m_waitingFrameCallbacks.append(frame); + + m_pending.commitSpecific = PerCommitData(); + emit commit(); + if (m_committed.commitSpecific.attached) + emit bufferCommitted(); + } +} + +void Surface::surface_frame(Resource *resource, uint32_t callback) +{ + if (m_wlshell) { + wl_resource *frameCallback = wl_resource_create(resource->client(), &wl_callback_interface, 1, callback); + m_frameCallbackList << frameCallback; + } else { + // Although valid, there is really no point having multiple frame requests in the same commit. + // Make sure we don't do it + QCOMPARE(m_pending.commitSpecific.frame, nullptr); + + auto *frame = new Callback(resource->client(), callback, 1); + m_pending.commitSpecific.frame = frame; + } +} + +void Surface::surface_offset(Resource *resource, int32_t x, int32_t y) +{ + Q_UNUSED(resource); + QPoint offset(x, y); + m_pending.commitSpecific.attachOffset = offset; +} + +bool WlCompositor::isClean() { + for (auto *surface : std::as_const(m_surfaces)) { + if (!CursorRole::fromSurface(surface)) { + if (surface->isMapped()) + return false; + } + } + return true; +} + +QString WlCompositor::dirtyMessage() +{ + if (isClean()) + return "clean"; + QStringList messages; + for (auto *s : std::as_const(m_surfaces)) { + QString role = s->m_role ? s->m_role->staticMetaObject.className(): "none/unknown"; + messages << "Surface with role: " + role; + } + return "Dirty, surfaces left:\n\t" + messages.join("\n\t"); +} + +void SubCompositor::subcompositor_get_subsurface(Resource *resource, uint32_t id, + ::wl_resource *surfaceResource, + ::wl_resource *parent) +{ + QTRY_VERIFY(parent); + QTRY_VERIFY(surfaceResource); + auto surface = fromResource(surfaceResource); + if (!surface->m_role) { + surface->m_role = new SubSurfaceRole; + } else if (!qobject_cast(surface->m_role)) { + QFAIL(QByteArrayLiteral("surface already has role") + surface->m_role->metaObject()->className()); + return; + } + + auto *subsurface = new Subsurface(this, resource->client(), id, resource->version()); + m_subsurfaces.append(subsurface); + emit subsurfaceCreated(subsurface); +} + +void Output::sendGeometry() +{ + const auto resources = resourceMap().values(); + for (auto r : resources) + sendGeometry(r); +} + +void Output::sendGeometry(Resource *resource) +{ + // TODO: check resource version as well? + wl_output::send_geometry(resource->handle, + m_data.position.x(), m_data.position.y(), + m_data.physicalSize.width(), m_data.physicalSize.height(), + m_data.subpixel, m_data.make, m_data.model, m_data.transform); +} + +void Output::sendScale(int factor) +{ + Q_ASSERT(m_version >= WL_OUTPUT_SCALE_SINCE_VERSION); + m_data.scale = factor; + const auto resources = resourceMap().values(); + for (auto r : resources) + sendScale(r); +} + +void Output::sendScale(Resource *resource) +{ + Q_ASSERT(m_version >= WL_OUTPUT_SCALE_SINCE_VERSION); + // TODO: check resource version as well? + wl_output::send_scale(resource->handle, m_data.scale); +} + +void Output::sendDone(wl_client *client) +{ + Q_ASSERT(m_version >= WL_OUTPUT_DONE_SINCE_VERSION); + auto resources = resourceMap().values(client); + for (auto *r : resources) + wl_output::send_done(r->handle); +} + +void Output::sendDone() +{ + Q_ASSERT(m_version >= WL_OUTPUT_DONE_SINCE_VERSION); + // TODO: check resource version as well? + const auto resources = resourceMap().values(); + for (auto r : resources) + wl_output::send_done(r->handle); +} + +void Output::output_bind_resource(QtWaylandServer::wl_output::Resource *resource) +{ + sendGeometry(resource); + send_mode(resource->handle, mode_preferred | mode_current, + m_data.mode.resolution.width(), m_data.mode.resolution.height(), m_data.mode.refreshRate); + if (m_version >= WL_OUTPUT_SCALE_SINCE_VERSION) + sendScale(resource); + + if (m_version >= WL_OUTPUT_DONE_SINCE_VERSION) + wl_output::send_done(resource->handle); + + Q_EMIT outputBound(resource); +} + +// Seat stuff +Seat::Seat(CoreCompositor *compositor, uint capabilities, int version, const QString &seatName) //TODO: check version + : QtWaylandServer::wl_seat(compositor->m_display, version) + , m_seatName(seatName) + , m_compositor(compositor) +{ + setCapabilities(capabilities); +} + +Seat::~Seat() +{ + qDeleteAll(m_oldPointers); + delete m_pointer; + + qDeleteAll(m_oldTouchs); + delete m_touch; + + qDeleteAll(m_oldKeyboards); + delete m_keyboard; +} + +void Seat::setCapabilities(uint capabilities) { + m_capabilities = capabilities; + + if (m_capabilities & capability_pointer) { + if (!m_pointer) + m_pointer = (new Pointer(this)); + } else if (m_pointer) { + m_oldPointers << m_pointer; + m_pointer = nullptr; + } + + if (m_capabilities & capability_touch) { + if (!m_touch) + m_touch = (new Touch(this)); + } else if (m_touch) { + m_oldTouchs << m_touch; + m_touch = nullptr; + } + + if (m_capabilities & capability_keyboard) { + if (!m_keyboard) + m_keyboard = (new Keyboard(this)); + } else if (m_keyboard) { + m_oldKeyboards << m_keyboard; + m_keyboard = nullptr; + } + + for (auto *resource : resourceMap()) + wl_seat::send_capabilities(resource->handle, capabilities); +} + +void Seat::seat_get_pointer(Resource *resource, uint32_t id) +{ + if (~m_capabilities & capability_pointer) { + qWarning() << "Client requested a wl_pointer without the capability being available." + << "This Could be a race condition when hotunplugging," + << "but is most likely a client error"; + Pointer *pointer = new Pointer(this); + pointer->add(resource->client(), id, resource->version()); + // TODO: mark as destroyed + m_oldPointers << pointer; + return; + } + m_pointer->add(resource->client(), id, resource->version()); +} + +void Seat::seat_get_touch(QtWaylandServer::wl_seat::Resource *resource, uint32_t id) +{ + if (~m_capabilities & capability_touch) { + qWarning() << "Client requested a wl_touch without the capability being available." + << "This Could be a race condition when hotunplugging," + << "but is most likely a client error"; + Touch *touch = new Touch(this); + touch->add(resource->client(), id, resource->version()); + // TODO: mark as destroyed + m_oldTouchs << touch; + return; + } + m_touch->add(resource->client(), id, resource->version()); +} + +void Seat::seat_get_keyboard(QtWaylandServer::wl_seat::Resource *resource, uint32_t id) +{ + if (~m_capabilities & capability_keyboard) { + qWarning() << "Client requested a wl_keyboard without the capability being available." + << "This Could be a race condition when hotunplugging," + << "but is most likely a client error"; + Keyboard *keyboard = new Keyboard(this); + keyboard->add(resource->client(), id, resource->version()); + // TODO: mark as destroyed + m_oldKeyboards << keyboard; + return; + } + m_keyboard->add(resource->client(), id, resource->version()); +} + +Surface *Pointer::cursorSurface() +{ + return m_cursorRole ? m_cursorRole->m_surface : nullptr; +} + +uint Pointer::sendEnter(Surface *surface, const QPointF &position) +{ + wl_fixed_t x = wl_fixed_from_double(position.x()); + wl_fixed_t y = wl_fixed_from_double(position.y()); + + uint serial = m_seat->m_compositor->nextSerial(); + m_enterSerials << serial; + m_cursorRole.clear(); // According to the protocol, the pointer image is undefined after enter + + wl_client *client = surface->resource()->client(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) { + wl_pointer::send_enter(r->handle, serial, surface->resource()->handle, x ,y); + } + return serial; +} + +uint Pointer::sendLeave(Surface *surface) +{ + uint serial = m_seat->m_compositor->nextSerial(); + + wl_client *client = surface->resource()->client(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + wl_pointer::send_leave(r->handle, serial, surface->resource()->handle); + return serial; +} + +// Make sure you call enter, frame etc. first +void Pointer::sendMotion(wl_client *client, const QPointF &position) +{ + wl_fixed_t x = wl_fixed_from_double(position.x()); + wl_fixed_t y = wl_fixed_from_double(position.y()); + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_motion(r->handle, time, x, y); +} + +// Make sure you call enter, frame etc. first +uint Pointer::sendButton(wl_client *client, uint button, uint state) +{ + Q_ASSERT(state == button_state_pressed || state == button_state_released); + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + uint serial = m_seat->m_compositor->nextSerial(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_button(r->handle, serial, time, button, state); + return serial; +} + +// Make sure you call enter, frame etc. first +void Pointer::sendAxis(wl_client *client, axis axis, qreal value) +{ + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + wl_fixed_t val = wl_fixed_from_double(value); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_axis(r->handle, time, axis, val); +} + +void Pointer::sendAxisDiscrete(wl_client *client, QtWaylandServer::wl_pointer::axis axis, int discrete) +{ + // TODO: assert v5 or newer + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_axis_discrete(r->handle, axis, discrete); +} + +void Pointer::sendAxisSource(wl_client *client, QtWaylandServer::wl_pointer::axis_source source) +{ + // TODO: assert v5 or newer + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_axis_source(r->handle, source); +} + +void Pointer::sendAxisStop(wl_client *client, QtWaylandServer::wl_pointer::axis axis) +{ + // TODO: assert v5 or newer + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_axis_stop(r->handle, time, axis); +} + +void Pointer::sendFrame(wl_client *client) +{ + //TODO: assert version 5 or newer? + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_frame(r->handle); +} + +void Pointer::sendAxisValue120(wl_client *client, QtWaylandServer::wl_pointer::axis axis, int value120) +{ + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_axis_value120(r->handle, axis, value120); +} + +void Pointer::sendAxisRelativeDirection(wl_client *client, QtWaylandServer::wl_pointer::axis axis, QtWaylandServer::wl_pointer::axis_relative_direction direction) +{ + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_axis_relative_direction(r->handle, axis, direction); +} + +void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y) +{ + Q_UNUSED(resource); + + if (!surface) { + m_cursorRole = nullptr; + } else { + auto *s = fromResource(surface); + QVERIFY(s); + if (s->m_role) { + m_cursorRole = CursorRole::fromSurface(s); + QVERIFY(m_cursorRole); + } else { + m_cursorRole = new CursorRole(s); //TODO: make sure we don't leak CursorRole + s->m_role = m_cursorRole; + } + } + + // Directly checking the last serial would be racy, we may just have sent leaves/enters which + // the client hasn't yet seen. Instead just check if the serial matches an enter serial since + // the last time the client sent a set_cursor request. + QVERIFY(m_enterSerials.contains(serial)); + while (m_enterSerials.first() < serial) { m_enterSerials.removeFirst(); } + + m_hotspot = QPoint(hotspot_x, hotspot_y); + emit setCursor(serial); +} + +uint Touch::sendDown(Surface *surface, const QPointF &position, int id) +{ + wl_fixed_t x = wl_fixed_from_double(position.x()); + wl_fixed_t y = wl_fixed_from_double(position.y()); + uint serial = m_seat->m_compositor->nextSerial(); + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + wl_client *client = surface->resource()->client(); + + const auto touchResources = resourceMap().values(client); + for (auto *r : touchResources) + wl_touch::send_down(r->handle, serial, time, surface->resource()->handle, id, x, y); + + return serial; +} + +uint Touch::sendUp(wl_client *client, int id) +{ + uint serial = m_seat->m_compositor->nextSerial(); + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + + const auto touchResources = resourceMap().values(client); + for (auto *r : touchResources) + wl_touch::send_up(r->handle, serial, time, id); + + return serial; +} + +void Touch::sendMotion(wl_client *client, const QPointF &position, int id) +{ + wl_fixed_t x = wl_fixed_from_double(position.x()); + wl_fixed_t y = wl_fixed_from_double(position.y()); + + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + + const auto touchResources = resourceMap().values(client); + for (auto *r : touchResources) + wl_touch::send_motion(r->handle, time, id, x, y); +} + +void Touch::sendFrame(wl_client *client) +{ + const auto touchResources = resourceMap().values(client); + for (auto *r : touchResources) + send_frame(r->handle); +} + +void Touch::sendCancel(wl_client *client) +{ + const auto touchResources = resourceMap().values(client); + for (auto *r : touchResources) + send_cancel(r->handle); +} + +uint Keyboard::sendEnter(Surface *surface) +{ + auto serial = m_seat->m_compositor->nextSerial(); + wl_client *client = surface->resource()->client(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_enter(r->handle, serial, surface->resource()->handle, QByteArray()); + m_enteredSurface = surface; + return serial; +} + +uint Keyboard::sendLeave(Surface *surface) +{ + auto serial = m_seat->m_compositor->nextSerial(); + wl_client *client = surface->resource()->client(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_leave(r->handle, serial, surface->resource()->handle); + m_enteredSurface = nullptr; + return serial; +} + +uint Keyboard::sendKey(wl_client *client, uint key, uint state) +{ + Q_ASSERT(state == key_state_pressed || state == key_state_released); + auto time = m_seat->m_compositor->currentTimeMilliseconds(); + uint serial = m_seat->m_compositor->nextSerial(); + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) { + send_key(r->handle, serial, time, key, state); + } + return serial; +} + +// Shm implementation +Shm::Shm(CoreCompositor *compositor, QList formats, int version) + : QtWaylandServer::wl_shm(compositor->m_display, version) + , m_compositor(compositor) + , m_formats(formats) +{ + // Some formats are specified as mandatory + Q_ASSERT(m_formats.contains(format_argb8888)); + Q_ASSERT(m_formats.contains(format_xrgb8888)); +} + +bool Shm::isClean() +{ +// for (ShmPool *pool : std::as_const(m_pools)) { +// //TODO: return false if not cursor buffer +// if (pool->m_buffers.isEmpty()) { +// return false; +// } +// } + return true; +} + +void Shm::shm_create_pool(Resource *resource, uint32_t id, int32_t fd, int32_t size) +{ + Q_UNUSED(fd); + Q_UNUSED(size); + auto *pool = new ShmPool(this, resource->client(), id, 1); + m_pools.append(pool); +} + +ShmPool::ShmPool(Shm *shm, wl_client *client, int id, int version) + : QtWaylandServer::wl_shm_pool(client, id, version) + , m_shm(shm) +{ +} + +void ShmPool::shm_pool_create_buffer(Resource *resource, uint32_t id, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format) +{ + QSize size(width, height); + new ShmBuffer(offset, size, stride, Shm::format(format), resource->client(), id); +} + +void ShmPool::shm_pool_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource); + bool removed = m_shm->m_pools.removeOne(this); + Q_ASSERT(removed); + delete this; +} + +WlShell::WlShell(CoreCompositor *compositor, int version) + : QtWaylandServer::wl_shell(compositor->m_display, version) + , m_compositor(compositor) +{ +} + +void WlShell::shell_get_shell_surface(Resource *resource, uint32_t id, wl_resource *surface) +{ + auto *s = fromResource(surface); + if (!s->m_role) { + s->m_role = new WlShellSurfaceRole; + } else if (!qobject_cast(s->m_role)) { + QFAIL(QByteArrayLiteral("surface already has role") + s->m_role->metaObject()->className()); + return; + } + + auto *wlShellSurface = new WlShellSurface(this, resource->client(), id, s); + m_wlShellSurfaces << wlShellSurface; + emit wlShellSurfaceCreated(wlShellSurface); +} + +WlShellSurface::WlShellSurface(WlShell *wlShell, wl_client *client, int id, Surface *surface) + : QtWaylandServer::wl_shell_surface(client, id, 1) + , m_wlShell(wlShell) + , m_surface(surface) +{ + surface->m_wlShellSurface = this; + surface->map(); +} + +WlShellSurface::~WlShellSurface() +{ + if (m_surface) { + m_surface->unmap(); + m_surface->m_wlShellSurface = nullptr; + } +} + +void WlShellSurface::sendConfigure(uint32_t edges, int32_t width, int32_t height) +{ + wl_shell_surface::send_configure(edges, width, height); +} + +} // namespace MockCompositor diff --git a/tests/auto/wayland/shared/coreprotocol.h b/tests/auto/wayland/shared/coreprotocol.h new file mode 100644 index 00000000000..127ff0964b2 --- /dev/null +++ b/tests/auto/wayland/shared/coreprotocol.h @@ -0,0 +1,489 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_COREPROTOCOL_H +#define MOCKCOMPOSITOR_COREPROTOCOL_H + +#include "corecompositor.h" + +#include + +#include + +namespace MockCompositor { + +class WlCompositor; +class WlShell; +class WlShellSurface; +class Output; +class Pointer; +class Touch; +class Keyboard; +class CursorRole; +class ShmPool; +class ShmBuffer; +class DataDevice; + +class Buffer : public QObject, public QtWaylandServer::wl_buffer +{ + Q_OBJECT +public: + explicit Buffer(wl_client *client, int id, int version) + : QtWaylandServer::wl_buffer(client, id, version) + { + } + virtual QSize size() const = 0; + bool m_destroyed = false; + +protected: + void buffer_destroy_resource(Resource *resource) override + { + Q_UNUSED(resource); + m_destroyed = true; + // The client side resource has been destroyed, but we keep this object because it may be + // be used as a reference by e.g. surface for the currently committed buffer so it's not + // yet safe to free it. + + //TODO: The memory should be freed by its factory + } + void buffer_destroy(Resource *resource) override { wl_resource_destroy(resource->handle); } +}; + +class Callback : public QObject, public QtWaylandServer::wl_callback +{ + Q_OBJECT +public: + explicit Callback(wl_client *client, int id, int version = 1) + : QtWaylandServer::wl_callback(client, id, version) + { + } + ~Callback() override { if (!m_destroyed) wl_resource_destroy(resource()->handle); } + void send_done(uint32_t data) = delete; // use state-tracking method below instead + void sendDone(uint data) { Q_ASSERT(!m_done); QtWaylandServer::wl_callback::send_done(data); m_done = true; } + void sendDoneAndDestroy(uint data) { sendDone(data); wl_resource_destroy(resource()->handle); } + bool m_done = false; + bool m_destroyed = false; +protected: + void callback_destroy_resource(Resource *resource) override { Q_UNUSED(resource); m_destroyed = true; } +}; + +class SurfaceRole : public QObject { + Q_OBJECT +}; + +class Surface : public QObject, public QtWaylandServer::wl_surface +{ + Q_OBJECT +public: + explicit Surface(WlCompositor *wlCompositor, wl_client *client, int id, int version); + ~Surface() override; + void sendFrameCallbacks(); + void sendEnter(Output *output); + void send_enter(::wl_resource *output) = delete; + void sendLeave(Output *output); + void send_leave(::wl_resource *output) = delete; + + void map(); + void unmap(); + bool isMapped() const { return m_mapped; } + WlShellSurface *wlShellSurface() const { return m_wlShellSurface; } + + WlCompositor *m_wlCompositor; + struct PerCommitData { + Callback *frame = nullptr; + QPoint attachOffset; + bool attached = false; + }; + struct DoubleBufferedState { + PerCommitData commitSpecific; + Buffer *buffer = nullptr; + uint configureSerial = 0; + int bufferScale = 1; + } m_pending, m_committed; + QList m_commits; + QList m_waitingFrameCallbacks; + QList m_outputs; + SurfaceRole *m_role = nullptr; + + WlShellSurface *m_wlShellSurface = nullptr; + bool m_mapped = false; + QList m_frameCallbackList; + + wl_resource *m_buffer = nullptr; + QImage m_image; // checking backingStore + bool m_wlshell = false; + +signals: + void attach(void *buffer, QPoint offset); + void commit(); + void bufferCommitted(); + +protected: + void surface_destroy_resource(Resource *resource) override; + void surface_destroy(Resource *resource) override; + void surface_attach(Resource *resource, wl_resource *buffer, int32_t x, int32_t y) override; + void surface_set_buffer_scale(Resource *resource, int32_t scale) override; + void surface_commit(Resource *resource) override; + void surface_frame(Resource *resource, uint32_t callback) override; + void surface_offset(Resource *resource, int32_t x, int32_t y) override; +}; + +class Region : public QtWaylandServer::wl_region +{ +public: + explicit Region(wl_client *client, int id, int version) + : QtWaylandServer::wl_region(client, id, version) + { + } + + void region_destroy_resource(Resource *resource) override + { + Q_UNUSED(resource); + delete this; + } +}; + +class WlCompositor : public Global, public QtWaylandServer::wl_compositor +{ + Q_OBJECT +public: + explicit WlCompositor(CoreCompositor *compositor, int version = 6) + : QtWaylandServer::wl_compositor(compositor->m_display, version) + , m_compositor(compositor) + {} + bool isClean() override; + QString dirtyMessage() override; + QList m_surfaces; + CoreCompositor *m_compositor = nullptr; + +signals: + void surfaceCreated(Surface *surface); + +protected: + void compositor_create_surface(Resource *resource, uint32_t id) override + { + auto *surface = new Surface(this, resource->client(), id, resource->version()); + m_surfaces.append(surface); + emit surfaceCreated(surface); + } + + void compositor_create_region(Resource *resource, uint32_t id) override + { + new Region(resource->client(), id, resource->version()); + } +}; + +class WlShell : public Global, public QtWaylandServer::wl_shell +{ + Q_OBJECT +public: + explicit WlShell(CoreCompositor *compositor, int version = 1); + QList m_wlShellSurfaces; + CoreCompositor *m_compositor = nullptr; + +signals: + void wlShellSurfaceCreated(WlShellSurface *wlShellSurface); + +protected: + void shell_get_shell_surface(Resource *resource, uint32_t id, ::wl_resource *surface) override; +}; + +class WlShellSurfaceRole : public SurfaceRole +{ + Q_OBJECT +}; + +class WlShellSurface : public QObject, public QtWaylandServer::wl_shell_surface +{ + Q_OBJECT +public: + explicit WlShellSurface(WlShell *wlShell, wl_client *client, int id, Surface *surface); + ~WlShellSurface() override; + void sendConfigure(uint32_t edges, int32_t width, int32_t height); + void send_configure(uint32_t edges, int32_t width, int32_t height) = delete; + + void shell_surface_destroy_resource(Resource *) override { delete this; } + + WlShell *m_wlShell = nullptr; + Surface *m_surface = nullptr; +}; + +class Subsurface; + +class SubCompositor : public Global, public QtWaylandServer::wl_subcompositor +{ + Q_OBJECT +public: + explicit SubCompositor(CoreCompositor *compositor, int version = 1) + : QtWaylandServer::wl_subcompositor(compositor->m_display, version) + {} + QList m_subsurfaces; + +signals: + void subsurfaceCreated(Subsurface *subsurface); + +protected: + void subcompositor_get_subsurface(Resource *resource, uint32_t id, + ::wl_resource *surfaceResource, + ::wl_resource *parent) override; +}; + +class SubSurfaceRole : public SurfaceRole +{ + Q_OBJECT +}; + +class Subsurface : public QObject, public QtWaylandServer::wl_subsurface +{ + Q_OBJECT +public: + explicit Subsurface(SubCompositor *subCompositor, wl_client *client, int id, int version) + : QtWaylandServer::wl_subsurface(client, id, version), m_subcompositor(subCompositor) + { + } + ~Subsurface() + { + m_subcompositor->m_subsurfaces.removeOne(this); + qDebug() << m_subcompositor->m_subsurfaces; + } + void subsurface_destroy(Resource *resource) override { wl_resource_destroy(resource->handle); } + void subsurface_destroy_resource(Resource *resource) override + { + Q_UNUSED(resource) + delete this; + } + +private: + SubCompositor *m_subcompositor; +}; + +struct OutputMode { + explicit OutputMode() = default; + explicit OutputMode(const QSize &resolution, int refreshRate = 60000) + : resolution(resolution), refreshRate(refreshRate) + {} + QSize resolution = QSize(1920, 1080); + int refreshRate = 60000; // In mHz + //TODO: flags (they're currently hard-coded) + + // in mm + QSize physicalSizeForDpi(int dpi) { return (QSizeF(resolution) * 25.4 / dpi).toSize(); } +}; + +struct OutputData { + using Subpixel = QtWaylandServer::wl_output::subpixel; + using Transform = QtWaylandServer::wl_output::transform; + explicit OutputData() = default; + + // for geometry event + QPoint position; + QSize physicalSize = QSize(0, 0); // means unknown physical size + QString make = "Make"; + QString model = "Model"; + Subpixel subpixel = Subpixel::subpixel_unknown; + Transform transform = Transform::transform_normal; + + int scale = 1; // for scale event + OutputMode mode; // for mode event +}; + +class Output : public Global, public QtWaylandServer::wl_output +{ + Q_OBJECT +public: + explicit Output(CoreCompositor *compositor, OutputData data = OutputData(), int version = 2) + : QtWaylandServer::wl_output(compositor->m_display, version) + , m_data(std::move(data)) + , m_version(version) + {} + + void send_geometry() = delete; + void sendGeometry(); + void sendGeometry(Resource *resource); // Sends to only one client + + void send_scale(int32_t factor) = delete; + void sendScale(int factor); + void sendScale(Resource *resource); // Sends current scale to only one client + + void sendDone(wl_client *client); + void sendDone(); + + int scale() const { return m_data.scale; } + + OutputData m_data; + int m_version = 1; // TODO: remove on libwayland upgrade + +Q_SIGNALS: + void outputBound(Resource *resource); + +protected: + void output_bind_resource(Resource *resource) override; +}; + +class Seat : public Global, public QtWaylandServer::wl_seat +{ + Q_OBJECT +public: + explicit Seat(CoreCompositor *compositor, uint capabilities = Seat::capability_pointer | Seat::capability_keyboard | Seat::capability_touch, int version = 9, const QString &seatName = QLatin1String("seat0")); + ~Seat() override; + void send_capabilities(Resource *resource, uint capabilities) = delete; // Use wrapper instead + void send_capabilities(uint capabilities) = delete; // Use wrapper instead + void setCapabilities(uint capabilities); + + + QString m_seatName; + CoreCompositor *m_compositor = nullptr; + + Pointer* m_pointer = nullptr; + QList m_oldPointers; + + Touch* m_touch = nullptr; + QList m_oldTouchs; + + Keyboard* m_keyboard = nullptr; + QList m_oldKeyboards; + + uint m_capabilities = 0; + +protected: + void seat_bind_resource(Resource *resource) override + { + wl_seat::send_capabilities(resource->handle, m_capabilities); + wl_seat::send_name(resource->handle, m_seatName); // in any normal world this is would be set before capabilities. Weston does it after + } + + void seat_get_pointer(Resource *resource, uint32_t id) override; + void seat_get_touch(Resource *resource, uint32_t id) override; + void seat_get_keyboard(Resource *resource, uint32_t id) override; + +// void seat_release(Resource *resource) override; +}; + +class Pointer : public QObject, public QtWaylandServer::wl_pointer +{ + Q_OBJECT +public: + explicit Pointer(Seat *seat) : m_seat(seat) {} + Surface *cursorSurface(); + QPointer m_cursorRole; + void send_enter() = delete; + uint sendEnter(Surface *surface, const QPointF &position); + void send_leave() = delete; + uint sendLeave(Surface *surface); + void sendMotion(wl_client *client, const QPointF &position); + uint sendButton(wl_client *client, uint button, uint state); + void sendAxis(wl_client *client, axis axis, qreal value); + void sendAxisDiscrete(wl_client *client, axis axis, int discrete); + void sendAxisSource(wl_client *client, axis_source source); + void sendAxisStop(wl_client *client, axis axis); + void sendFrame(wl_client *client); + void sendAxisValue120(wl_client *client, axis axis, int value120); + void sendAxisRelativeDirection(wl_client *client, axis axis, axis_relative_direction direction); + + Seat *m_seat = nullptr; + QList m_enterSerials; + QPoint m_hotspot; + +signals: + void setCursor(uint serial); //TODO: add arguments? + +protected: + void pointer_set_cursor(Resource *resource, uint32_t serial, ::wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y) override; + //TODO +}; + +class CursorRole : public SurfaceRole { + Q_OBJECT +public: + explicit CursorRole(Surface *surface) // TODO: needs some more args + : m_surface(surface) + { + connect(m_surface, &QObject::destroyed, this, &QObject::deleteLater); + } + static CursorRole *fromSurface(Surface *surface) { return qobject_cast(surface->m_role); } + Surface *m_surface = nullptr; +}; + +class Touch : public QObject, public QtWaylandServer::wl_touch +{ + Q_OBJECT +public: + explicit Touch(Seat *seat) : m_seat(seat) {} + uint sendDown(Surface *surface, const QPointF &position, int id); + uint sendUp(wl_client *client, int id); + void sendMotion(wl_client *client, const QPointF &position, int id); + void sendFrame(wl_client *client); + void sendCancel(wl_client *client); + + Seat *m_seat = nullptr; +}; + +class Keyboard : public QObject, public QtWaylandServer::wl_keyboard +{ + Q_OBJECT +public: + explicit Keyboard(Seat *seat) : m_seat(seat) {} + //TODO: Keymap event + uint sendEnter(Surface *surface); + uint sendLeave(Surface *surface); + uint sendKey(wl_client *client, uint key, uint state); + Seat *m_seat = nullptr; + Surface *m_enteredSurface = nullptr; +}; + +class Shm : public Global, public QtWaylandServer::wl_shm +{ + Q_OBJECT +public: + explicit Shm(CoreCompositor *compositor, QList formats = {format_argb8888, format_xrgb8888, format_rgb888}, int version = 1); + bool isClean() override; + CoreCompositor *m_compositor = nullptr; + QList m_pools; + const QList m_formats; + +protected: + void shm_create_pool(Resource *resource, uint32_t id, int32_t fd, int32_t size) override; + void shm_bind_resource(Resource *resource) override + { + for (auto format : std::as_const(m_formats)) + send_format(resource->handle, format); + } +}; + +class ShmPool : QObject, public QtWaylandServer::wl_shm_pool +{ + Q_OBJECT +public: + explicit ShmPool(Shm *shm, wl_client *client, int id, int version = 1); + Shm *m_shm = nullptr; + QList m_buffers; + +protected: + void shm_pool_create_buffer(Resource *resource, uint32_t id, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format) override; + void shm_pool_destroy_resource(Resource *resource) override; + void shm_pool_destroy(Resource *resource) override { wl_resource_destroy(resource->handle); } +}; + +class ShmBuffer : public Buffer +{ + Q_OBJECT +public: + static ShmBuffer *fromBuffer(Buffer *buffer) { return qobject_cast(buffer); } + explicit ShmBuffer(int offset, const QSize &size, int stride, Shm::format format, wl_client *client, int id, int version = 1) + : Buffer(client, id, version) + , m_offset(offset) + , m_size(size) + , m_stride(stride) + , m_format(format) + { + } + QSize size() const override { return m_size; } + const int m_offset; + const QSize m_size; + const int m_stride; + const Shm::format m_format; +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_COREPROTOCOL_H diff --git a/tests/auto/wayland/shared/datadevice.cpp b/tests/auto/wayland/shared/datadevice.cpp new file mode 100644 index 00000000000..efb88d0b1f8 --- /dev/null +++ b/tests/auto/wayland/shared/datadevice.cpp @@ -0,0 +1,115 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "datadevice.h" + +namespace MockCompositor { + +bool DataDeviceManager::isClean() +{ + for (auto *device : std::as_const(m_dataDevices)) { + // The client should not leak selection offers, i.e. if this fails, there is a missing + // data_offer.destroy request + if (!device->m_sentSelectionOffers.empty()) + return false; + } + return true; +} + +DataDevice *DataDeviceManager::deviceFor(Seat *seat) +{ + Q_ASSERT(seat); + if (auto *device = m_dataDevices.value(seat, nullptr)) + return device; + + auto *device = new DataDevice(this, seat); + m_dataDevices[seat] = device; + return device; +} + +void DataDeviceManager::data_device_manager_get_data_device(Resource *resource, uint32_t id, wl_resource *seatResource) +{ + auto *seat = fromResource(seatResource); + QVERIFY(seat); + auto *device = deviceFor(seat); + device->add(resource->client(), id, resource->version()); +} + +void DataDeviceManager::data_device_manager_create_data_source(Resource *resource, uint32_t id) +{ + new QtWaylandServer::wl_data_source(resource->client(), id, 1); +} + +DataDevice::~DataDevice() +{ + // If the client(s) hasn't deleted the wayland object, just ignore subsequent events + for (auto *r : resourceMap()) + wl_resource_set_implementation(r->handle, nullptr, nullptr, nullptr); +} + +DataOffer *DataDevice::sendDataOffer(wl_client *client, const QStringList &mimeTypes) +{ + Q_ASSERT(client); + auto *offer = new DataOffer(this, client, m_manager->m_version); + m_offer = offer; + for (auto *resource : resourceMap().values(client)) + wl_data_device::send_data_offer(resource->handle, offer->resource()->handle); + for (const auto &mimeType : mimeTypes) + offer->send_offer(mimeType); + return offer; +} + +void DataDevice::sendSelection(DataOffer *offer) +{ + auto *client = offer->resource()->client(); + for (auto *resource : resourceMap().values(client)) + wl_data_device::send_selection(resource->handle, offer->resource()->handle); + m_sentSelectionOffers << offer; +} + +void DataDevice::sendEnter(Surface *surface, const QPoint &position) +{ + uint serial = m_manager->m_compositor->nextSerial(); + Resource *resource = resourceMap().value(surface->resource()->client()); + if (m_offer) + wl_data_device::send_enter(resource->handle, serial, surface->resource()->handle, position.x(), position.y(), m_offer->resource()->handle); +} + +void DataDevice::sendMotion(Surface *surface, const QPoint &position) +{ + uint32_t time = m_manager->m_compositor->nextSerial(); + Resource *resource = resourceMap().value(surface->resource()->client()); + wl_data_device::send_motion(resource->handle, time, position.x(), position.y()); +} + +void DataDevice::sendDrop(Surface *surface) +{ + Resource *resource = resourceMap().value(surface->resource()->client()); + wl_data_device::send_drop(resource->handle); +} + +void DataDevice::sendLeave(Surface *surface) +{ + Resource *resource = resourceMap().value(surface->resource()->client()); + wl_data_device::send_leave(resource->handle); +} + +void DataOffer::data_offer_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource); + delete this; +} + +void DataOffer::data_offer_receive(Resource *resource, const QString &mime_type, int32_t fd) +{ + Q_UNUSED(resource); + emit receive(mime_type, fd); +} + +void DataOffer::data_offer_destroy(QtWaylandServer::wl_data_offer::Resource *resource) +{ + m_dataDevice->m_sentSelectionOffers.removeOne(this); + wl_resource_destroy(resource->handle); +} + +} // namespace MockCompositor diff --git a/tests/auto/wayland/shared/datadevice.h b/tests/auto/wayland/shared/datadevice.h new file mode 100644 index 00000000000..356dfa74a3a --- /dev/null +++ b/tests/auto/wayland/shared/datadevice.h @@ -0,0 +1,113 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_DATADEVICE_H +#define MOCKCOMPOSITOR_DATADEVICE_H + +//TODO: need this? +#include "coreprotocol.h" + +namespace MockCompositor { + +class DataOffer; + +class DataDeviceManager : public Global, public QtWaylandServer::wl_data_device_manager +{ + Q_OBJECT +public: + explicit DataDeviceManager(CoreCompositor *compositor, int version = 1) + : QtWaylandServer::wl_data_device_manager(compositor->m_display, version) + , m_version(version) + , m_compositor(compositor) + {} + ~DataDeviceManager() override { qDeleteAll(m_dataDevices); } + bool isClean() override; + DataDevice *deviceFor(Seat *seat); + + int m_version = 1; // TODO: remove on libwayland upgrade + QMap m_dataDevices; + CoreCompositor *m_compositor; + +protected: + void data_device_manager_get_data_device(Resource *resource, uint32_t id, ::wl_resource *seatResource) override; + void data_device_manager_create_data_source(Resource *resource, uint32_t id) override; +}; + +class DataDevice : public QObject, public QtWaylandServer::wl_data_device +{ + Q_OBJECT +public: + explicit DataDevice(DataDeviceManager *manager, Seat *seat) + : m_manager(manager) + , m_seat(seat) + {} + ~DataDevice() override; + void send_data_offer(::wl_resource *resource) = delete; + DataOffer *sendDataOffer(::wl_client *client, const QStringList &mimeTypes = {}); + + void send_selection(::wl_resource *resource) = delete; + void sendSelection(DataOffer *offer); + + void send_enter(uint32_t serial, ::wl_resource *surface, wl_fixed_t x, wl_fixed_t y, ::wl_resource *id) = delete; + void sendEnter(Surface *surface, const QPoint& position); + + void send_motion(uint32_t time, wl_fixed_t x, wl_fixed_t y) = delete; + void sendMotion(Surface *surface, const QPoint &position); + + void send_drop(::wl_resource *resource) = delete; + void sendDrop(Surface *surface); + + void send_leave(::wl_resource *resource) = delete; + void sendLeave(Surface *surface); + + DataDeviceManager *m_manager = nullptr; + Seat *m_seat = nullptr; + QList m_sentSelectionOffers; + QPointer m_offer; + +signals: + void dragStarted(); + +protected: + void data_device_start_drag(Resource *resource, ::wl_resource *source, ::wl_resource *origin, ::wl_resource *icon, uint32_t serial) override + { + Q_UNUSED(resource); + Q_UNUSED(source); + Q_UNUSED(origin); + Q_UNUSED(icon); + Q_UNUSED(serial); + emit dragStarted(); + } + + void data_device_release(Resource *resource) override + { + int removed = m_manager->m_dataDevices.remove(m_seat); + QVERIFY(removed); + wl_resource_destroy(resource->handle); + } +}; + +class DataOffer : public QObject, public QtWaylandServer::wl_data_offer +{ + Q_OBJECT +public: + explicit DataOffer(DataDevice *dataDevice, ::wl_client *client, int version) + : QtWaylandServer::wl_data_offer (client, 0, version) + , m_dataDevice(dataDevice) + {} + + DataDevice *m_dataDevice = nullptr; + +signals: + void receive(QString mimeType, int fd); + +protected: + void data_offer_destroy_resource(Resource *resource) override; + void data_offer_receive(Resource *resource, const QString &mime_type, int32_t fd) override; +// void data_offer_accept(Resource *resource, uint32_t serial, const QString &mime_type) override; + void data_offer_destroy(Resource *resource) override; +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_DATADEVICE_H diff --git a/tests/auto/wayland/shared/fractionalscalev1.cpp b/tests/auto/wayland/shared/fractionalscalev1.cpp new file mode 100644 index 00000000000..e2ad29897cc --- /dev/null +++ b/tests/auto/wayland/shared/fractionalscalev1.cpp @@ -0,0 +1,40 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "fractionalscalev1.h" + +namespace MockCompositor { + +FractionalScaleManager::FractionalScaleManager(CoreCompositor *compositor, int version) + : QtWaylandServer::wp_fractional_scale_manager_v1(compositor->m_display, version) +{ +} + +void FractionalScaleManager::wp_fractional_scale_manager_v1_get_fractional_scale(Resource *resource, uint32_t id, wl_resource *surface) +{ + auto *s = fromResource(surface); + auto *scaler = new FractionalScale(s, resource->client(), id, resource->version()); + connect(scaler, &QObject::destroyed, this, [this, scaler]() { + m_fractionalScales.removeOne(scaler); + }, Qt::DirectConnection); + m_fractionalScales << scaler; +} + +FractionalScale::FractionalScale(Surface *surface, wl_client *client, int id, int version) + : QtWaylandServer::wp_fractional_scale_v1(client, id, version) + , m_surface(surface) +{ +} + +void FractionalScale::wp_fractional_scale_v1_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + delete this; +} + +void FractionalScale::wp_fractional_scale_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +} diff --git a/tests/auto/wayland/shared/fractionalscalev1.h b/tests/auto/wayland/shared/fractionalscalev1.h new file mode 100644 index 00000000000..1ae2fad1f1f --- /dev/null +++ b/tests/auto/wayland/shared/fractionalscalev1.h @@ -0,0 +1,39 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_FRACTIONALSCALE_H +#define MOCKCOMPOSITOR_FRACTIONALSCALE_H + +#include "coreprotocol.h" +#include + +namespace MockCompositor { + +class FractionalScale; + +class FractionalScaleManager : public Global, public QtWaylandServer::wp_fractional_scale_manager_v1 +{ + Q_OBJECT +public: + explicit FractionalScaleManager(CoreCompositor *compositor, int version = 1); + QList m_fractionalScales; + +protected: + void wp_fractional_scale_manager_v1_get_fractional_scale(Resource *resource, uint32_t id, wl_resource *surface) override; +}; + +class FractionalScale : public QObject, public QtWaylandServer::wp_fractional_scale_v1 +{ + Q_OBJECT +public: + explicit FractionalScale(Surface *surface, wl_client *client, int id, int version); + Surface *m_surface; + +protected: + void wp_fractional_scale_v1_destroy_resource(Resource *resource) override; + void wp_fractional_scale_v1_destroy(Resource *resource) override; +}; + +} + +#endif diff --git a/tests/auto/wayland/shared/fullscreenshellv1.cpp b/tests/auto/wayland/shared/fullscreenshellv1.cpp new file mode 100644 index 00000000000..24468e14bf4 --- /dev/null +++ b/tests/auto/wayland/shared/fullscreenshellv1.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2021 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "fullscreenshellv1.h" + +namespace MockCompositor { + +FullScreenShellV1::FullScreenShellV1(CoreCompositor *compositor) +{ + init(compositor->m_display, 1); +} + +void FullScreenShellV1::zwp_fullscreen_shell_v1_present_surface(Resource *resource, struct ::wl_resource *surface, uint32_t method, struct ::wl_resource *output) +{ + Q_UNUSED(resource); + Q_UNUSED(method); + Q_UNUSED(output); + + m_surfaces.append(fromResource(surface)); +} + +} // namespace MockCompositor diff --git a/tests/auto/wayland/shared/fullscreenshellv1.h b/tests/auto/wayland/shared/fullscreenshellv1.h new file mode 100644 index 00000000000..02ebf1e0cb8 --- /dev/null +++ b/tests/auto/wayland/shared/fullscreenshellv1.h @@ -0,0 +1,34 @@ +// Copyright (C) 2021 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_FULLSCREENSHELLV1_H +#define MOCKCOMPOSITOR_FULLSCREENSHELLV1_H + +#include "coreprotocol.h" +#include + +#include + +namespace MockCompositor { + +class Surface; +class FullScreenShellV1; + +class FullScreenShellV1 : public Global, public QtWaylandServer::zwp_fullscreen_shell_v1 +{ + Q_OBJECT +public: + explicit FullScreenShellV1(CoreCompositor *compositor); + + QList surfaces() const { return m_surfaces; } + +protected: + void zwp_fullscreen_shell_v1_present_surface(Resource *resource, struct ::wl_resource *surface, uint32_t method, struct ::wl_resource *output) override; + +private: + QList m_surfaces; +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_FULLSCREENSHELLV1_H diff --git a/tests/auto/wayland/shared/mockcompositor.cpp b/tests/auto/wayland/shared/mockcompositor.cpp new file mode 100644 index 00000000000..d34d4c7023b --- /dev/null +++ b/tests/auto/wayland/shared/mockcompositor.cpp @@ -0,0 +1,143 @@ +// Copyright (C) 2021 David Edmundson +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +namespace MockCompositor { + +DefaultCompositor::DefaultCompositor(CompositorType t, int socketFd) + : CoreCompositor(t, socketFd) +{ + { + Lock l(this); + + // Globals: Should ideally always be at least the latest versions we support. + // Legacy versions can override in separate tests by removing and adding. + add(); + add(); + auto *output = add(); + output->m_data.physicalSize = output->m_data.mode.physicalSizeForDpi(96); + add(Seat::capability_pointer | Seat::capability_keyboard | Seat::capability_touch); + add(); + add(); + add(); + add(); + add(); + + switch (m_type) { + case CompositorType::Default: + add(); + break; + case CompositorType::Legacy: + wl_display_init_shm(m_display); + break; + } + add(); + //add(); + + // TODO: other shells, viewporter, xdgoutput etc + + QObject::connect(get(), &WlCompositor::surfaceCreated, [this] (Surface *surface){ + QObject::connect(surface, &Surface::bufferCommitted, [this, surface] { + if (m_config.autoRelease && surface->m_committed.buffer) { + // Pretend we made a copy of the buffer and just release it immediately + surface->m_committed.buffer->send_release(); + } + if (m_config.autoFrameCallback) { + surface->sendFrameCallbacks(); + } + if (m_config.autoEnter && get() && surface->m_outputs.empty()) + surface->sendEnter(get()); + wl_display_flush_clients(m_display); + }); + }); + + QObject::connect(get(), &XdgWmBase::toplevelCreated, get(), [this] (XdgToplevel *toplevel) { + if (m_config.autoConfigure) + toplevel->sendCompleteConfigure(); + }, Qt::DirectConnection); + } + Q_ASSERT(isClean()); +} + +Surface *DefaultCompositor::surface(int i) +{ + QList surfaces; + switch (m_type) { + case CompositorType::Default: + return get()->m_surfaces.value(i, nullptr); + case CompositorType::Legacy: { + QList msurfaces = get()->m_surfaces; + for (Surface *surface : msurfaces) { + if (surface->isMapped()) { + surfaces << surface; + } + } + } + break; + } + + if (i >= 0 && i < surfaces.size()) + return surfaces[i]; + + return nullptr; +} + +uint DefaultCompositor::sendXdgShellPing() +{ + warnIfNotLockedByThread(Q_FUNC_INFO); + uint serial = nextSerial(); + auto *base = get(); + const auto resourceMap = base->resourceMap(); + Q_ASSERT(resourceMap.size() == 1); // binding more than once shouldn't be needed + base->send_ping(resourceMap.first()->handle, serial); + return serial; +} + +void DefaultCompositor::xdgPingAndWaitForPong() +{ + QSignalSpy pongSpy(exec([&] { return get(); }), &XdgWmBase::pong); + uint serial = exec([&] { return sendXdgShellPing(); }); + QTRY_COMPARE(pongSpy.size(), 1); + QTRY_COMPARE(pongSpy.first().at(0).toUInt(), serial); +} + +void DefaultCompositor::sendShellSurfaceConfigure(Surface *surface) +{ + switch (m_type) { + case CompositorType::Default: + break; + case CompositorType::Legacy: { + if (auto wlShellSurface = surface->wlShellSurface()) { + wlShellSurface->sendConfigure(0, 0, 0); + return; + } + break; + } + } + + qWarning() << "The mocking framework doesn't know how to send a configure event for this surface"; +} + +WlShellCompositor::WlShellCompositor(CompositorType t) + : DefaultCompositor(t) +{ +} + +Surface *DefaultCompositor::wlSurface(int i) +{ + QList surfaces, msurfaces; + msurfaces = get()->m_surfaces; + for (Surface *surface : msurfaces) { + if (surface->isMapped()) + surfaces << surface; + } + + if (i >=0 && i < surfaces.size()) + return surfaces[i]; + + return nullptr; +} + +} // namespace MockCompositor diff --git a/tests/auto/wayland/shared/mockcompositor.h b/tests/auto/wayland/shared/mockcompositor.h new file mode 100644 index 00000000000..0e90982614c --- /dev/null +++ b/tests/auto/wayland/shared/mockcompositor.h @@ -0,0 +1,97 @@ +// Copyright (C) 2021 David Edmundson +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_H +#define MOCKCOMPOSITOR_H + +#include "corecompositor.h" +#include "coreprotocol.h" +#include "datadevice.h" +#include "fullscreenshellv1.h" +//#include "iviapplication.h" +#include "xdgshell.h" +#include "viewport.h" +#include "fractionalscalev1.h" +#include "xdgdialog.h" + +#include + +// As defined in linux/input-event-codes.h +#ifndef BTN_LEFT +#define BTN_LEFT 0x110 +#endif +#ifndef BTN_RIGHT +#define BTN_RIGHT 0x111 +#endif +#ifndef BTN_MIDDLE +#define BTN_MIDDLE 0x112 +#endif + +namespace MockCompositor { + +class DefaultCompositor : public CoreCompositor +{ +public: + explicit DefaultCompositor(CompositorType t = CompositorType::Default, int socketFd = -1); + // Convenience functions + Output *output(int i = 0) { return getAll().value(i, nullptr); } + Surface *surface(int i = 0); + Subsurface *subSurface(int i = 0) { return get()->m_subsurfaces.value(i, nullptr); } + WlShellSurface *wlShellSurface(int i = 0) { return get()->m_wlShellSurfaces.value(i, nullptr); } + Surface *wlSurface(int i = 0); + XdgSurface *xdgSurface(int i = 0) { return get()->m_xdgSurfaces.value(i, nullptr); } + XdgToplevel *xdgToplevel(int i = 0) { return get()->toplevel(i); } + XdgPopup *xdgPopup(int i = 0) { return get()->popup(i); } + Pointer *pointer() { auto *seat = get(); Q_ASSERT(seat); return seat->m_pointer; } + Touch *touch() { auto *seat = get(); Q_ASSERT(seat); return seat->m_touch; } + Surface *cursorSurface() { auto *p = pointer(); return p ? p->cursorSurface() : nullptr; } + Keyboard *keyboard() { auto *seat = get(); Q_ASSERT(seat); return seat->m_keyboard; } + FullScreenShellV1 *fullScreenShellV1() {return get();}; + //IviSurface *iviSurface(int i = 0) { return get()->m_iviSurfaces.value(i, nullptr); } + FractionalScale *fractionalScale(int i = 0) {return get()->m_fractionalScales.value(i, nullptr); } + Viewport *viewport(int i = 0) {return get()->m_viewports.value(i, nullptr); } + XdgDialog *xdgDialog(int i = 0) { return get()->m_dialogs.value(i, nullptr); } + + uint sendXdgShellPing(); + void xdgPingAndWaitForPong(); + + void sendShellSurfaceConfigure(Surface *surface); + + // Things that can be changed run-time without confusing the client (i.e. don't require separate tests) + struct Config { + bool autoEnter = true; + bool autoRelease = true; + bool autoConfigure = false; + bool autoFrameCallback = true; + } m_config; + void resetConfig() { exec([&] { m_config = Config{}; }); } +}; + +class WlShellCompositor : public DefaultCompositor +{ +public: + explicit WlShellCompositor(CompositorType t = CompositorType::Legacy); +}; + +} // namespace MockCompositor + +#define QCOMPOSITOR_VERIFY(expr) QVERIFY(exec([&]{ return expr; })) +#define QCOMPOSITOR_TRY_VERIFY(expr) QTRY_VERIFY(exec([&]{ return expr; })) +#define QCOMPOSITOR_COMPARE(expr, expr2) QCOMPARE(exec([&]{ return expr; }), expr2) +#define QCOMPOSITOR_TRY_COMPARE(expr, expr2) QTRY_COMPARE(exec([&]{ return expr; }), expr2) + +#define QCOMPOSITOR_TEST_MAIN(test) \ +int main(int argc, char **argv) \ +{ \ + QTemporaryDir tmpRuntimeDir; \ + setenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit(), 1); \ + setenv("XDG_CURRENT_DESKTOP", "qtwaylandtests", 1); \ + setenv("QT_QPA_PLATFORM", "wayland", 1); \ + test tc; \ + QGuiApplication app(argc, argv); \ + QTEST_SET_MAIN_SOURCE_PATH \ + return QTest::qExec(&tc, argc, argv); \ +} \ + +#endif diff --git a/tests/auto/wayland/shared/qttextinput.cpp b/tests/auto/wayland/shared/qttextinput.cpp new file mode 100644 index 00000000000..1fb5ef1c4e0 --- /dev/null +++ b/tests/auto/wayland/shared/qttextinput.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "qttextinput.h" + +namespace MockCompositor { + +QtTextInputManager::QtTextInputManager(CoreCompositor *compositor) +{ + init(compositor->m_display, 1); +} + +void QtTextInputManager::text_input_method_manager_v1_get_text_input_method(Resource *resource, uint32_t id, wl_resource *seatResource) +{ + Q_UNUSED(resource); + Q_UNUSED(id); + Q_UNUSED(seatResource); +} + +} // namespace MockCompositor diff --git a/tests/auto/wayland/shared/qttextinput.h b/tests/auto/wayland/shared/qttextinput.h new file mode 100644 index 00000000000..047cec7d395 --- /dev/null +++ b/tests/auto/wayland/shared/qttextinput.h @@ -0,0 +1,26 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_QTTEXTINPUT_H +#define MOCKCOMPOSITOR_QTTEXTINPUT_H + +#include "coreprotocol.h" +#include + +#include + +namespace MockCompositor { + +class QtTextInputManager : public Global, public QtWaylandServer::qt_text_input_method_manager_v1 +{ + Q_OBJECT +public: + QtTextInputManager(CoreCompositor *compositor); + +protected: + void text_input_method_manager_v1_get_text_input_method(Resource *resource, uint32_t id, struct ::wl_resource *seatResource) override; +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_QTTEXTINPUT_H diff --git a/tests/auto/wayland/shared/textinput.cpp b/tests/auto/wayland/shared/textinput.cpp new file mode 100644 index 00000000000..fc4865d710f --- /dev/null +++ b/tests/auto/wayland/shared/textinput.cpp @@ -0,0 +1,19 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "textinput.h" + +namespace MockCompositor { + +TextInputManager::TextInputManager(CoreCompositor *compositor) +{ + init(compositor->m_display, 1); +} + +void TextInputManager::zwp_text_input_manager_v2_get_text_input(Resource *resource, uint32_t id, wl_resource *seatResource) +{ + Q_UNUSED(seatResource); + add(resource->client(), id, resource->version()); +} + +} // namespace MockCompositor diff --git a/tests/auto/wayland/shared/textinput.h b/tests/auto/wayland/shared/textinput.h new file mode 100644 index 00000000000..ca20ddbad63 --- /dev/null +++ b/tests/auto/wayland/shared/textinput.h @@ -0,0 +1,26 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_TEXTINPUT_H +#define MOCKCOMPOSITOR_TEXTINPUT_H + +#include "coreprotocol.h" +#include + +#include + +namespace MockCompositor { + +class TextInputManager : public Global, public QtWaylandServer::zwp_text_input_manager_v2 +{ + Q_OBJECT +public: + TextInputManager(CoreCompositor *compositor); + +protected: + void zwp_text_input_manager_v2_get_text_input(Resource *resource, uint32_t id, struct ::wl_resource *seatResource) override; +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_TEXTINPUT_H diff --git a/tests/auto/wayland/shared/viewport.cpp b/tests/auto/wayland/shared/viewport.cpp new file mode 100644 index 00000000000..da00a5b6a0e --- /dev/null +++ b/tests/auto/wayland/shared/viewport.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "viewport.h" + +namespace MockCompositor { + +Viewporter::Viewporter(CoreCompositor *compositor, int version) + : QtWaylandServer::wp_viewporter(compositor->m_display, version) +{ +} + +void Viewporter::wp_viewporter_get_viewport(Resource *resource, uint32_t id, wl_resource *surface) +{ + auto *s = fromResource(surface); + auto *viewport = new Viewport(s, resource->client(), id, resource->version()); + connect(viewport, &QObject::destroyed, this, [this, viewport]() { + m_viewports.removeOne(viewport); + }, Qt::DirectConnection); + m_viewports << viewport; +} + +Viewport::Viewport(Surface *surface, wl_client *client, int id, int version) + : QtWaylandServer::wp_viewport(client, id, version) + , m_surface(surface) +{ +} + +void Viewport::wp_viewport_set_source(Resource *resource, wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) +{ + Q_UNUSED(resource) + m_source = QRectF(wl_fixed_to_double(x), + wl_fixed_to_double(y), + wl_fixed_to_double(width), + wl_fixed_to_double(height)); + Q_EMIT sourceChanged(); +} + +void Viewport::wp_viewport_set_destination(Resource *resource, int32_t width, int32_t height) +{ + Q_UNUSED(resource) + + m_destination = QSize(width, height); + Q_EMIT destinationChanged(); +} + +void Viewport::wp_viewport_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + delete this; +} + +void Viewport::wp_viewport_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +} diff --git a/tests/auto/wayland/shared/viewport.h b/tests/auto/wayland/shared/viewport.h new file mode 100644 index 00000000000..ddc4297db98 --- /dev/null +++ b/tests/auto/wayland/shared/viewport.h @@ -0,0 +1,50 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_VIEWPORT_H +#define MOCKCOMPOSITOR_VIEWPORT_H + +#include "coreprotocol.h" +#include + +namespace MockCompositor { + +class Viewport; + +class Viewporter : public Global, public QtWaylandServer::wp_viewporter +{ + Q_OBJECT +public: + explicit Viewporter(CoreCompositor *compositor, int version = 1); + QList m_viewports; + +protected: + void wp_viewporter_get_viewport(Resource *resource, uint32_t id, wl_resource *surface) override; +}; + +class Viewport : public QObject, public QtWaylandServer::wp_viewport +{ + Q_OBJECT +public: + explicit Viewport(Surface *surface, wl_client *client, int id, int version); + + QRectF m_source; + QSize m_destination; + + Surface* m_surface; + +Q_SIGNALS: + void sourceChanged(); + void destinationChanged(); + +protected: + void wp_viewport_set_source(Resource *resource, wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) override; + void wp_viewport_set_destination(Resource *resource, int32_t width, int32_t height) override; + + void wp_viewport_destroy_resource(Resource *resource) override; + void wp_viewport_destroy(Resource *resource) override; +}; + +} + +#endif diff --git a/tests/auto/wayland/shared/xdgdialog.cpp b/tests/auto/wayland/shared/xdgdialog.cpp new file mode 100644 index 00000000000..b973415bd56 --- /dev/null +++ b/tests/auto/wayland/shared/xdgdialog.cpp @@ -0,0 +1,59 @@ +// Copyright (C) 2024 David Redondo +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include "xdgdialog.h" + +#include "xdgshell.h" + +namespace MockCompositor { + +XdgDialog::XdgDialog(XdgWmDialog *wm, XdgToplevel *toplevel, wl_client *client, int id, int version) + : QtWaylandServer::xdg_dialog_v1(client, id, version), + toplevel(toplevel), + modal(false), + m_wm(wm) +{ +} + +void XdgDialog::xdg_dialog_v1_set_modal(Resource *resource) +{ + Q_UNUSED(resource) + modal = true; +} + +void XdgDialog::xdg_dialog_v1_unset_modal(Resource *resource) +{ + Q_UNUSED(resource) + modal = false; +} + +void XdgDialog::xdg_dialog_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void XdgDialog::xdg_dialog_v1_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + m_wm->m_dialogs.removeOne(this); + delete this; +} + +XdgWmDialog::XdgWmDialog(CoreCompositor *compositor, int version) + : QtWaylandServer::xdg_wm_dialog_v1(compositor->m_display, version) +{ +} + +void XdgWmDialog::xdg_wm_dialog_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void XdgWmDialog::xdg_wm_dialog_v1_get_xdg_dialog(Resource *resource, uint32_t id, + struct ::wl_resource *toplevel) +{ + auto *t = fromResource(toplevel); + auto *dialog = new XdgDialog(this, t, resource->client(), id, resource->version()); + m_dialogs.push_back(dialog); +} + +} // namespace MockCompositor diff --git a/tests/auto/wayland/shared/xdgdialog.h b/tests/auto/wayland/shared/xdgdialog.h new file mode 100644 index 00000000000..d9e3de9960e --- /dev/null +++ b/tests/auto/wayland/shared/xdgdialog.h @@ -0,0 +1,49 @@ +// Copyright (C) 2024 David Redondo +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_XDG_DIALOG_H +#define MOCKCOMPOSITOR_XDG_DIALOG_H + +#include "corecompositor.h" +#include + +namespace MockCompositor { + +class XdgToplevel; +class XdgWmDialog; + +class XdgDialog : public QtWaylandServer::xdg_dialog_v1 +{ +public: + explicit XdgDialog(XdgWmDialog *wm, XdgToplevel *toplevel, wl_client *client, int id, + int version); + XdgToplevel *toplevel; + bool modal; + +protected: + void xdg_dialog_v1_set_modal(Resource *resource) override; + void xdg_dialog_v1_unset_modal(Resource *resource) override; + void xdg_dialog_v1_destroy(Resource *resource) override; + void xdg_dialog_v1_destroy_resource(Resource *resource) override; + +private: + XdgWmDialog *m_wm; +}; + +class XdgWmDialog : public Global, public QtWaylandServer::xdg_wm_dialog_v1 +{ + Q_OBJECT +public: + explicit XdgWmDialog(CoreCompositor *compositor, int version = 1); + ~XdgWmDialog() = default; + QList m_dialogs; + +protected: + void xdg_wm_dialog_v1_destroy(Resource *resource) override; + void xdg_wm_dialog_v1_get_xdg_dialog(Resource *resource, uint32_t id, + struct ::wl_resource *toplevel) override; +}; + +} // namespace MockCompositor + +#endif diff --git a/tests/auto/wayland/shared/xdgoutputv1.cpp b/tests/auto/wayland/shared/xdgoutputv1.cpp new file mode 100644 index 00000000000..af72ae2eb61 --- /dev/null +++ b/tests/auto/wayland/shared/xdgoutputv1.cpp @@ -0,0 +1,34 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "xdgoutputv1.h" + +namespace MockCompositor { + +int XdgOutputV1::s_nextId = 1; + +void XdgOutputV1::sendLogicalSize(const QSize &size) +{ + m_logicalGeometry.setSize(size); + for (auto *resource : resourceMap()) + zxdg_output_v1::send_logical_size(resource->handle, size.width(), size.height()); +} + +void XdgOutputV1::addResource(wl_client *client, int id, int version) +{ + auto *resource = add(client, id, version)->handle; + zxdg_output_v1::send_logical_size(resource, m_logicalGeometry.width(), m_logicalGeometry.height()); + send_logical_position(resource, m_logicalGeometry.x(), m_logicalGeometry.y()); + if (version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) + send_name(resource, m_name); + if (version >= ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION) + send_description(resource, m_description); + + if (version < 3) // zxdg_output_v1.done has been deprecated + zxdg_output_v1::send_done(resource); + else { + m_output->sendDone(client); + } +} + +} // namespace MockCompositor diff --git a/tests/auto/wayland/shared/xdgoutputv1.h b/tests/auto/wayland/shared/xdgoutputv1.h new file mode 100644 index 00000000000..8c6276741b7 --- /dev/null +++ b/tests/auto/wayland/shared/xdgoutputv1.h @@ -0,0 +1,63 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_XDGOUTPUTV1_H +#define MOCKCOMPOSITOR_XDGOUTPUTV1_H + +#include "coreprotocol.h" + +#include + +namespace MockCompositor { + +class XdgOutputV1 : public QObject, public QtWaylandServer::zxdg_output_v1 +{ +public: + explicit XdgOutputV1(Output *output) + : m_output(output) + , m_logicalGeometry(m_output->m_data.position, QSize(m_output->m_data.mode.resolution / m_output->m_data.scale)) + , m_name(QString("WL-%1").arg(s_nextId++)) + {} + + void send_logical_size(int32_t width, int32_t height) = delete; + void sendLogicalSize(const QSize &size); + + void send_done() = delete; // zxdg_output_v1.done has been deprecated (in protocol version 3) + + void addResource(wl_client *client, int id, int version); + Output *m_output = nullptr; + QRect m_logicalGeometry; + QString m_name; + QString m_description = "This is an Xdg Output description"; + static int s_nextId; +}; + +class XdgOutputManagerV1 : public Global, public QtWaylandServer::zxdg_output_manager_v1 +{ + Q_OBJECT +public: + explicit XdgOutputManagerV1(CoreCompositor *compositor, int version = 3) + : QtWaylandServer::zxdg_output_manager_v1(compositor->m_display, version) + , m_version(version) + {} + int m_version = 1; // TODO: remove on libwayland upgrade + QMap m_xdgOutputs; + XdgOutputV1 *getXdgOutput(Output *output) + { + if (auto *xdgOutput = m_xdgOutputs.value(output)) + return xdgOutput; + return m_xdgOutputs[output] = new XdgOutputV1(output); // TODO: free memory + } + +protected: + void zxdg_output_manager_v1_get_xdg_output(Resource *resource, uint32_t id, wl_resource *outputResource) override + { + auto *output = fromResource(outputResource); + auto *xdgOutput = getXdgOutput(output); + xdgOutput->addResource(resource->client(), id, resource->version()); + } +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_XDGOUTPUTV1_H diff --git a/tests/auto/wayland/shared/xdgshell.cpp b/tests/auto/wayland/shared/xdgshell.cpp new file mode 100644 index 00000000000..415039ced35 --- /dev/null +++ b/tests/auto/wayland/shared/xdgshell.cpp @@ -0,0 +1,244 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "xdgshell.h" + +namespace MockCompositor { + +XdgWmBase::XdgWmBase(CoreCompositor *compositor, int version) + : QtWaylandServer::xdg_wm_base(compositor->m_display, version) + , m_compositor(compositor) +{ +} + +XdgToplevel *XdgWmBase::toplevel(int i) +{ + int j = 0; + for (auto *xdgSurface : std::as_const(m_xdgSurfaces)) { + if (auto *toplevel = xdgSurface->m_toplevel) { + if (j == i) + return toplevel; + ++j; + } + } + return nullptr; +} + +XdgPopup *XdgWmBase::popup(int i) +{ + int j = 0; + for (auto *xdgSurface : std::as_const(m_xdgSurfaces)) { + if (auto *popup = xdgSurface->m_popup) { + if (j == i) + return popup; + ++j; + } + } + return nullptr; +} + +void XdgWmBase::xdg_wm_base_get_xdg_surface(Resource *resource, uint32_t id, wl_resource *surface) +{ + auto *s = fromResource(surface); + auto *xdgSurface = new XdgSurface(this, s, resource->client(), id, resource->version()); + m_xdgSurfaces << xdgSurface; + emit xdgSurfaceCreated(xdgSurface); +} + +void XdgWmBase::xdg_wm_base_pong(Resource *resource, uint32_t serial) +{ + Q_UNUSED(resource); + emit pong(serial); +} + +XdgSurface::XdgSurface(XdgWmBase *xdgWmBase, Surface *surface, wl_client *client, int id, int version) + : QtWaylandServer::xdg_surface(client, id, version) + , m_xdgWmBase(xdgWmBase) + , m_surface(surface) +{ + QVERIFY(!surface->m_pending.buffer); + QVERIFY(!surface->m_committed.buffer); + connect(this, &XdgSurface::toplevelCreated, xdgWmBase, &XdgWmBase::toplevelCreated, Qt::DirectConnection); + connect(surface, &Surface::attach, this, [this] (void *buffer) { + if (buffer) + verifyConfigured(); + }); + connect(surface, &Surface::commit, this, [this] { + m_committed = m_pending; + + if (m_ackedConfigureSerial != m_committedConfigureSerial) { + m_committedConfigureSerial = m_ackedConfigureSerial; + emit configureCommitted(m_committedConfigureSerial); + } + }); + connect(surface, &Surface::bufferCommitted, this, [this] { + if (m_surface->m_committed.buffer && (m_toplevel || m_popup)) { + m_surface->map(); + } else { + m_surface->unmap(); + } + }); +} + +void XdgSurface::sendConfigure(uint serial) +{ + Q_ASSERT(serial); + m_pendingConfigureSerials.append(serial); + m_configureSent = true; + xdg_surface::send_configure(serial); +} + +uint XdgSurface::sendConfigure() +{ + const uint serial = m_xdgWmBase->m_compositor->nextSerial(); + sendConfigure(serial); + return serial; +} + +void XdgSurface::xdg_surface_get_toplevel(Resource *resource, uint32_t id) +{ + QVERIFY(!m_toplevel); + QVERIFY(!m_popup); + if (!m_surface->m_role) { + m_surface->m_role = new XdgToplevelRole; + } else if (!qobject_cast(m_surface->m_role)) { + QFAIL(QByteArrayLiteral("surface already has role") + m_surface->m_role->metaObject()->className()); + return; + } + m_toplevel = new XdgToplevel(this, id, resource->version()); + emit toplevelCreated(m_toplevel); +} + +void XdgSurface::xdg_surface_get_popup(Resource *resource, uint32_t id, wl_resource *parent, wl_resource *positioner) +{ + Q_UNUSED(positioner); + QVERIFY(!m_toplevel); + QVERIFY(!m_popup); + if (!m_surface->m_role) { + m_surface->m_role = new XdgPopupRole; + } else if (!qobject_cast(m_surface->m_role)) { + qWarning() << "surface already has role" << m_surface->m_role->metaObject()->className(); + return; + } + auto *p = fromResource(parent); + m_popup = new XdgPopup(this, p, id, resource->version()); +} + +void XdgSurface::xdg_surface_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource); + bool removed = m_xdgWmBase->m_xdgSurfaces.removeOne(this); + Q_ASSERT(removed); + delete this; +} + +void XdgSurface::xdg_surface_destroy(QtWaylandServer::xdg_surface::Resource *resource) +{ + QVERIFY(m_popups.empty()); + wl_resource_destroy(resource->handle); +} + +void XdgSurface::xdg_surface_set_window_geometry(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) +{ + Q_UNUSED(resource); + QRect rect(x, y, width, height); + QVERIFY(rect.isValid()); + m_pending.windowGeometry = rect; +} + +void XdgSurface::xdg_surface_ack_configure(Resource *resource, uint32_t serial) +{ + Q_UNUSED(resource); + QVERIFY2(m_pendingConfigureSerials.contains(serial), qPrintable(QString::number(serial))); + m_ackedConfigureSerial = serial; + while (!m_pendingConfigureSerials.empty()) { + uint s = m_pendingConfigureSerials.takeFirst(); + if (s == serial) + return; + } +} + +XdgToplevel::XdgToplevel(XdgSurface *xdgSurface, int id, int version) + : QtWaylandServer::xdg_toplevel(xdgSurface->resource()->client(), id, version) + , m_xdgSurface(xdgSurface) +{ + connect(surface(), &Surface::commit, this, [this] { m_committed = m_pending; }); +} + +void XdgToplevel::sendConfigureBounds(const QSize &size) +{ + send_configure_bounds(size.width(), size.height()); +} + +void XdgToplevel::sendConfigure(const QSize &size, const QList &states) +{ + send_configure(size.width(), size.height(), toByteArray(states)); +} + +uint XdgToplevel::sendCompleteConfigure(const QSize &size, const QList &states) +{ + sendConfigure(size, states); + return m_xdgSurface->sendConfigure(); +} + +void XdgToplevel::xdg_toplevel_set_max_size(Resource *resource, int32_t width, int32_t height) +{ + Q_UNUSED(resource); + QSize size(width, height); + QVERIFY(size.isValid()); + m_pending.maxSize = size; +} + +void XdgToplevel::xdg_toplevel_set_min_size(Resource *resource, int32_t width, int32_t height) +{ + Q_UNUSED(resource); + QSize size(width, height); + QVERIFY(size.isValid()); + m_pending.minSize = size; +} + +XdgPopup::XdgPopup(XdgSurface *xdgSurface, XdgSurface *parent, int id, int version) + : QtWaylandServer::xdg_popup(xdgSurface->resource()->client(), id, version) + , m_xdgSurface(xdgSurface) + , m_parentXdgSurface(parent) +{ + Q_ASSERT(m_xdgSurface); + Q_ASSERT(m_parentXdgSurface); + m_parentXdgSurface->m_popups << this; +} + +void XdgPopup::sendConfigure(const QRect &geometry) +{ + send_configure(geometry.x(), geometry.y(), geometry.width(), geometry.height()); +} + +uint XdgPopup::sendCompleteConfigure(const QRect &geometry) +{ + sendConfigure(geometry); + return m_xdgSurface->sendConfigure(); +} + +void XdgPopup::xdg_popup_grab(QtWaylandServer::xdg_popup::Resource *resource, wl_resource *seat, uint32_t serial) +{ + Q_UNUSED(resource); + Q_UNUSED(seat); // TODO: verify correct seat as well + QVERIFY(!m_grabbed); + QVERIFY(m_parentXdgSurface->isValidPopupGrabParent()); + m_xdgSurface->m_xdgWmBase->m_topmostGrabbingPopup = this; + m_grabbed = true; + m_grabSerial = serial; +} + +void XdgPopup::xdg_popup_destroy(Resource *resource) { + Q_UNUSED(resource); + if (m_grabbed) { + auto *base = m_xdgSurface->m_xdgWmBase; + QCOMPARE(base->m_topmostGrabbingPopup, this); + base->m_topmostGrabbingPopup = this->m_parentXdgSurface->m_popup; + } + m_xdgSurface->m_popup = nullptr; + m_parentXdgSurface->m_popups.removeAll(this); + emit destroyRequested(); +} + +} // namespace MockCompositor diff --git a/tests/auto/wayland/shared/xdgshell.h b/tests/auto/wayland/shared/xdgshell.h new file mode 100644 index 00000000000..7eab8028e80 --- /dev/null +++ b/tests/auto/wayland/shared/xdgshell.h @@ -0,0 +1,140 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef MOCKCOMPOSITOR_XDGSHELL_H +#define MOCKCOMPOSITOR_XDGSHELL_H + +#include "coreprotocol.h" +#include + +namespace MockCompositor { + +class XdgSurface; +class XdgToplevel; +class XdgPopup; +using XdgPositioner = QtWaylandServer::xdg_positioner; + +class XdgWmBase : public Global, public QtWaylandServer::xdg_wm_base +{ + Q_OBJECT +public: + explicit XdgWmBase(CoreCompositor *compositor, int version = 4); + using QtWaylandServer::xdg_wm_base::send_ping; + void send_ping(uint32_t) = delete; // It's a global, use resource specific instead + bool isClean() override { return m_xdgSurfaces.empty(); } + QString dirtyMessage() override { return m_xdgSurfaces.empty() ? "clean" : "remaining xdg surfaces"; } + QList m_xdgSurfaces; + XdgToplevel *toplevel(int i = 0); + XdgPopup *popup(int i = 0); + XdgPopup *m_topmostGrabbingPopup = nullptr; + CoreCompositor *m_compositor = nullptr; + +signals: + void pong(uint serial); + void xdgSurfaceCreated(XdgSurface *xdgSurface); + void toplevelCreated(XdgToplevel *toplevel); + +protected: + void xdg_wm_base_get_xdg_surface(Resource *resource, uint32_t id, ::wl_resource *surface) override; + void xdg_wm_base_pong(Resource *resource, uint32_t serial) override; + void xdg_wm_base_create_positioner(Resource *resource, uint32_t id) override + { + new XdgPositioner(resource->client(), id, resource->version()); + } +}; + +class XdgSurface : public QObject, public QtWaylandServer::xdg_surface +{ + Q_OBJECT +public: + explicit XdgSurface(XdgWmBase *xdgWmBase, Surface *surface, wl_client *client, int id, int version); + void send_configure(uint serial) = delete; // Use the one below instead, as it tracks state + void sendConfigure(uint serial); + uint sendConfigure(); + bool isTopmostGrabbingPopup() const { return m_popup && m_xdgWmBase->m_topmostGrabbingPopup == m_popup; } + bool isValidPopupGrabParent() const { return isTopmostGrabbingPopup() || (m_toplevel && !m_xdgWmBase->m_topmostGrabbingPopup); } + + // Role objects + XdgToplevel *m_toplevel = nullptr; + XdgPopup *m_popup = nullptr; + + XdgWmBase *m_xdgWmBase = nullptr; + Surface *m_surface = nullptr; + bool m_configureSent = false; + QList m_pendingConfigureSerials; + uint m_ackedConfigureSerial = 0; + uint m_committedConfigureSerial = 0; + struct DoubleBufferedState { + QRect windowGeometry = {0, 0, 0, 0}; + } m_pending, m_committed; + QList m_popups; + +public slots: + void verifyConfigured() { QVERIFY(m_configureSent); } + +signals: + void configureCommitted(uint); + void toplevelCreated(XdgToplevel *toplevel); + +protected: + void xdg_surface_get_toplevel(Resource *resource, uint32_t id) override; + void xdg_surface_get_popup(Resource *resource, uint32_t id, ::wl_resource *parent, ::wl_resource *positioner) override; + void xdg_surface_destroy_resource(Resource *resource) override; + void xdg_surface_destroy(Resource *resource) override; + void xdg_surface_set_window_geometry(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) override; + void xdg_surface_ack_configure(Resource *resource, uint32_t serial) override; +}; + +class XdgToplevelRole : public SurfaceRole +{ + Q_OBJECT +}; + +class XdgToplevel : public QObject, public QtWaylandServer::xdg_toplevel +{ + Q_OBJECT +public: + explicit XdgToplevel(XdgSurface *xdgSurface, int id, int version = 1); + void sendConfigureBounds(const QSize &size); + void sendConfigure(const QSize &size = {0, 0}, const QList &states = {}); + uint sendCompleteConfigure(const QSize &size = {0, 0}, const QList &states = {}); + Surface *surface() { return m_xdgSurface->m_surface; } + + XdgSurface *m_xdgSurface = nullptr; + struct DoubleBufferedState { + QSize minSize = {0, 0}; + QSize maxSize = {0, 0}; + } m_pending, m_committed; + +protected: + void xdg_toplevel_set_max_size(Resource *resource, int32_t width, int32_t height) override; + void xdg_toplevel_set_min_size(Resource *resource, int32_t width, int32_t height) override; +}; + +class XdgPopupRole : public SurfaceRole +{ + Q_OBJECT +}; + +class XdgPopup : public QObject, public QtWaylandServer::xdg_popup +{ + Q_OBJECT +public: + explicit XdgPopup(XdgSurface *xdgSurface, XdgSurface *parent, int id, int version = 1); + void sendConfigure(const QRect &geometry); + uint sendCompleteConfigure(const QRect &geometry); + Surface *surface() { return m_xdgSurface->m_surface; } + XdgSurface *m_xdgSurface = nullptr; + XdgSurface *m_parentXdgSurface = nullptr; + bool m_grabbed = false; + uint m_grabSerial = 0; +signals: + void destroyRequested(); +protected: + void xdg_popup_grab(Resource *resource, ::wl_resource *seat, uint32_t serial) override; + void xdg_popup_destroy(Resource *resource) override; +}; + +} // namespace MockCompositor + +#endif // MOCKCOMPOSITOR_XDGSHELL_H diff --git a/tests/auto/wayland/surface/CMakeLists.txt b/tests/auto/wayland/surface/CMakeLists.txt new file mode 100644 index 00000000000..b175a5331e9 --- /dev/null +++ b/tests/auto/wayland/surface/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from surface.pro. + +##################################################################### +## tst_surface Test: +##################################################################### + +qt_internal_add_test(tst_surface + SOURCES + tst_surface.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/surface/tst_surface.cpp b/tests/auto/wayland/surface/tst_surface.cpp new file mode 100644 index 00000000000..e5512faad06 --- /dev/null +++ b/tests/auto/wayland/surface/tst_surface.cpp @@ -0,0 +1,247 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include +#if QT_CONFIG(opengl) +#include +#endif + +using namespace MockCompositor; + +class tst_surface : public QObject, private DefaultCompositor +{ + Q_OBJECT +public: + explicit tst_surface(); +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void createDestroySurface(); + void waitForFrameCallbackRaster(); +#if QT_CONFIG(opengl) + void waitForFrameCallbackGl(); +#endif + void negotiateShmFormat(); + + // Subsurfaces + void createSubsurface(); + void createSubsurfaceForHiddenParent(); + void changeToSubsurface(); +}; + +tst_surface::tst_surface() +{ + m_config.autoFrameCallback = false; +} + +void tst_surface::createDestroySurface() +{ + QWindow window; + window.show(); + + QCOMPOSITOR_TRY_VERIFY(surface()); + + window.destroy(); + QCOMPOSITOR_TRY_VERIFY(!surface()); +} + +void tst_surface::waitForFrameCallbackRaster() +{ + class TestWindow : public QRasterWindow { + public: + explicit TestWindow() { resize(40, 40); } + void paintEvent(QPaintEvent *event) override + { + Q_UNUSED(event); + update(); + } + }; + TestWindow window; + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + QSignalSpy bufferSpy(exec([&] { return xdgSurface()->m_surface; }), &Surface::bufferCommitted); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + + // We should get the first buffer without waiting for a frame callback + QTRY_COMPARE(bufferSpy.size(), 1); + bufferSpy.removeFirst(); + + // Make sure we follow frame callbacks for some frames + for (int i = 0; i < 5; ++i) { + xdgPingAndWaitForPong(); // Make sure things have happened on the client + exec([&] { + QVERIFY(bufferSpy.empty()); // Make sure no extra buffers have arrived + QVERIFY(!xdgToplevel()->surface()->m_waitingFrameCallbacks.empty()); + xdgToplevel()->surface()->sendFrameCallbacks(); + }); + QTRY_COMPARE(bufferSpy.size(), 1); + bufferSpy.removeFirst(); + } +} + +#if QT_CONFIG(opengl) +void tst_surface::waitForFrameCallbackGl() +{ + class TestWindow : public QOpenGLWindow { + public: + explicit TestWindow() + { + resize(40, 40); + connect(this, &QOpenGLWindow::frameSwapped, + this, QOverload<>::of(&QPaintDeviceWindow::update)); + update(); + } + void paintGL() override + { + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + }; + TestWindow window; + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + QSignalSpy bufferSpy(exec([&] { return xdgSurface()->m_surface; }), &Surface::bufferCommitted); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + + // We should get the first buffer without waiting for a frame callback + QTRY_COMPARE(bufferSpy.size(), 1); + bufferSpy.removeFirst(); + + // Make sure we follow frame callbacks for some frames + for (int i = 0; i < 5; ++i) { + xdgPingAndWaitForPong(); // Make sure things have happened on the client + if (!qEnvironmentVariableIntValue("QT_WAYLAND_DISABLE_WINDOWDECORATION") && i == 0) { + QCOMPARE(bufferSpy.size(), 1); + bufferSpy.removeFirst(); + } + exec([&] { + QVERIFY(bufferSpy.empty()); // Make sure no extra buffers have arrived + QVERIFY(!xdgToplevel()->surface()->m_waitingFrameCallbacks.empty()); + xdgToplevel()->surface()->sendFrameCallbacks(); + }); + QTRY_COMPARE(bufferSpy.size(), 1); + bufferSpy.removeFirst(); + } +} +#endif // QT_CONFIG(opengl) + +void tst_surface::negotiateShmFormat() +{ + QSKIP("TODO: I'm not sure why we're choosing xrgb over argb in this case..."); + QRasterWindow window; + window.setFlag(Qt::FramelessWindowHint); // decorations force alpha + QSurfaceFormat format; + format.setAlphaBufferSize(0); + window.setFormat(format); + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + QSignalSpy bufferSpy(exec([&] { return xdgSurface()->m_surface; }), &Surface::bufferCommitted); + const uint serial = exec([&] { return xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, serial); + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + auto *shmBuffer = ShmBuffer::fromBuffer(buffer); + QVERIFY(shmBuffer); + qDebug() << "shmBuffer->m_format" << shmBuffer->m_format; + QCOMPARE(shmBuffer->m_format, Shm::format_xrgb8888); + }); +} + +void tst_surface::createSubsurface() +{ + m_config.autoFrameCallback = true; + auto autoFrameCallback = qScopeGuard([&] { m_config.autoFrameCallback = false; }); + + QRasterWindow window; + window.setObjectName("main"); + window.resize(200, 200); + + QRasterWindow subWindow; + subWindow.setObjectName("subwindow"); + subWindow.setParent(&window); + subWindow.resize(64, 64); + + window.show(); + subWindow.show(); + + QCOMPOSITOR_TRY_VERIFY(subSurface()); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgSurface()->m_committedConfigureSerial); + + const Surface *mainSurface = exec([&] {return surface(0);}); + const Surface *childSurface = exec([&] {return surface(1);}); + QSignalSpy mainSurfaceCommitSpy(mainSurface, &Surface::commit); + QSignalSpy childSurfaceCommitSpy(childSurface, &Surface::commit); + + // Move subsurface. The parent should redraw and commit + subWindow.setGeometry(100, 100, 64, 64); + // the toplevel should commit to indicate the subsurface moved + QCOMPOSITOR_TRY_COMPARE(mainSurfaceCommitSpy.size(), 1); + mainSurfaceCommitSpy.clear(); + childSurfaceCommitSpy.clear(); + + // Move and resize the subSurface. The parent should redraw and commit + // The child should also redraw + subWindow.setGeometry(50, 50, 80, 80); + QCOMPOSITOR_TRY_COMPARE(mainSurfaceCommitSpy.size(), 1); + QCOMPOSITOR_TRY_COMPARE(childSurfaceCommitSpy.size(), 1); + +} + +// Used to cause a crash in libwayland (QTBUG-79674) +void tst_surface::createSubsurfaceForHiddenParent() +{ + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgSurface()->m_committedConfigureSerial); + + window.hide(); + + QRasterWindow subWindow; + subWindow.setParent(&window); + subWindow.resize(64, 64); + subWindow.show(); + + // Make sure the client doesn't quit before it has a chance to crash + xdgPingAndWaitForPong(); + + // Make sure the subsurface was actually created + const Subsurface *subsurface = exec([&] {return subSurface(0);}); + QVERIFY(subsurface); +} + +void tst_surface::changeToSubsurface() +{ + QRasterWindow window1; + window1.resize(64, 64); + window1.show(); + + QRasterWindow window2; + window2.resize(64, 64); + window2.show(); + + window2.setParent(&window1); + QCOMPOSITOR_TRY_VERIFY(subSurface()); + + window2.setParent(nullptr); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel(1)); + + window2.hide(); + window2.setParent(&window1); + window2.show(); + QCOMPOSITOR_TRY_VERIFY(subSurface()); + + window2.hide(); + window2.setParent(nullptr); + window2.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel(1)); +} + +QCOMPOSITOR_TEST_MAIN(tst_surface) +#include "tst_surface.moc" diff --git a/tests/auto/wayland/tabletv2/CMakeLists.txt b/tests/auto/wayland/tabletv2/CMakeLists.txt new file mode 100644 index 00000000000..1400a511a96 --- /dev/null +++ b/tests/auto/wayland/tabletv2/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from tabletv2.pro. + +##################################################################### +## tst_tabletv2 Test: +##################################################################### + +qt_internal_add_test(tst_tabletv2 + SOURCES + tst_tabletv2.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/tabletv2/tst_tabletv2.cpp b/tests/auto/wayland/tabletv2/tst_tabletv2.cpp new file mode 100644 index 00000000000..d5c2ccb3665 --- /dev/null +++ b/tests/auto/wayland/tabletv2/tst_tabletv2.cpp @@ -0,0 +1,933 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include + +#include + +using namespace MockCompositor; + +constexpr int tabletVersion = 1; // protocol VERSION, not the name suffix (_v2) + +class TabletManagerV2; +class TabletSeatV2; + +class TabletV2 : public QObject, public QtWaylandServer::zwp_tablet_v2 +{ + Q_OBJECT +public: + explicit TabletV2(TabletSeatV2 *tabletSeat) + : m_tabletSeat(tabletSeat) + { + } + + void send_removed() = delete; + void send_removed(struct ::wl_resource *resource) = delete; + void sendRemoved(); + + QPointer m_tabletSeat; // destroy order is not guaranteed +protected: + void zwp_tablet_v2_destroy(Resource *resource) override; +}; + +class TabletToolV2 : public QObject, public QtWaylandServer::zwp_tablet_tool_v2 +{ + Q_OBJECT +public: + using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type; + explicit TabletToolV2(TabletSeatV2 *tabletSeat, ToolType toolType, quint64 hardwareSerial) + : m_tabletSeat(tabletSeat) + , m_toolType(toolType) + , m_hardwareSerial(hardwareSerial) + { + } + + wl_resource *toolResource() // for convenience + { + Q_ASSERT(resourceMap().size() == 1); + // Strictly speaking, there may be more than one resource for the tool, for intsance if + // if there are multiple clients, or a client has called get_tablet_seat multiple times. + // For now we'll pretend there can only be one resource. + return resourceMap().first()->handle; + } + + void send_removed() = delete; + void send_removed(struct ::wl_resource *resource) = delete; + void sendRemoved(); + + uint sendProximityIn(TabletV2 *tablet, Surface *surface); + void sendProximityOut(); + void sendMotion(QPointF position) + { + Q_ASSERT(m_proximitySurface); + for (auto *resource : resourceMap()) + send_motion(resource->handle, wl_fixed_from_double(position.x()), wl_fixed_from_double(position.y())); + } + uint sendDown(); + void sendUp() { send_up(toolResource()); } + void sendPressure(uint pressure); + void sendTilt(qreal tiltX, qreal tiltY) { send_tilt(toolResource(), wl_fixed_from_double(tiltX), wl_fixed_from_double(tiltY)); } + void sendRotation(qreal rotation) { send_rotation(toolResource(), wl_fixed_from_double(rotation)); } + uint sendButton(uint button, bool pressed); + uint sendFrame(); + + QPointer m_tabletSeat; // destruction order is not guaranteed + ToolType m_toolType = ToolType::type_pen; + quint64 m_hardwareSerial = 0; + QPointer m_proximitySurface; +protected: + void zwp_tablet_tool_v2_destroy(Resource *resource) override; +}; + +class TabletPadV2 : public QObject, public QtWaylandServer::zwp_tablet_pad_v2 +{ + Q_OBJECT +public: + explicit TabletPadV2(TabletSeatV2 *tabletSeat) + : m_tabletSeat(tabletSeat) + { + } + + void send_removed() = delete; + void send_removed(struct ::wl_resource *resource) = delete; + void sendRemoved(); + + QPointer m_tabletSeat; // destroy order is not guaranteed +protected: + void zwp_tablet_pad_v2_destroy(Resource *resource) override; +}; + +class TabletSeatV2 : public QObject, public QtWaylandServer::zwp_tablet_seat_v2 +{ + Q_OBJECT +public: + explicit TabletSeatV2(TabletManagerV2 *manager, Seat *seat) + : m_manager(manager) + , m_seat(seat) + {} + TabletV2 *addTablet() + { + auto *tablet = new TabletV2(this); + m_tablets.append(tablet); + for (auto *resource : resourceMap()) + sendTabletAdded(resource, tablet); + return tablet; + } + + void sendTabletAdded(Resource *resource, TabletV2 *tablet) + { + // Although, not necessarily correct, assuming just one tablet_seat per client + auto *tabletResource = tablet->add(resource->client(), resource->version()); + zwp_tablet_seat_v2::send_tablet_added(resource->handle, tabletResource->handle); + // TODO: send extra stuff before done? + tablet->send_done(tabletResource->handle); + } + + using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type; + TabletToolV2 *addTool(ToolType toolType = ToolType::type_pen, quint64 hardwareSerial = 0) + { + auto *tool = new TabletToolV2(this, toolType, hardwareSerial); + m_tools.append(tool); + for (auto *resource : resourceMap()) + sendToolAdded(resource, tool); + return tool; + } + + void sendToolAdded(Resource *resource, TabletToolV2 *tool) + { + // Although, not necessarily correct, assuming just one tablet_seat per client + auto *toolResource = tool->add(resource->client(), resource->version())->handle; + zwp_tablet_seat_v2::send_tool_added(resource->handle, toolResource); + tool->send_type(toolResource, tool->m_toolType); + if (tool->m_hardwareSerial) { + const uint hi = tool->m_hardwareSerial >> 32; + const uint lo = tool->m_hardwareSerial & 0xffffffff; + tool->send_hardware_serial(toolResource, hi, lo); + } + tool->send_done(toolResource); + } + + TabletPadV2 *addPad() + { + auto *pad = new TabletPadV2(this); + m_pads.append(pad); + for (auto *resource : resourceMap()) + sendPadAdded(resource, pad); + return pad; + } + + void sendPadAdded(Resource *resource, TabletPadV2 *pad) + { + // Although, not necessarily correct, assuming just one tablet_seat per client + auto *padResource = pad->add(resource->client(), resource->version())->handle; + zwp_tablet_seat_v2::send_pad_added(resource->handle, padResource); + pad->send_done(padResource); + } + + void removeAll() + { + const auto tools = m_tools; + for (auto *tool : tools) + tool->sendRemoved(); + + const auto tablets = m_tablets; + for (auto *tablet : tablets) + tablet->sendRemoved(); + + const auto pads = m_pads; + for (auto *pad : pads) + pad->sendRemoved(); + } + + TabletManagerV2 *m_manager = nullptr; + Seat *m_seat = nullptr; + QList m_tablets; + QList m_tabletsWaitingForDestroy; + QList m_tools; + QList m_toolsWaitingForDestroy; + QList m_pads; + QList m_padsWaitingForDestroy; + +protected: + void zwp_tablet_seat_v2_bind_resource(Resource *resource) override + { + for (auto *tablet : m_tablets) + sendTabletAdded(resource, tablet); + for (auto *tool : m_tools) + sendToolAdded(resource, tool); + for (auto *pad : m_pads) + sendPadAdded(resource, pad); + } +}; + +class TabletManagerV2 : public Global, public QtWaylandServer::zwp_tablet_manager_v2 +{ + Q_OBJECT +public: + explicit TabletManagerV2(CoreCompositor *compositor, int version = 1) + : QtWaylandServer::zwp_tablet_manager_v2(compositor->m_display, version) + , m_version(version) + {} + bool isClean() override + { + for (auto *seat : m_tabletSeats) { + if (!seat->m_tabletsWaitingForDestroy.empty()) + return false; + if (!seat->m_toolsWaitingForDestroy.empty()) + return false; + if (!seat->m_padsWaitingForDestroy.empty()) + return false; + } + return true; + } + + TabletSeatV2 *tabletSeatFor(Seat *seat) + { + Q_ASSERT(seat); + if (auto *tabletSeat = m_tabletSeats.value(seat, nullptr)) + return tabletSeat; + + auto *tabletSeat = new TabletSeatV2(this, seat); + m_tabletSeats[seat] = tabletSeat; + return tabletSeat; + } + + int m_version = 1; // TODO: Remove on libwayland upgrade + QMap m_tabletSeats; + +protected: + void zwp_tablet_manager_v2_destroy(Resource *resource) override + { + // tablet_seats created from this object are unaffected and should be destroyed separately. + wl_resource_destroy(resource->handle); + } + + void zwp_tablet_manager_v2_get_tablet_seat(Resource *resource, uint32_t id, ::wl_resource *seatResource) override + { + auto *seat = fromResource(seatResource); + QVERIFY(seat); + auto *tabletSeat = tabletSeatFor(seat); + tabletSeat->add(resource->client(), id, resource->version()); + } +}; + +void TabletV2::sendRemoved() +{ + for (auto *resource : resourceMap()) + zwp_tablet_v2_send_removed(resource->handle); + bool removed = m_tabletSeat->m_tablets.removeOne(this); + QVERIFY(removed); + m_tabletSeat->m_tabletsWaitingForDestroy.append(this); +} + +void TabletV2::zwp_tablet_v2_destroy(QtWaylandServer::zwp_tablet_v2::Resource *resource) +{ + Q_UNUSED(resource); + if (m_tabletSeat) { + bool removed = m_tabletSeat->m_tabletsWaitingForDestroy.removeOne(this); + QVERIFY(removed); + } + wl_resource_destroy(resource->handle); +} + +void TabletToolV2::sendRemoved() +{ + for (auto *resource : resourceMap()) { + zwp_tablet_tool_v2_send_removed(resource->handle); + m_tabletSeat->m_toolsWaitingForDestroy.append(resource); + } + bool removed = m_tabletSeat->m_tools.removeOne(this); + QVERIFY(removed); +} + +uint TabletToolV2::sendProximityIn(TabletV2 *tablet, Surface *surface) +{ + Q_ASSERT(!m_proximitySurface); + m_proximitySurface = surface; + uint serial = m_tabletSeat->m_seat->m_compositor->nextSerial(); + auto *client = surface->resource()->client(); + auto tabletResource = tablet->resourceMap().value(client)->handle; + send_proximity_in(toolResource(), serial, tabletResource, surface->resource()->handle); + return serial; +} + +void TabletToolV2::sendProximityOut() +{ + Q_ASSERT(m_proximitySurface); + send_proximity_out(toolResource()); + m_proximitySurface = nullptr; +} + +uint TabletToolV2::sendDown() +{ + uint serial = m_tabletSeat->m_seat->m_compositor->nextSerial(); + send_down(toolResource(), serial); + return serial; +} + +void TabletToolV2::sendPressure(uint pressure) +{ + Q_ASSERT(m_proximitySurface); + auto *client = m_proximitySurface->resource()->client(); + auto toolResource = resourceMap().value(client)->handle; + send_pressure(toolResource, pressure); +} + +uint TabletToolV2::sendButton(uint button, bool pressed) +{ + button_state state = pressed ? button_state_pressed : button_state_released; + uint serial = m_tabletSeat->m_seat->m_compositor->nextSerial(); + send_button(toolResource(), serial, button, state); + return serial; +} + +uint TabletToolV2::sendFrame() +{ + uint time = m_tabletSeat->m_seat->m_compositor->currentTimeMilliseconds(); + for (auto *resource : resourceMap()) + send_frame(resource->handle, time); + return time; +} + +void TabletToolV2::zwp_tablet_tool_v2_destroy(QtWaylandServer::zwp_tablet_tool_v2::Resource *resource) +{ + if (m_tabletSeat) { + m_tabletSeat->m_toolsWaitingForDestroy.removeOne(resource); + } + wl_resource_destroy(resource->handle); +} + +void TabletPadV2::sendRemoved() +{ + for (auto *resource : resourceMap()) { + zwp_tablet_pad_v2_send_removed(resource->handle); + m_tabletSeat->m_padsWaitingForDestroy.append(resource); + } + bool removed = m_tabletSeat->m_pads.removeOne(this); + QVERIFY(removed); +} + +void TabletPadV2::zwp_tablet_pad_v2_destroy(QtWaylandServer::zwp_tablet_pad_v2::Resource *resource) +{ + if (m_tabletSeat) { + m_tabletSeat->m_padsWaitingForDestroy.removeOne(resource); + } + wl_resource_destroy(resource->handle); +} + +class TabletCompositor : public DefaultCompositor { +public: + explicit TabletCompositor() + { + exec([this] { + m_config.autoConfigure = true; + add(tabletVersion); + }); + } + TabletSeatV2 *tabletSeat(int i = 0) + { + return get()->tabletSeatFor(get(i)); + } + TabletV2 *tablet(int i = 0, int iSeat = 0) + { + if (auto *ts = tabletSeat(iSeat)) + return ts->m_tablets.value(i, nullptr); + return nullptr; + } + TabletToolV2 *tabletTool(int i = 0, int iSeat = 0) + { + if (auto *ts = tabletSeat(iSeat)) + return ts->m_tools.value(i, nullptr); + return nullptr; + } + TabletPadV2 *tabletPad(int i = 0, int iSeat = 0) + { + if (auto *ts = tabletSeat(iSeat)) + return ts->m_pads.value(i, nullptr); + return nullptr; + } +}; + +Q_DECLARE_METATYPE(QtWaylandServer::zwp_tablet_tool_v2::type); +Q_DECLARE_METATYPE(QPointingDevice::PointerType); +Q_DECLARE_METATYPE(Qt::MouseButton); + +class tst_tabletv2 : public QObject, private TabletCompositor +{ + using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type; + Q_OBJECT +private slots: + void cleanup(); + void bindsToManager(); + void createsTabletSeat(); + void destroysTablet(); + void destroysTool(); + void destroysPad(); + void removeTabletBeforeTool(); + void removeTabletBeforePad(); + void proximityEvents(); + void moveEvent(); + void pointerType_data(); + void pointerType(); + void hardwareSerial(); + void buttons_data(); + void buttons(); + void tabletEvents(); +}; + +class ProximityFilter : public QObject { + Q_OBJECT +public: + ProximityFilter() { qApp->installEventFilter(this); } + ~ProximityFilter() override { qDeleteAll(m_events); } + QList m_events; + + int nextEventIndex = 0; + int numEvents() const { return m_events.size() - nextEventIndex; } + QTabletEvent *popEvent() + { + auto *event = m_events.value(nextEventIndex, nullptr); + if (event) + ++nextEventIndex; + return event; + } + +protected: + bool eventFilter(QObject *object, QEvent *event) override + { + Q_UNUSED(object); + switch (event->type()) { + case QEvent::TabletEnterProximity: + case QEvent::TabletLeaveProximity: { + auto *e = static_cast(event); + auto *ev = new QTabletEvent(e->type(), e->pointingDevice(), e->position(), e->globalPosition(), + e->pressure(), e->xTilt(), e->yTilt(), + e->tangentialPressure(), e->rotation(), e->z(), + Qt::KeyboardModifier::NoModifier, + e->button(), e->buttons()); + m_events << ev; + break; + } + default: + break; + } + return false; + } +}; + +void tst_tabletv2::cleanup() +{ + exec([&] { + tabletSeat()->removeAll(); + }); + QCOMPOSITOR_COMPARE(get()->m_tabletSeats.size(), 1); + QCOMPOSITOR_COMPARE(tabletSeat()->m_tablets.size(), 0); + QCOMPOSITOR_COMPARE(tabletSeat()->m_tools.size(), 0); + QCOMPOSITOR_COMPARE(tabletSeat()->m_pads.size(), 0); + + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); +} + +void tst_tabletv2::bindsToManager() +{ + QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().first()->version(), tabletVersion); +} + +void tst_tabletv2::createsTabletSeat() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + QCOMPOSITOR_TRY_VERIFY(tabletSeat()->resourceMap().contains(client())); + QCOMPOSITOR_TRY_COMPARE(tabletSeat()->resourceMap().value(client())->version(), tabletVersion); + //TODO: Maybe also assert some capability reported though qt APIs? +} + +void tst_tabletv2::destroysTablet() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + }); + QCOMPOSITOR_TRY_VERIFY(tablet()); + + exec([&] { + tablet()->sendRemoved(); + }); + + QCOMPOSITOR_TRY_VERIFY(!tablet()); + QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_tabletsWaitingForDestroy.empty()); +} + +void tst_tabletv2::destroysTool() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(); + }); + QCOMPOSITOR_TRY_VERIFY(tabletTool()); + + exec([&] { + tabletTool()->sendRemoved(); + tablet()->sendRemoved(); + }); + + QCOMPOSITOR_TRY_VERIFY(!tabletTool()); + QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_toolsWaitingForDestroy.empty()); +} + +void tst_tabletv2::destroysPad() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addPad(); + }); + QCOMPOSITOR_TRY_VERIFY(tabletPad()); + + exec([&] { + tabletPad()->sendRemoved(); + }); + + QCOMPOSITOR_TRY_VERIFY(!tabletPad()); + QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_padsWaitingForDestroy.empty()); +} + +void tst_tabletv2::removeTabletBeforeTool() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(); + }); + QCOMPOSITOR_TRY_VERIFY(tablet()); + QCOMPOSITOR_TRY_VERIFY(tabletTool()); + + exec([&] { tablet()->sendRemoved(); }); + QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_tabletsWaitingForDestroy.empty()); + + exec([&] { tabletTool()->sendRemoved(); }); + QCOMPOSITOR_TRY_VERIFY(!tabletTool()); + QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_toolsWaitingForDestroy.empty()); +} + +void tst_tabletv2::removeTabletBeforePad() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addPad(); + }); + QCOMPOSITOR_TRY_VERIFY(tablet()); + QCOMPOSITOR_TRY_VERIFY(tabletPad()); + + exec([&] { tablet()->sendRemoved(); }); + QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_tabletsWaitingForDestroy.empty()); + + exec([&] { tabletPad()->sendRemoved(); }); + QCOMPOSITOR_TRY_VERIFY(!tabletPad()); + QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_padsWaitingForDestroy.empty()); +} + +void tst_tabletv2::proximityEvents() +{ + ProximityFilter filter; + + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(); + }); + + QRasterWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + auto *surface = xdgSurface()->m_surface; + auto *tool = tabletTool(); + tool->sendProximityIn(tablet(), surface); + tool->sendFrame(); + }); + + QTRY_COMPARE(filter.numEvents(), 1); + QTabletEvent *enterEvent = filter.popEvent(); + QCOMPARE(enterEvent->type(), QEvent::TabletEnterProximity); + + exec([&] { + auto *tool = tabletTool(); + tool->sendProximityOut(); + tool->sendFrame(); + }); + + QTRY_COMPARE(filter.numEvents(), 1); + QTabletEvent *leaveEvent = filter.popEvent(); + QCOMPARE(leaveEvent->type(), QEvent::TabletLeaveProximity); +} + +class TabletWindow : public QRasterWindow { + Q_OBJECT +public: + ~TabletWindow() override { qDeleteAll(m_events); } + + void tabletEvent(QTabletEvent *e) override + { + m_events << new QTabletEvent(e->type(), e->pointingDevice(), e->position(), e->globalPosition(), + e->pressure(), e->xTilt(), e->yTilt(), + e->tangentialPressure(), e->rotation(), e->z(), + Qt::KeyboardModifier::NoModifier, + e->button(), e->buttons()); + emit tabletEventReceived(m_events.last()); + } + int nextEventIndex = 0; + int numEvents() const { return m_events.size() - nextEventIndex; } + QTabletEvent *popEvent() + { + auto *event = m_events.value(nextEventIndex, nullptr); + if (event) + ++nextEventIndex; + return event; + } + +signals: + void tabletEventReceived(QTabletEvent *event); + +private: + QList m_events; +}; + +void tst_tabletv2::moveEvent() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(); + }); + + TabletWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + auto *surface = xdgSurface()->m_surface; + auto *tool = tabletTool(); + tool->sendProximityIn(tablet(), surface); + QMargins margins = window.frameMargins(); + tool->sendMotion(QPointF(12 + margins.left(), 34 + margins.top())); + tool->sendFrame(); + }); + QTRY_VERIFY(window.numEvents()); + QTabletEvent *event = window.popEvent(); + QCOMPARE(event->type(), QEvent::TabletMove); + QCOMPARE(event->pressure(), 0); + QCOMPARE(event->position(), QPointF(12, 34)); +} + +void tst_tabletv2::pointerType_data() +{ + QTest::addColumn("toolType"); + QTest::addColumn("pointerType"); + QTest::addColumn("tabletDevice"); + + QTest::newRow("pen") << ToolType::type_pen << QPointingDevice::PointerType::Pen << QInputDevice::DeviceType::Stylus; + QTest::newRow("eraser") << ToolType::type_eraser << QPointingDevice::PointerType::Eraser << QInputDevice::DeviceType::Stylus; + QTest::newRow("pencil") << ToolType::type_pencil << QPointingDevice::PointerType::Pen << QInputDevice::DeviceType::Stylus; + QTest::newRow("airbrush") << ToolType::type_airbrush << QPointingDevice::PointerType::Pen << QInputDevice::DeviceType::Airbrush; + QTest::newRow("brush") << ToolType::type_brush << QPointingDevice::PointerType::Pen << QInputDevice::DeviceType::Stylus; // TODO: is TabletDevice::Stylus the right thing? + QTest::newRow("lens") << ToolType::type_lens << QPointingDevice::PointerType::Cursor << QInputDevice::DeviceType::Puck; + // TODO: also add tests for FourDMouse and RotationStylus (also need to send capabilities) + + // TODO: should these rather be mapped to touch/mouse events? + QTest::newRow("finger") << ToolType::type_finger << QPointingDevice::PointerType::Unknown << QInputDevice::DeviceType::Unknown; + QTest::newRow("mouse") << ToolType::type_mouse << QPointingDevice::PointerType::Cursor << QInputDevice::DeviceType::Unknown; +} + +void tst_tabletv2::pointerType() +{ + using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type; + QFETCH(ToolType, toolType); + QFETCH(QPointingDevice::PointerType, pointerType); + QFETCH(QInputDevice::DeviceType, tabletDevice); + + ProximityFilter filter; + + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(toolType); + }); + + TabletWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + auto *surface = xdgSurface()->m_surface; + auto *tool = tabletTool(); + tool->sendProximityIn(tablet(), surface); + QMargins margins = window.frameMargins(); + tool->sendMotion(QPointF(12 + margins.left(), 34 + margins.top())); + tool->sendFrame(); + }); + + QTRY_COMPARE(filter.numEvents(), 1); + QTabletEvent *event = filter.popEvent(); + QCOMPARE(event->pointerType(), pointerType); + QCOMPARE(event->deviceType(), tabletDevice); + + QTRY_VERIFY(window.numEvents()); + event = window.popEvent(); + QCOMPARE(event->pointerType(), pointerType); + QCOMPARE(event->deviceType(), tabletDevice); + + exec([&] { + tabletTool()->sendProximityOut(); + tabletTool()->sendFrame(); + }); + + QTRY_VERIFY(filter.numEvents()); + event = filter.popEvent(); + QCOMPARE(event->pointerType(), pointerType); + QCOMPARE(event->deviceType(), tabletDevice); +} + +void tst_tabletv2::hardwareSerial() +{ + ProximityFilter filter; + const QPointingDeviceUniqueId uid = QPointingDeviceUniqueId::fromNumericId(0xbaba15dead15f00d); + + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(ToolType::type_pen, uid.numericId()); + }); + + TabletWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + auto *surface = xdgSurface()->m_surface; + auto *tool = tabletTool(); + tool->sendProximityIn(tablet(), surface); + QMargins margins = window.frameMargins(); + tool->sendMotion(QPointF(12 + margins.left(), 34 + margins.top())); + tool->sendFrame(); + }); + + QTRY_COMPARE(filter.numEvents(), 1); + QTabletEvent *event = filter.popEvent(); + QCOMPARE(event->pointingDevice()->uniqueId(), uid); + + QTRY_VERIFY(window.numEvents()); + event = window.popEvent(); + QCOMPARE(event->pointingDevice()->uniqueId(), uid); + + exec([&] { + tabletTool()->sendProximityOut(); + tabletTool()->sendFrame(); + }); + + QTRY_VERIFY(filter.numEvents()); + event = filter.popEvent(); + QCOMPARE(event->pointingDevice()->uniqueId(), uid); +} + +// As defined in linux/input-event-codes.h +#ifndef BTN_STYLUS +#define BTN_STYLUS 0x14b +#endif +#ifndef BTN_STYLUS2 +#define BTN_STYLUS2 0x14c +#endif + +void tst_tabletv2::buttons_data() +{ + QTest::addColumn("tabletButton"); + QTest::addColumn("mouseButton"); + + QTest::newRow("BTN_STYLUS2") << uint(BTN_STYLUS2) << Qt::MouseButton::RightButton; + QTest::newRow("BTN_STYLUS") << uint(BTN_STYLUS) << Qt::MouseButton::MiddleButton; +} + +void tst_tabletv2::buttons() +{ + QFETCH(uint, tabletButton); + QFETCH(Qt::MouseButton, mouseButton); + + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(); + }); + + TabletWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + tabletTool()->sendProximityIn(tablet(), xdgSurface()->m_surface); + QMargins margins = window.frameMargins(); + tabletTool()->sendMotion(QPointF(12 + margins.left(), 34 + margins.top())); + tabletTool()->sendFrame(); + }); + + QTRY_VERIFY(window.numEvents()); + window.popEvent(); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + tabletTool()->sendButton(tabletButton, true); + tabletTool()->sendFrame(); + tabletTool()->sendButton(tabletButton, false); + tabletTool()->sendFrame(); + }); + + QTRY_VERIFY(window.numEvents()); + QTabletEvent *event = window.popEvent(); + QCOMPARE(event->buttons(), mouseButton); + + exec([&] { + tabletTool()->sendProximityOut(); + tabletTool()->sendFrame(); + }); +} + +void tst_tabletv2::tabletEvents() +{ + QCOMPOSITOR_TRY_VERIFY(tabletSeat()); + exec([&] { + tabletSeat()->addTablet(); + tabletSeat()->addTool(); + }); + + TabletWindow window; + window.resize(64, 64); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + const QPointF insideDecorations(window.frameMargins().left(), window.frameMargins().top()); + + QCOMPOSITOR_TRY_VERIFY(tablet()); + exec([&] { + auto *surface = xdgSurface()->m_surface; + auto *tool = tabletTool(); + // TODO: encapsulate this into a helper function? + tool->sendProximityIn(tablet(), surface); + tool->sendMotion(QPointF(12, 34) + insideDecorations); + tool->sendDown(); + tool->sendPressure(65535); + tool->sendFrame(); + }); + + QTRY_VERIFY(window.numEvents()); + QTabletEvent *event = window.popEvent(); + QCOMPARE(event->type(), QEvent::TabletPress); + QCOMPARE(event->pressure(), 1.0); + QCOMPARE(event->position(), QPointF(12, 34)); + + // Values we didn't send should be 0 + QCOMPARE(event->rotation(), 0); + QCOMPARE(event->xTilt(), 0); + QCOMPARE(event->yTilt(), 0); + + exec([&] { + tabletTool()->sendMotion(QPointF(45, 56) + insideDecorations); + tabletTool()->sendPressure(65535/2); + tabletTool()->sendRotation(90); + tabletTool()->sendTilt(13, 37); + tabletTool()->sendFrame(); + }); + + QTRY_VERIFY(window.numEvents()); + event = window.popEvent(); + QCOMPARE(event->type(), QEvent::TabletMove); + QVERIFY(qAbs(event->pressure() - 0.5) < 0.01); + QVERIFY(qAbs(event->rotation() - 90) < 0.01); + QVERIFY(qAbs(event->xTilt() - 13) < 0.01); + QVERIFY(qAbs(event->yTilt() - 37) < 0.01); + QCOMPARE(event->position(), QPointF(45, 56)); + + // Verify that the values stay the same if we don't update them + exec([&] { + tabletTool()->sendMotion(QPointF(10, 11) + insideDecorations); // Change position only + tabletTool()->sendFrame(); + }); + QTRY_VERIFY(window.numEvents()); + event = window.popEvent(); + QCOMPARE(event->type(), QEvent::TabletMove); + QVERIFY(qAbs(event->pressure() - 0.5) < 0.01); + QVERIFY(qAbs(event->rotation() - 90) < 0.01); + QVERIFY(qAbs(event->xTilt() - 13) < 0.01); + QVERIFY(qAbs(event->yTilt() - 37) < 0.01); + QCOMPARE(event->position(), QPointF(10, 11)); + + exec([&] { + tabletTool()->sendPressure(0); + tabletTool()->sendUp(); + tabletTool()->sendFrame(); + + tabletTool()->sendProximityOut(); + tabletTool()->sendFrame(); + }); + + QTRY_VERIFY(window.numEvents()); + event = window.popEvent(); + QCOMPARE(event->type(), QEvent::TabletRelease); + QCOMPARE(event->pressure(), 0); + QCOMPARE(event->position(), QPointF(10, 11)); +} + +QCOMPOSITOR_TEST_MAIN(tst_tabletv2) +#include "tst_tabletv2.moc" diff --git a/tests/auto/wayland/wl_connect/CMakeLists.txt b/tests/auto/wayland/wl_connect/CMakeLists.txt new file mode 100644 index 00000000000..fff3835bbaa --- /dev/null +++ b/tests/auto/wayland/wl_connect/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from wl_connect.pro. + +##################################################################### +## tst_wlconnect Test: +##################################################################### + +qt_internal_add_test(tst_wlconnect + SOURCES + tst_wlconnect.cpp + LIBRARIES + Qt::Gui + Qt::GuiPrivate +) diff --git a/tests/auto/wayland/wl_connect/tst_wlconnect.cpp b/tests/auto/wayland/wl_connect/tst_wlconnect.cpp new file mode 100644 index 00000000000..d66d0ad6a4a --- /dev/null +++ b/tests/auto/wayland/wl_connect/tst_wlconnect.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include +#include +#include + +class tst_WlConnect : public QObject +{ + Q_OBJECT +private slots: + void failsGracefully() + { + // This tests whether the Wayland platform integration will fail gracefully when it's + // unable to connect to a compositor + + // Make sure the connection actually fails + setenv("XDG_RUNTIME_DIR", "/dev/null", 1); // a place where there are no Wayland sockets + setenv("WAYLAND_DISPLAY", "qt_invalid_socket", 1); // just to be sure + + QStringList arguments; + QString platformPluginPath; + int argc = 0; + char **argv = nullptr; //It's not currently used by the wayland plugin + auto *platformIntegration = QPlatformIntegrationFactory::create("wayland", arguments, argc, argv, platformPluginPath); + + // The factory method should return nullptr to signify it failed gracefully + Q_ASSERT(!platformIntegration); + } +}; + +QTEST_APPLESS_MAIN(tst_WlConnect) +#include diff --git a/tests/auto/wayland/xdgdecorationv1/CMakeLists.txt b/tests/auto/wayland/xdgdecorationv1/CMakeLists.txt new file mode 100644 index 00000000000..0f727aaca27 --- /dev/null +++ b/tests/auto/wayland/xdgdecorationv1/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from xdgdecorationv1.pro. + +##################################################################### +## tst_xdgdecorationv1 Test: +##################################################################### + +qt_internal_add_test(tst_xdgdecorationv1 + SOURCES + tst_xdgdecorationv1.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp b/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp new file mode 100644 index 00000000000..5ee85694406 --- /dev/null +++ b/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp @@ -0,0 +1,198 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" + +#include + +#include +#include +#include + +#include + +using namespace MockCompositor; + +constexpr int xdgDecorationVersion = 1; // protocol VERSION, not the name suffix (_v1) + +class XdgDecorationManagerV1; +class XdgToplevelDecorationV1 : public QObject, public QtWaylandServer::zxdg_toplevel_decoration_v1 +{ + Q_OBJECT +public: + explicit XdgToplevelDecorationV1(XdgDecorationManagerV1 *manager, XdgToplevel *toplevel, int id, int version) + : zxdg_toplevel_decoration_v1(toplevel->resource()->client(), id, version) + , m_manager(manager) + , m_toplevel(toplevel) + { + } + void sendConfigure(mode mode) + { + if (!m_configureSent) { + // Attaching buffers before the configure is a protocol error + QVERIFY(!m_toplevel->surface()->m_pending.buffer); + QVERIFY(!m_toplevel->surface()->m_committed.buffer); + } + send_configure(mode); + m_configureSent = true; + } + void zxdg_toplevel_decoration_v1_destroy(Resource *resource) override + { + wl_resource_destroy(resource->handle); + } + void zxdg_toplevel_decoration_v1_destroy_resource(Resource *resource) override; + void zxdg_toplevel_decoration_v1_set_mode(Resource *resource, uint32_t mode) override + { + Q_UNUSED(resource); + m_unsetModeRequested = false; + m_requestedMode = XdgToplevelDecorationV1::mode(mode); + } + void zxdg_toplevel_decoration_v1_unset_mode(Resource *resource) override + { + Q_UNUSED(resource); + m_unsetModeRequested = true; + m_requestedMode = mode(0); + } + XdgDecorationManagerV1 *m_manager = nullptr; + XdgToplevel *m_toplevel = nullptr; + mode m_requestedMode = mode(0); + bool m_unsetModeRequested = false; + bool m_configureSent = false; +}; + +class XdgDecorationManagerV1 : public Global, public QtWaylandServer::zxdg_decoration_manager_v1 +{ + Q_OBJECT +public: + explicit XdgDecorationManagerV1(CoreCompositor *compositor, int version = 1) + : QtWaylandServer::zxdg_decoration_manager_v1(compositor->m_display, version) + , m_version(version) + {} + bool isClean() override { return m_decorations.empty(); } + XdgToplevelDecorationV1 *decorationFor(XdgToplevel *toplevel) + { + return m_decorations.value(toplevel, nullptr); + } + + int m_version = 1; // TODO: Remove on libwayland upgrade + QMap m_decorations; + +protected: + void zxdg_decoration_manager_v1_destroy(Resource *resource) override + { + //TODO: Should the decorations be destroyed at this point? + wl_resource_destroy(resource->handle); + } + + void zxdg_decoration_manager_v1_get_toplevel_decoration(Resource *resource, uint32_t id, ::wl_resource *toplevelResource) override + { + auto *toplevel = fromResource(toplevelResource); + QVERIFY(toplevel); + QVERIFY(!decorationFor(toplevel)); + + // Attaching buffers before the configure is a protocol error + QVERIFY(!toplevel->surface()->m_pending.buffer); + QVERIFY(!toplevel->surface()->m_committed.buffer); + + m_decorations[toplevel] = new XdgToplevelDecorationV1(this, toplevel, id, resource->version()); + } +}; + +void XdgToplevelDecorationV1::zxdg_toplevel_decoration_v1_destroy_resource(QtWaylandServer::zxdg_toplevel_decoration_v1::Resource *resource) +{ + Q_UNUSED(resource); + int removed = m_manager->m_decorations.remove(m_toplevel); + Q_ASSERT(removed == 1); + delete this; +} + +class XdgDecorationCompositor : public DefaultCompositor { +public: + explicit XdgDecorationCompositor() + { + exec([this] { + m_config.autoConfigure = false; + add(xdgDecorationVersion); + }); + } + XdgToplevelDecorationV1 *toplevelDecoration(int i = 0) { + return get()->decorationFor(xdgToplevel(i)); + } +}; + +class tst_xdgdecorationv1 : public QObject, private XdgDecorationCompositor +{ + Q_OBJECT +private slots: + void initTestCase(); + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void clientSidePreferredByCompositor(); + void initialFramelessWindowHint(); + void delayedFramelessWindowHint(); +}; + +void tst_xdgdecorationv1::initTestCase() +{ + if (qEnvironmentVariableIntValue("QT_WAYLAND_DISABLE_WINDOWDECORATION")) + QSKIP("This test doesn't make sense when QT_WAYLAND_DISABLE_WINDOWDECORATION is set in the environment"); +} + +void tst_xdgdecorationv1::clientSidePreferredByCompositor() +{ + QRasterWindow window; + window.show(); + QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().first()->version(), xdgDecorationVersion); + QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()); // The client creates a toplevel object + + // Check that we don't assume decorations before the server has configured them + QVERIFY(window.frameMargins().isNull()); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()->m_unsetModeRequested); + QVERIFY(window.frameMargins().isNull()); // We're still waiting for a configure + exec([&] { + toplevelDecoration()->sendConfigure(XdgToplevelDecorationV1::mode_client_side); + xdgToplevel()->sendCompleteConfigure(); + }); + QTRY_VERIFY(!window.frameMargins().isNull()); +} + +void tst_xdgdecorationv1::initialFramelessWindowHint() +{ + QRasterWindow window; + window.setFlag(Qt::FramelessWindowHint, true); + window.show(); + QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&]{ + xdgToplevel()->sendCompleteConfigure(); + }); + QCOMPOSITOR_TRY_VERIFY(xdgSurface()->m_committedConfigureSerial); + + // The client should not have create a decoration object, because that allows the compositor + // to override our decision and add server side decorations to our window. + QCOMPOSITOR_TRY_VERIFY(!toplevelDecoration()); +} + +void tst_xdgdecorationv1::delayedFramelessWindowHint() +{ + QRasterWindow window; + window.show(); + QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().size(), 1); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&]{ + xdgToplevel()->sendCompleteConfigure(); + }); + QCOMPOSITOR_TRY_VERIFY(xdgSurface()->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()); + + window.setFlag(Qt::FramelessWindowHint, true); + + // The client should now destroy the decoration object, so the compositor is no longer + // able to force window decorations + QCOMPOSITOR_TRY_VERIFY(!toplevelDecoration()); +} + +QCOMPOSITOR_TEST_MAIN(tst_xdgdecorationv1) +#include "tst_xdgdecorationv1.moc" diff --git a/tests/auto/wayland/xdgoutput/CMakeLists.txt b/tests/auto/wayland/xdgoutput/CMakeLists.txt new file mode 100644 index 00000000000..123a78f8e51 --- /dev/null +++ b/tests/auto/wayland/xdgoutput/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from xdgoutput.pro. + +##################################################################### +## tst_xdgoutput Test: +##################################################################### + +qt_internal_add_test(tst_xdgoutput + SOURCES + tst_xdgoutput.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/xdgoutput/tst_xdgoutput.cpp b/tests/auto/wayland/xdgoutput/tst_xdgoutput.cpp new file mode 100644 index 00000000000..d021e388316 --- /dev/null +++ b/tests/auto/wayland/xdgoutput/tst_xdgoutput.cpp @@ -0,0 +1,147 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "xdgoutputv1.h" +#include "mockcompositor.h" + +#include +#include + +using namespace MockCompositor; + +class XdgOutputV1Compositor : public DefaultCompositor { +public: + explicit XdgOutputV1Compositor() + { + exec([this] { + int version = 3; // version 3 of of unstable-v1 + add(version); + }); + } + XdgOutputV1 *xdgOutput(int i = 0) { return get()->getXdgOutput(output(i)); } +}; + +class tst_xdgoutput : public QObject, private XdgOutputV1Compositor +{ + Q_OBJECT +private slots: + void cleanup(); + void primaryScreen(); + void overrideGeometry(); + void changeGeometry(); + void outputCreateEnterRace(); +}; + +void tst_xdgoutput::cleanup() +{ + QCOMPOSITOR_COMPARE(getAll().size(), 1); // Only the default output should be left + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); +} + +void tst_xdgoutput::primaryScreen() +{ + // Verify that the client has bound to the global + QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().size(), 1); + exec([&] { + auto *resource = xdgOutput()->resourceMap().value(client()); + QCOMPARE(resource->version(), 3); + QCOMPARE(xdgOutput()->m_logicalGeometry.size(), QSize(1920, 1080)); + }); + auto *s = QGuiApplication::primaryScreen(); + QTRY_COMPARE(s->size(), QSize(1920, 1080)); + QTRY_COMPARE(s->geometry().topLeft(), QPoint(0, 0)); + QTRY_COMPARE(s->name(), QString("WL-1")); +} + +void tst_xdgoutput::overrideGeometry() +{ + exec([&] { + auto *output = add(); + auto *xdgOutput = get()->getXdgOutput(output); + xdgOutput->m_logicalGeometry = QRect(10, 20, 800, 1200); + }); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + auto *s = QGuiApplication::screens()[1]; + + QTRY_COMPARE(s->size(), QSize(800, 1200)); + QTRY_COMPARE(s->geometry().topLeft(), QPoint(10, 20)); + + exec([&] { remove(output(1)); }); +} + +void tst_xdgoutput::changeGeometry() +{ + auto *xdgOutput = exec([&] { + auto *output = add(); + auto *xdgOutput = get()->getXdgOutput(output); + xdgOutput->m_logicalGeometry = QRect(10, 20, 800, 1200); + return xdgOutput; + }); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + auto *screen = QGuiApplication::screens()[1]; + QTRY_COMPARE(screen->size(), QSize(800, 1200)); + + exec([&] { + xdgOutput->sendLogicalSize(QSize(1024, 768)); + }); + + // Now we want to check that the client doesn't apply the size immediately, but waits for the + // done event. If we TRY_COMPARE immediately, we risk that the client just hasn't handled the + // logical_size request yet, so we add a screen and verify it on the client side just to give + // the client a chance to mess up. + exec([&] { add(); }); + QTRY_COMPARE(QGuiApplication::screens().size(), 3); + exec([&] { remove(output(2)); }); + + // The logical_size event should have been handled by now, but state should not have been applied yet. + QTRY_COMPARE(screen->size(), QSize(800, 1200)); + + exec([&] { + xdgOutput->m_output->sendDone(); + }); + + // Finally, the size should change + QTRY_COMPARE(screen->size(), QSize(1024, 768)); + + exec([&] { remove(output(1)); }); +} + +void tst_xdgoutput::outputCreateEnterRace() +{ + m_config.autoConfigure = true; + m_config.autoEnter = false; + QRasterWindow window; + QSignalSpy screenChanged(&window, &QWindow::screenChanged); + window.resize(400, 320); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([&] { xdgToplevel()->surface()->sendEnter(output(0));}); + + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QScreen *primaryScreen = QGuiApplication::screens().first(); + QCOMPARE(window.screen(), primaryScreen); + + auto *out = exec([&] { + return add(); + }); + + // In Compositor Thread + connect(out, &Output::outputBound, this, [this](QtWaylandServer::wl_output::Resource *resource){ + auto surface = xdgToplevel()->surface(); + surface->sendLeave(output(0)); + surface->QtWaylandServer::wl_surface::send_enter(surface->resource()->handle, resource->handle); + }, Qt::DirectConnection); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + QTRY_COMPARE(window.screen(), QGuiApplication::screens()[1]); + + exec([&] { remove(out); }); + m_config.autoConfigure = false; + m_config.autoEnter = true; +} + +QCOMPOSITOR_TEST_MAIN(tst_xdgoutput) +#include "tst_xdgoutput.moc" diff --git a/tests/auto/wayland/xdgshell/CMakeLists.txt b/tests/auto/wayland/xdgshell/CMakeLists.txt new file mode 100644 index 00000000000..fa759024954 --- /dev/null +++ b/tests/auto/wayland/xdgshell/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Generated from xdgshell.pro. + +##################################################################### +## tst_xdgshell Test: +##################################################################### + +qt_internal_add_test(tst_xdgshell + SOURCES + tst_xdgshell.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/xdgshell/tst_xdgshell.cpp b/tests/auto/wayland/xdgshell/tst_xdgshell.cpp new file mode 100644 index 00000000000..e23cc83f5b6 --- /dev/null +++ b/tests/auto/wayland/xdgshell/tst_xdgshell.cpp @@ -0,0 +1,898 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// Copyright (C) 2023 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "mockcompositor.h" +#include +#include +#include +#include + +using namespace MockCompositor; + +class tst_xdgshell : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void init(); + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void showMinimized(); + void basicConfigure(); + void configureSize(); + void configureStates(); + void configureBounds(); + void popup(); + void tooltipOnPopup(); + void tooltipAndSiblingPopup(); + void switchPopups(); + void hidePopupParent(); + void popupsWithoutParent(); + void pongs(); + void minMaxSize_data(); + void minMaxSize(); + void windowGeometry(); + void foreignSurface(); + void nativeResources(); + void suspended(); + void initiallySuspended(); + void modality(); + void modalityWithoutTransientParent(); +}; + +void tst_xdgshell::init() +{ + setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1); +} + +void tst_xdgshell::showMinimized() +{ + // On xdg-shell there's really no way for the compositor to tell the window if it's minimized + // There are wl_surface.enter events and so on, but there's really no way to differentiate + // between a window preview and an unminimized window. + QWindow window; + window.showMinimized(); + QTRY_COMPARE(window.windowStates(), Qt::WindowNoState); // rejected by handleWindowStateChanged + + // Make sure the window on the compositor side is/was created here, and not after the test + // finishes, as that may mess up for later tests. + QCOMPOSITOR_TRY_VERIFY(xdgSurface()); + QVERIFY(!window.isExposed()); +} + +void tst_xdgshell::basicConfigure() +{ + QRasterWindow window; + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QSignalSpy configureSpy(exec([&] { return xdgSurface(); }), &XdgSurface::configureCommitted); + + QTRY_VERIFY(window.isVisible()); + // The window should not be exposed before the first xdg_surface configure event + QTRY_VERIFY(!window.isExposed()); + + exec([&] { + xdgToplevel()->sendConfigure({0, 0}, {}); // Let the window decide the size + }); + + // Nothing should happen before the *xdg_surface* configure + QTRY_VERIFY(!window.isExposed()); //Window should not be exposed before the first configure event + QVERIFY(configureSpy.isEmpty()); + + const uint serial = exec([&] { return nextSerial(); }); + + exec([&] { + xdgSurface()->sendConfigure(serial); + }); + + // Finally, we're exposed + QTRY_VERIFY(window.isExposed()); + + // The client is now going to ack the configure + QTRY_COMPARE(configureSpy.size(), 1); + QCOMPARE(configureSpy.takeFirst().at(0).toUInt(), serial); + + // And attach a buffer + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), window.frameGeometry().size()); + }); +} + +void tst_xdgshell::configureSize() +{ + QRasterWindow window; + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QSignalSpy configureSpy(exec([&] { return xdgSurface(); }), &XdgSurface::configureCommitted); + + const QSize configureSize(60, 40); + + int pendingSerial; + exec([&] { + xdgToplevel()->sendCompleteConfigure(configureSize); + pendingSerial = xdgSurface()->m_pendingConfigureSerials.last(); + }); + + QTRY_COMPARE(configureSpy.size(), 1); + QCOMPARE(configureSpy.last()[0].toInt(), pendingSerial); + + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), configureSize); + }); + + // clients should always respond with a new ack configure + commit + // even if nothing changed + exec([&] { + xdgToplevel()->sendCompleteConfigure(configureSize); + pendingSerial = xdgSurface()->m_pendingConfigureSerials.last(); + }); + + QTRY_COMPARE(configureSpy.size(), 2); + QCOMPARE(configureSpy.last()[0].toInt(), pendingSerial); + + exec([&] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), configureSize); + }); +} + +void tst_xdgshell::configureStates() +{ + QVERIFY(qputenv("QT_WAYLAND_FRAME_CALLBACK_TIMEOUT", "0")); + QRasterWindow window; + window.resize(64, 48); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + const QSize windowedSize(320, 240); + const uint windowedSerial = exec([&] { + return xdgToplevel()->sendCompleteConfigure(windowedSize, { XdgToplevel::state_activated }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, windowedSerial); + QCOMPARE(window.visibility(), QWindow::Windowed); + QCOMPARE(window.windowStates(), Qt::WindowNoState); + QCOMPARE(window.frameGeometry().size(), windowedSize); + // Toplevel windows don't know their position on xdg-shell +// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + + // window.windowstate() is driven by keyboard focus, however for decorations we want to follow + // XDGShell this is internal to QtWayland so it is queried directly + auto waylandWindow = static_cast(window.handle()); + Q_ASSERT(waylandWindow); + QTRY_VERIFY(waylandWindow->windowStates().testFlag( + Qt::WindowActive)); // Just make sure it eventually get's set correctly + + const QSize screenSize(640, 480); + const uint maximizedSerial = exec([&] { + return xdgToplevel()->sendCompleteConfigure(screenSize, { XdgToplevel::state_activated, XdgToplevel::state_maximized }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, maximizedSerial); + QCOMPARE(window.visibility(), QWindow::Maximized); + QCOMPARE(window.windowStates(), Qt::WindowMaximized); + QCOMPARE(window.frameGeometry().size(), screenSize); +// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + + const uint fullscreenSerial = exec([&] { + return xdgToplevel()->sendCompleteConfigure(screenSize, { XdgToplevel::state_activated, XdgToplevel::state_fullscreen }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, fullscreenSerial); + QCOMPARE(window.visibility(), QWindow::FullScreen); + QCOMPARE(window.windowStates(), Qt::WindowFullScreen); + QCOMPARE(window.frameGeometry().size(), screenSize); +// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + + // The window should remember its original size + const uint restoreSerial = exec([&] { + return xdgToplevel()->sendCompleteConfigure({0, 0}, { XdgToplevel::state_activated }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, restoreSerial); + QCOMPARE(window.visibility(), QWindow::Windowed); + QCOMPARE(window.windowStates(), Qt::WindowNoState); + QCOMPARE(window.frameGeometry().size(), windowedSize); +// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + QVERIFY(qunsetenv("QT_WAYLAND_FRAME_CALLBACK_TIMEOUT")); +} + +void tst_xdgshell::configureBounds() +{ + QRasterWindow window; + window.resize(1280, 1024); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + // Take xdg_toplevel.configure_bounds into account only if the configure event has 0x0 size. + const uint serial1 = exec([&] { + xdgToplevel()->sendConfigureBounds(QSize(800, 600)); + return xdgToplevel()->sendCompleteConfigure(QSize(0, 0), { XdgToplevel::state_activated }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, serial1); + QCOMPARE(window.frameGeometry().size(), QSize(800, 600)); + + // Window size in xdg_toplevel configure events takes precedence over the configure bounds. + const uint serial2 = exec([&] { + xdgToplevel()->sendConfigureBounds(QSize(800, 600)); + return xdgToplevel()->sendCompleteConfigure(QSize(1600, 900), { XdgToplevel::state_activated }); + }); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committedConfigureSerial, serial2); + QCOMPARE(window.frameGeometry().size(), QSize(1600, 900)); +} + +void tst_xdgshell::popup() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override + { + QRasterWindow::mousePressEvent(event); + m_popup.reset(new QRasterWindow); + m_popup->setTransientParent(this); + m_popup->setFlags(Qt::Popup); + m_popup->resize(100, 100); + m_popup->show(); + } + QScopedPointer m_popup; + }; + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + QSignalSpy toplevelConfigureSpy(exec([&] { return xdgSurface(); }), &XdgSurface::configureCommitted); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QTRY_COMPARE(toplevelConfigureSpy.size(), 1); + + uint clickSerial = exec([&] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + uint serial = p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(c, BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + return serial; + }); + + QTRY_VERIFY(window.m_popup); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + QSignalSpy popupConfigureSpy(exec([&] { return xdgPopup()->m_xdgSurface; }), &XdgSurface::configureCommitted); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_grabbed); + QCOMPOSITOR_TRY_COMPARE(xdgPopup()->m_grabSerial, clickSerial); + + QRasterWindow *popup = window.m_popup.get(); + QVERIFY(!popup->isExposed()); // wait for configure + + QRect rect1 = QRect(100, 100, 100, 100); + exec([&] { xdgPopup()->sendConfigure(rect1); }); + + // Nothing should happen before the *xdg_surface* configure + QTRY_VERIFY(!popup->isExposed()); // Popup shouldn't be exposed before the first configure event + QVERIFY(popupConfigureSpy.isEmpty()); + + const uint configureSerial = exec([&] { + return xdgPopup()->m_xdgSurface->sendConfigure(); + }); + + // Finally, we're exposed + QTRY_VERIFY(popup->isExposed()); + + // The client is now going to ack the configure + QTRY_COMPARE(popupConfigureSpy.size(), 1); + QCOMPARE(popupConfigureSpy.takeFirst().at(0).toUInt(), configureSerial); + QCOMPARE(popup->geometry(), rect1); + + QRect rect2 = QRect(50, 50, 150, 150); + exec([&] { xdgPopup()->sendConfigure(rect2); }); + + const uint configureSerial2 = exec([&] { + return xdgPopup()->m_xdgSurface->sendConfigure(); + }); + + QTRY_COMPARE(popupConfigureSpy.size(), 1); + QCOMPARE(popupConfigureSpy.takeFirst().at(0).toUInt(), configureSerial2); + QCOMPARE(popup->geometry(), rect2); + + // And attach a buffer + exec([&] { + Buffer *buffer = xdgPopup()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), popup->frameGeometry().size()); + }); +} + +void tst_xdgshell::tooltipOnPopup() +{ + class Popup : public QRasterWindow { + public: + explicit Popup(QWindow *parent) { + setTransientParent(parent); + setFlags(Qt::Popup); + resize(100, 100); + show(); + } + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + m_tooltip = new QRasterWindow; + m_tooltip->setTransientParent(this); + m_tooltip->setFlags(Qt::ToolTip); + m_tooltip->resize(100, 100); + m_tooltip->show(); + } + QWindow *m_tooltip = nullptr; + }; + + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + m_popup = new Popup(this); + } + Popup *m_popup = nullptr; + }; + + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + p->sendLeave(surface); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + exec([&] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_grabbed); + + exec([&] { + auto *surface = xdgPopup()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)); + exec([&] { xdgPopup(1)->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(!xdgPopup(1)->m_grabbed); + + // Close the middle popup (according protocol this should also destroy any nested popups) + window.m_popup->close(); + + // Close order is verified in XdgSurface::xdg_surface_destroy + + QCOMPOSITOR_TRY_COMPARE(xdgPopup(1), nullptr); + QCOMPOSITOR_TRY_COMPARE(xdgPopup(0), nullptr); +} + +void tst_xdgshell::tooltipAndSiblingPopup() +{ + class ToolTip : public QRasterWindow { + public: + explicit ToolTip(QWindow *parent) { + setTransientParent(parent); + setFlags(Qt::ToolTip); + resize(100, 100); + show(); + } + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + m_popup = new QRasterWindow; + m_popup->setTransientParent(transientParent()); + m_popup->setFlags(Qt::Popup); + m_popup->resize(100, 100); + m_popup->show(); + } + + QRasterWindow *m_popup = nullptr; + }; + + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + m_tooltip = new ToolTip(this); + } + ToolTip *m_tooltip = nullptr; + }; + + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + p->sendLeave(surface); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + exec([&] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(!xdgPopup()->m_grabbed); + + exec([&] { + auto *surface = xdgPopup()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)); + exec([&] { xdgPopup(1)->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)->m_grabbed); + + // Close the middle tooltip (it should not close the sibling popup) + window.m_tooltip->close(); + + QCOMPOSITOR_TRY_COMPARE(xdgPopup(1), nullptr); + // Verify the remaining xdg surface is a grab popup.. + QCOMPOSITOR_TRY_VERIFY(xdgPopup(0)); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(0)->m_grabbed); + + window.m_tooltip->m_popup->close(); + QCOMPOSITOR_TRY_COMPARE(xdgPopup(1), nullptr); + QCOMPOSITOR_TRY_COMPARE(xdgPopup(0), nullptr); +} + +// QTBUG-65680 +void tst_xdgshell::switchPopups() +{ + class Popup : public QRasterWindow { + public: + explicit Popup(QWindow *parent) { + setTransientParent(parent); + setFlags(Qt::Popup); + resize(10, 10); + show(); + } + }; + + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + if (!m_popups.empty()) + m_popups.last()->setVisible(false); + } + // The bug this checks for, is the case where there is a flushWindowSystemEvents() call + // somewhere within setVisible(false) before the grab has been released. + // This leads to the the release event below—including its show() call—to be handled + // At a time where there is still an active grab, making it illegal for the new popup to + // grab. + void mouseReleaseEvent(QMouseEvent *event) override { + QRasterWindow::mouseReleaseEvent(event); + m_popups << new Popup(this); + } + ~Window() override { qDeleteAll(m_popups); } + QList m_popups; + }; + + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(c, BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(c, BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + p->sendLeave(surface); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + exec([&] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_grabbed); + + QSignalSpy firstDestroyed(exec([&] { return xdgPopup(); }), &XdgPopup::destroyRequested); + + exec([&] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(c, BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(c, BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + }); + + // The client will now hide one popup and then show another + firstDestroyed.wait(); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + QCOMPOSITOR_TRY_VERIFY(!xdgPopup(1)); + + // Verify we still grabbed in case the client has checks to avoid illegal grabs + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_grabbed); + + // Sometimes the popup will select another parent if one is illegal at the time it tries to + // grab. So we verify we got the intended parent on the compositor side. + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_parentXdgSurface == xdgToplevel()->m_xdgSurface); + + // For good measure just check that configuring works as usual + exec([&] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); +} + +void tst_xdgshell::hidePopupParent() +{ + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override + { + QRasterWindow::mousePressEvent(event); + m_popup.reset(new QRasterWindow); + m_popup->setTransientParent(this); + m_popup->setFlags(Qt::Popup); + m_popup->resize(100, 100); + m_popup->show(); + } + QScopedPointer m_popup; + }; + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + + exec([&] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(c, BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(c, BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + exec([&] { + xdgPopup()->sendConfigure(QRect(100, 100, 100, 100)); + xdgPopup()->m_xdgSurface->sendConfigure(); + }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); + + window.hide(); + QCOMPOSITOR_TRY_VERIFY(!xdgToplevel()); +} + +void tst_xdgshell::popupsWithoutParent() +{ + QRasterWindow popup; + QSignalSpy popupDoneSpy(&popup, &QWindow::visibilityChanged); + popup.setFlags(Qt::Popup); + popup.resize(100, 100); + popup.show(); + QVERIFY(popup.isVisible()); + + // popup cannot be created within the spec, so it gets auto-dismissed + QVERIFY(popupDoneSpy.wait()); + QVERIFY(!popup.isVisible()); + + QCOMPOSITOR_VERIFY(!xdgToplevel()); + + // now make a normal window with an input event + QRasterWindow window; + window.setTitle("main window"); + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + exec([&] { + keyboard()->sendEnter(xdgToplevel()->surface()); + keyboard()->sendKey(client(), 72, Keyboard::key_state_pressed); // related with native scan code + keyboard()->sendKey(client(), 72, Keyboard::key_state_released); // related with native scan code + }); + QTRY_COMPARE(qGuiApp->focusWindow(), &window); + + // now re-show our popup, it should be able to guess a transient this time + // and correctly show as a popup + popup.show(); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + exec([&] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); +} + +void tst_xdgshell::pongs() +{ + // Create and show a window to trigger shell integration initialzation, + // otherwise we don't have anything to send ping events to. + QRasterWindow window; + window.resize(200, 200); + window.show(); + + // Verify that the client has bound to the global + QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().size(), 1); + + QSignalSpy pongSpy(exec([&] { return get(); }), &XdgWmBase::pong); + const uint serial = exec([&] { return nextSerial(); }); + exec([&] { + auto *base = get(); + wl_resource *resource = base->resourceMap().first()->handle; + base->send_ping(resource, serial); + }); + QTRY_COMPARE(pongSpy.size(), 1); + QCOMPARE(pongSpy.first().at(0).toUInt(), serial); +} + +void tst_xdgshell::minMaxSize_data() +{ + QTest::addColumn("initialMinSize"); + QTest::addColumn("initialMaxSize"); + QTest::addColumn("nextMinSize"); + QTest::addColumn("nextMaxSize"); + QTest::addColumn("expectedInitialMinSize"); + QTest::addColumn("expectedInitialMaxSize"); + QTest::addColumn("expectedNextMinSize"); + QTest::addColumn("expectedNextMaxSize"); + + QTest::newRow("onlyMinSize") << QSize(50, 60) << QSize() << QSize(500, 600) << QSize() + << QSize(50, 60) << QSize(0, 0) << QSize(500, 600) << QSize(0, 0); + + QTest::newRow("onlyMaxSize") << QSize() << QSize(70, 80) << QSize() << QSize(700, 800) + << QSize(0,0 ) << QSize(70, 80) << QSize(0, 0) << QSize(700, 800); + + QTest::newRow("maxIsSentAsZero") << QSize() << QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX) << QSize() << QSize() + << QSize(0,0 ) << QSize(0, 0) << QSize(0, 0) << QSize(0, 0); + + + QTest::newRow("fullHints") << QSize(50, 60) << QSize(700, 800) << QSize(500, 600) << QSize(710, 810) + << QSize(50, 60) << QSize(700, 800) << QSize(500, 600) << QSize(710, 810); + + // setting a minimum above the maximum is not allowed, we should no-op + QTest::newRow("invalidResize") << QSize(50, 60) << QSize(100, 100) << QSize(500, 600) << QSize(100, 100) + << QSize(50, 60) << QSize(100, 100) << QSize(50, 60) << QSize(100, 100);} + +void tst_xdgshell::minMaxSize() +{ + QFETCH(QSize, initialMinSize); + QFETCH(QSize, initialMaxSize); + + QFETCH(QSize, expectedInitialMinSize); + QFETCH(QSize, expectedInitialMaxSize); + + QRasterWindow window; + if (initialMinSize.isValid()) + window.setMinimumSize(initialMinSize); + if (initialMaxSize.isValid()) + window.setMaximumSize(initialMaxSize); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + + // we don't roundtrip with our configuration the initial commit should be correct + + QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.minSize, expectedInitialMinSize); + QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.maxSize, expectedInitialMaxSize); + + QFETCH(QSize, nextMinSize); + QFETCH(QSize, expectedNextMinSize); + window.setMinimumSize(nextMinSize); + window.update(); + QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.minSize, expectedNextMinSize); + + QFETCH(QSize, nextMaxSize); + QFETCH(QSize, expectedNextMaxSize); + + window.setMaximumSize(nextMaxSize); + window.update(); + QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.maxSize, expectedNextMaxSize); +} + +void tst_xdgshell::windowGeometry() +{ + QRasterWindow window; + window.resize(400, 320); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + + QSize marginsSize; + marginsSize.setWidth(window.frameMargins().left() + window.frameMargins().right()); + marginsSize.setHeight(window.frameMargins().top() + window.frameMargins().bottom()); + + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committed.windowGeometry, QRect(QPoint(0, 0), QSize(400, 320) + marginsSize)); + + window.resize(800, 600); + QCOMPOSITOR_TRY_COMPARE(xdgSurface()->m_committed.windowGeometry, QRect(QPoint(0, 0), QSize(800, 600) + marginsSize)); +} + +void tst_xdgshell::foreignSurface() +{ + auto *ni = QGuiApplication::platformNativeInterface(); + auto *compositor = static_cast<::wl_compositor *>(ni->nativeResourceForIntegration("compositor")); + ::wl_surface *foreignSurface = wl_compositor_create_surface(compositor); + + // There *could* be cursor surfaces lying around, we don't want to confuse those with + // the foreign surface we will be creating. + const int newSurfaceIndex = exec([&]{ + return get()->m_surfaces.size(); + }); + + QCOMPOSITOR_TRY_VERIFY(surface(newSurfaceIndex)); + exec([&] { + pointer()->sendEnter(surface(newSurfaceIndex), {32, 32}); + pointer()->sendLeave(surface(newSurfaceIndex)); + }); + + // Just do something to make sure we don't destroy the surface before + // the pointer events above are handled. + QSignalSpy spy(exec([&] { return surface(newSurfaceIndex); }), &Surface::commit); + wl_surface_commit(foreignSurface); + QTRY_COMPARE(spy.size(), 1); + + wl_surface_destroy(foreignSurface); +} + +void tst_xdgshell::nativeResources() +{ + QRasterWindow window; + window.resize(400, 320); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + auto *ni = QGuiApplication::platformNativeInterface(); + auto *xdg_surface_proxy = static_cast<::wl_proxy *>(ni->nativeResourceForWindow("xdg_surface", &window)); + QCOMPARE(wl_proxy_get_class(xdg_surface_proxy), "xdg_surface"); + + auto *xdg_toplevel_proxy = static_cast<::wl_proxy *>(ni->nativeResourceForWindow("xdg_toplevel", &window)); + QCOMPARE(wl_proxy_get_class(xdg_toplevel_proxy), "xdg_toplevel"); + + auto *xdg_popup_proxy = static_cast<::wl_proxy *>(ni->nativeResourceForWindow("xdg_popup", &window)); + QCOMPARE(xdg_popup_proxy, nullptr); +} + +void tst_xdgshell::suspended() +{ + QRasterWindow window; + window.resize(400, 320); + window.show(); + QVERIFY(!window.isExposed()); // not exposed until we're configured + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + exec([&] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + QTRY_VERIFY(window.isExposed()); + + exec([&] { xdgToplevel()->sendCompleteConfigure(QSize(), {XdgToplevel::state_suspended}); }); + QTRY_VERIFY(!window.isExposed()); + + exec([&] { xdgToplevel()->sendCompleteConfigure(QSize(), {}); }); + QTRY_VERIFY(window.isExposed()); +} + +void tst_xdgshell::initiallySuspended() +{ + QRasterWindow window; + window.resize(400, 320); + window.show(); + QVERIFY(!window.isExposed()); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([&] { xdgToplevel()->sendCompleteConfigure(QSize(), {XdgToplevel::state_suspended}); }); + QVERIFY(!window.isExposed()); +} + +void tst_xdgshell::modality() +{ + QRasterWindow parent; + parent.resize(400, 320); + parent.show(); + + QRasterWindow child; + child.resize(400, 320); + child.setTransientParent(&parent); + child.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel(1)); + QCOMPOSITOR_VERIFY(!xdgDialog()); + + child.hide(); + child.setModality(Qt::WindowModal); + child.show(); + QCOMPOSITOR_TRY_VERIFY(xdgDialog()); + QCOMPOSITOR_VERIFY(xdgDialog()->modal); + + child.hide(); + QCOMPOSITOR_TRY_VERIFY(!xdgDialog()); + + child.setModality(Qt::ApplicationModal); + child.show(); + QCOMPOSITOR_TRY_VERIFY(xdgDialog()); + QCOMPOSITOR_VERIFY(xdgDialog()->modal); + + child.hide(); + QCOMPOSITOR_TRY_VERIFY(!xdgDialog()); + + child.show(); + child.setModality(Qt::NonModal); + QCOMPOSITOR_TRY_VERIFY(!xdgDialog()); +} + +void tst_xdgshell::modalityWithoutTransientParent() +{ + QRasterWindow child; + child.resize(400, 320); + child.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + QCOMPOSITOR_VERIFY(!xdgDialog()); + + child.hide(); + child.setModality(Qt::WindowModal); + child.show(); + QCOMPOSITOR_TRY_VERIFY(xdgDialog()); + QCOMPOSITOR_VERIFY(xdgDialog()->modal); + + child.hide(); + QCOMPOSITOR_TRY_VERIFY(!xdgDialog()); + + child.setModality(Qt::ApplicationModal); + child.show(); + QCOMPOSITOR_TRY_VERIFY(xdgDialog()); + QCOMPOSITOR_VERIFY(xdgDialog()->modal); + + child.hide(); + QCOMPOSITOR_TRY_VERIFY(!xdgDialog()); + + child.show(); + child.setModality(Qt::NonModal); + QCOMPOSITOR_TRY_VERIFY(!xdgDialog()); +} + +QCOMPOSITOR_TEST_MAIN(tst_xdgshell) +#include "tst_xdgshell.moc"