wl-mirror-0.17.0/0000755000000000000000000000000014702736000010456 5ustar00wl-mirror-0.17.0/CMakeLists.txt0000644000000000000000000000422214702736000013216 0ustar00cmake_minimum_required(VERSION 3.18) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project(wl-mirror C) # options option(INSTALL_EXAMPLE_SCRIPTS "install wl-mirror example scripts" OFF) option(INSTALL_DOCUMENTATION "install wl-mirror manual pages" OFF) option(WITH_LIBDECOR "use libdecor for window decoration" OFF) set(FORCE_WAYLAND_SCANNER_PATH "" CACHE STRING "provide a custom path for wayland-scanner") # wayland protocols needed by wl-mirror option(FORCE_SYSTEM_WL_PROTOCOLS "force use of system wayland-protocols" OFF) option(FORCE_SYSTEM_WLR_PROTOCOLS "force use of system wlr-protocols" OFF) set(WL_PROTOCOL_DIR "/usr/share/wayland-protocols/" CACHE STRING "wayland-protocols directory") set(WLR_PROTOCOL_DIR "/usr/share/wlr-protocols/" CACHE STRING "wlr-protocols directory") set(PROTOCOLS "stable/xdg-shell/xdg-shell.xml" "stable/viewporter/viewporter.xml" "staging/fractional-scale/fractional-scale-v1.xml" "unstable/xdg-output/xdg-output-unstable-v1.xml" "unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml" "unstable/wlr-export-dmabuf-unstable-v1.xml" "unstable/wlr-screencopy-unstable-v1.xml" ) # required dependencies add_subdirectory(deps) # wayland protocol wrapper generation with wayland-scanner add_subdirectory(proto) # shader file embedding add_subdirectory(glsl) # version embedding add_subdirectory(version) # manual pages add_subdirectory(man) if (${BUILD_DOCUMENTATION}) build_scdoc_man_page(wl-mirror 1) build_scdoc_man_page(wl-present 1) endif() # main target file(GLOB sources CONFIGURE_DEPENDS src/*.c) add_executable(wl-mirror ${sources}) target_compile_options(wl-mirror PRIVATE -Wall -Wextra) target_include_directories(wl-mirror PRIVATE include/) target_link_libraries(wl-mirror PRIVATE deps protocols shaders version) # installation rules include(GNUInstallDirs) install(TARGETS wl-mirror DESTINATION "${CMAKE_INSTALL_BINDIR}") if (${INSTALL_EXAMPLE_SCRIPTS}) install(PROGRAMS scripts/wl-present DESTINATION "${CMAKE_INSTALL_BINDIR}") endif() if (${INSTALL_DOCUMENTATION}) install_scdoc_man_page(wl-mirror 1) if (${INSTALL_EXAMPLE_SCRIPTS}) install_scdoc_man_page(wl-present 1) endif() endif() wl-mirror-0.17.0/LICENSE0000644000000000000000000010451514702736000011471 0ustar00 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . wl-mirror-0.17.0/README.md0000644000000000000000000002267714702736000011753 0ustar00# `wl-mirror` - a simple Wayland output mirror client `wl-mirror` attempts to provide a solution to sway's lack of output mirroring by mirroring an output onto a client surface. ## Features - Mirror an output onto a resizable window - Mirror an output onto another output by fullscreening the window - Reacts to changes in output scale (including fractional scaling) - Preserves aspect ratio - Corrects for flipped or rotated outputs - Supports custom flips or rotations - Supports mirroring custom regions of outputs - Supports receiving additional options on stdin for changing the mirrored screen or region on the fly (works best when used with [pipectl](https://github.com/Ferdi265/pipectl)) ![demo screenshot](https://user-images.githubusercontent.com/4077106/141605347-37ba690c-f885-422a-93a6-81d5a48bee13.png) ## Usage ``` usage: wl-mirror [options] options: -h, --help show this help -V, --version print version -v, --verbose enable debug logging --no-verbose disable debug logging (default) -c, --show-cursor show the cursor on the mirrored screen (default) --no-show-cursor don't show the cursor on the mirrored screen -i, --invert-colors invert colors in the mirrored screen --no-invert-colors don't invert colors in the mirrored screen (default) -f, --freeze freeze the current image on the screen --unfreeze resume the screen capture after a freeze --toggle-freeze toggle freeze state of screen capture -F, --fullscreen open wl-mirror as fullscreen --no-fullscreen open wl-mirror as a window (default) --fullscreen-output O open wl-mirror as fullscreen on output O --no-fullscreen-output open wl-mirror as fullscreen on the current output (default) -s f, --scaling fit scale to fit (default) -s c, --scaling cover scale to cover, cropping if needed -s e, --scaling exact only scale to exact multiples of the output size -s l, --scaling linear use linear scaling (default) -s n, --scaling nearest use nearest neighbor scaling -b B --backend B use a specific backend for capturing the screen -t T, --transform T apply custom transform T -r R, --region R capture custom region R --no-region capture the entire output (default) -S, --stream accept a stream of additional options on stdin --title N specify a custom title N for the mirror window backends: - auto automatically try the backends in order and use the first that works (default) - dmabuf use the wlr-export-dmabuf-unstable-v1 protocol to capture outputs - screencopy use the wlr-screencopy-unstable-v1 protocol to capture outputs transforms: transforms are specified as a dash-separated list of flips followed by a rotation flips are applied before rotations - normal no transformation - flipX, flipY flip the X or Y coordinate - 0cw, 90cw, 180cw, 270cw apply a clockwise rotation - 0ccw, 90ccw, 180ccw, 270ccw apply a counter-clockwise rotation the following transformation options are provided for compatibility with sway output transforms - flipped flip the X coordinate - 0, 90, 180, 270 apply a clockwise rotation regions: regions are specified in the format used by the slurp utility - ', x [output]' on start, the region is translated into output coordinates when the output moves, the captured region moves with it when a region is specified, the argument is optional stream mode: in stream mode, wl-mirror interprets lines on stdin as additional command line options - arguments can be quoted with single or double quotes, but every argument must be fully quoted or fully unquoted - unquoted arguments are split on whitespace - no escape sequences are implemented title placeholders: the title string supports the following placeholders: - {width}, {height}: size of the mirrored area - {x}, {y}: offsets on the screen - {target_width}, {target_height}\n"); {target_output}: info about the mirrored device a few perhaps useful examples: --title='Wayland Mirror Output {target_output}' --title='{target_output}:{width}x{height}+{x}+{y}' --title='resize set {width} {height} move position {x} {y}' ``` The [`scripts/`](scripts/) folder contains examples on how `wl-mirror` can be used. - [`wl-present`](scripts/wl-present) is a small script to demonstrate the use of the `-S` option to interactively present on Sway. This script is especially useful when binding the `wl-present` subcommands to keyboard shortcuts. See example below. - [`release.sh`](scripts/release.sh) Generates a release tar ball for the currently checked out commit if there's a release tag on it. ### Sway Keybindings Example The following keybindings shortcuts can be used in your sway config. ``` mode "present" { # command starts mirroring bindsym m mode "default"; exec wl-present mirror # these commands modify an already running mirroring window bindsym o mode "default"; exec wl-present set-output bindsym r mode "default"; exec wl-present set-region bindsym Shift+r mode "default"; exec wl-present unset-region bindsym s mode "default"; exec wl-present set-scaling bindsym f mode "default"; exec wl-present toggle-freeze bindsym c mode "default"; exec wl-present custom # return to default mode bindsym Return mode "default" bindsym Escape mode "default" } bindsym $mod+p mode "present" ``` This requires `wl-mirror`, the `wl-present` script, `pipectl` (optional), slurp, and one of `wofi`, `wmenu`, `rofi`, or `dmenu`. Note that wl-present only allows one instance by default, but multiple instances can be used at the same time using the `--name` option or `WL_PRESENT_PIPE_NAME` environment variable. ### Kanshi Configuration Example The following [kanshi](https://git.sr.ht/~emersion/kanshi) profile will launch wl-mirror in fullscreen on an external output mirroring your internal output when switched to with `kanshictl switch mirror-hdmi` or when selected automatically. ``` profile mirror-hdmi { output eDP-1 enable mode 1920x1080 position 0,0 output HDMI-A-1 enable mode 1920x1080 position 1920,0 exec wl-present mirror eDP-1 --fullscreen-output HDMI-A-1 --fullscreen # alternatively, for wl-mirror < 0.16.4 # exec wl-present mirror eDP-1 & sleep .5; wl-present fullscreen-output HDMI-A-1; wl-present fullscreen } ``` ## Installation `wl-mirror` is already packaged in many distros and can be installed via the package manager: [![Packaging Status](https://repology.org/badge/vertical-allrepos/wl-mirror.svg?columns=3)](https://repology.org/project/wl-mirror/versions) ## Supported Wayland Compositors `wl-mirror` should work on all Wayland compositors based on wlroots, such as sway or hyprland. `wl-mirror` currently does not work on KDE and Gnome, due to `wl-mirror` not supporting the XDG Desktop Portal screen sharing protocol. This is being worked on (see issues [#16](https://github.com/Ferdi265/wl-mirror/issues/16) and [#17](https://github.com/Ferdi265/wl-mirror/issues/17)). ## Dependencies - `CMake` - `pkg-config` - `libwayland-client` - `libwayland-egl` - `libEGL` - `libGLESv2` - `epoll-shim` (on systems that do not have `epoll`, e.g. FreeBSD) - `libdecor` (see `WITH_LIBDECOR`) - `wayland-scanner` - `scdoc` (for manual pages, see `INSTALL_DOCUMENTATION`) ## Script Dependencies - `pipectl` (optional for `scripts/wl-present`) - `slurp` (`scripts/wl-present`) - `wofi`, `wmenu`, `rofi` or `dmenu` (`scripts/wl-present`) ## Building - Install Dependencies - Clone Submodules (`git submodule update --init`) - Run `cmake -B build` - Run `cmake --build build` ## CMake Options - `INSTALL_EXAMPLE_SCRIPTS`: also install example scripts (default `OFF`) - `INSTALL_DOCUMENTATION`: also build and install manual pages (default `OFF`) - `WITH_LIBDECOR`: build with libdecor for window decoration (default `OFF`) - `FORCE_WAYLAND_SCANNER_PATH`: always use the provided path for wayland-scanner, do not use pkg-config (default empty) - `FORCE_SYSTEM_WL_PROTOCOLS`: always use system-installed wayland-protocols, do not use submodules (default `OFF`) - `FORCE_SYSTEM_WLR_PROTOCOLS`: always use system-installed wlr-protocols, do not use submodules (default `OFF`) - `WL_PROTOCOL_DIR`: directory where system-installed wayland-protocols are located (default `/usr/share/wayland-protocols`) - `WLR_PROTOCOL_DIR`: directory where system-installed wlr-protocols are located (default `/usr/share/wlr-protocols`) ## Files - `src/main.c`: main entrypoint - `src/options.c`: CLI and stream option parsing - `src/wayland.c`: Wayland and `xdg_surface` boilerplate - `src/egl.c`: EGL boilerplate - `src/mirror.c`: output mirroring code - `src/mirror-dmabuf.c`: wlr-export-dmabuf-unstable-v1 backend code - `src/mirror-screencopy.c`: wlr-screencopy-unstable-v1 backend code - `src/transform.c`: matrix transformation code - `src/event.c`: event loop - `src/stream.c`: asynchronous option stream input ## License This project is licensed under the GNU GPL version 3.0 or later (SPDX [GPL-3.0-or-later](https://spdx.org/licenses/GPL-3.0-or-later.html)). The full license text can also be found in the [LICENSE](/LICENSE) file. wl-mirror-0.17.0/deps/0000755000000000000000000000000014702736000011411 5ustar00wl-mirror-0.17.0/deps/CMakeLists.txt0000644000000000000000000000400614702736000014151 0ustar00find_package(PkgConfig REQUIRED) add_library(deps INTERFACE) add_library(proto_deps INTERFACE) # helper function for finding one of a list of packages function(do_pkg_search_module name) pkg_search_module(${name} ${ARGN}) set(module_name "${${name}_MODULE_NAME}") set(module_version "${${name}_VERSION}") message(STATUS " Found ${module_name}, version ${module_version}") endfunction() # required dependencies find_library(MATH_LIBRARY m REQUIRED) pkg_check_modules(WaylandClient REQUIRED IMPORTED_TARGET "wayland-client") pkg_check_modules(WaylandEGL REQUIRED IMPORTED_TARGET "wayland-egl") pkg_check_modules(EGL REQUIRED IMPORTED_TARGET "egl") pkg_check_modules(GLESv2 REQUIRED IMPORTED_TARGET "glesv2") if ("${FORCE_WAYLAND_SCANNER_PATH}" STREQUAL "") pkg_check_modules(WaylandScanner REQUIRED "wayland-scanner") pkg_get_variable(WAYLAND_SCANNER "wayland-scanner" "wayland_scanner") set(WAYLAND_SCANNER "${WAYLAND_SCANNER}" PARENT_SCOPE) else() set(WAYLAND_SCANNER "${FORCE_WAYLAND_SCANNER_PATH}" PARENT_SCOPE) endif() # man dependencies if (${INSTALL_DOCUMENTATION}) pkg_check_modules(SCDOC REQUIRED "scdoc") else() pkg_check_modules(SCDOC "scdoc") endif() pkg_get_variable(SCDOC "scdoc" "scdoc") set(SCDOC "${SCDOC}" PARENT_SCOPE) # link dependencies target_link_libraries(deps INTERFACE ${MATH_LIBRARY} PkgConfig::WaylandClient PkgConfig::WaylandEGL PkgConfig::EGL PkgConfig::GLESv2 ) target_link_libraries(proto_deps INTERFACE PkgConfig::WaylandClient ) # check for epoll support in libc include(CheckSymbolExists) check_symbol_exists("epoll_wait" "sys/epoll.h" has-epoll) if(NOT has-epoll) # use epoll-shim to provide epoll pkg_check_modules(EPoll REQUIRED IMPORTED_TARGET "epoll-shim") target_link_libraries(deps INTERFACE PkgConfig::EPoll) endif() if(${WITH_LIBDECOR}) pkg_check_modules(LibDecor REQUIRED IMPORTED_TARGET "libdecor-0") target_link_libraries(deps INTERFACE PkgConfig::LibDecor) target_compile_definitions(deps INTERFACE WITH_LIBDECOR) endif() wl-mirror-0.17.0/glsl/0000755000000000000000000000000014702736000011417 5ustar00wl-mirror-0.17.0/glsl/CMakeLists.txt0000644000000000000000000000253614702736000014165 0ustar00add_library(shaders STATIC) target_include_directories(shaders PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/include/") file(GLOB shaders CONFIGURE_DEPENDS "*.glsl") foreach(shader ${shaders}) get_filename_component(shader-base "${shader}" NAME_WE) file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include/wlm/glsl") set(shader-template "${CMAKE_CURRENT_SOURCE_DIR}/embed.c.in") set(shader-header "${CMAKE_CURRENT_BINARY_DIR}/include/wlm/glsl/${shader-base}.h") set(shader-source "${CMAKE_CURRENT_BINARY_DIR}/src/glsl_${shader-base}.c") message(STATUS "embedding ${shader-base}.glsl") add_custom_command( OUTPUT "${shader-source}" MAIN_DEPENDENCY "${shader}" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/embed.cmake" "${shader-template}" COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_SOURCE_DIR}/embed.cmake" "${shader}" "${shader-template}" "${shader-source}" ) add_custom_target(gen-${shader-base} DEPENDS "${shader-source}") set(FILENAME "${shader-base}") set(DATA "${shader-data}") configure_file(embed.h.in "${shader-header}" @ONLY) set_source_files_properties("${shader-header}" PROPERTIES GENERATED 1) set_source_files_properties("${shader-source}" PROPERTIES GENERATED 1) add_dependencies(shaders gen-${shader-base}) target_sources(shaders PRIVATE "${shader-source}") endforeach() wl-mirror-0.17.0/glsl/embed.c.in0000644000000000000000000000013314702736000013241 0ustar00#include const char wlm_glsl_@FILENAME@[] = { @DATA@ 0 }; wl-mirror-0.17.0/glsl/embed.cmake0000644000000000000000000000126314702736000013477 0ustar00if(NOT ${CMAKE_ARGC} EQUAL 6) message(FATAL_ERROR "usage: cmake -P embed.cmake ") endif() set(shader "${CMAKE_ARGV3}") set(shader-template "${CMAKE_ARGV4}") set(shader-source "${CMAKE_ARGV5}") get_filename_component(shader-base "${shader}" NAME_WE) file(READ ${shader} shader-data HEX) string(REPEAT "[0-9a-f]" 2 byte-regex) string(REPEAT "[0-9a-f]" 16 line-regex) string(REGEX REPLACE "(${line-regex})" "\\1\n " shader-data "${shader-data}") string(REGEX REPLACE "(${byte-regex})" "0x\\1, " shader-data "${shader-data}") set(FILENAME "${shader-base}") set(DATA "${shader-data}") configure_file("${shader-template}" "${shader-source}" @ONLY) wl-mirror-0.17.0/glsl/embed.h.in0000644000000000000000000000017014702736000013247 0ustar00#ifndef WL_MIRROR_GLSL_@FILENAME@_ #define WL_MIRROR_GLSL_@FILENAME@_ extern const char wlm_glsl_@FILENAME@[]; #endif wl-mirror-0.17.0/glsl/fragment_shader.glsl0000644000000000000000000000054114702736000015433 0ustar00#version 100 precision mediump float; uniform sampler2D uTexture; uniform bool uInvertColors; varying vec2 vTexCoord; void main() { vec4 color = texture2D(uTexture, vTexCoord); if (uInvertColors) { gl_FragColor = vec4(1.0 - color.r, 1.0 - color.g, 1.0 - color.b, 1.0); } else { gl_FragColor = vec4(color.rgb, 1.0); } } wl-mirror-0.17.0/glsl/vertex_shader.glsl0000644000000000000000000000041014702736000015140 0ustar00#version 100 precision mediump float; uniform mat3 uTexTransform; attribute vec2 aPosition; attribute vec2 aTexCoord; varying vec2 vTexCoord; void main() { gl_Position = vec4(aPosition, 0.0, 1.0); vTexCoord = (uTexTransform * vec3(aTexCoord, 1.0)).xy; } wl-mirror-0.17.0/include/0000755000000000000000000000000014702736000012101 5ustar00wl-mirror-0.17.0/include/wlm/0000755000000000000000000000000014702736000012700 5ustar00wl-mirror-0.17.0/include/wlm/context.h0000644000000000000000000000102214702736000014530 0ustar00#ifndef WL_MIRROR_CONTEXT_H_ #define WL_MIRROR_CONTEXT_H_ #include #include #include #include #include #include #include #include #include #include typedef struct ctx { ctx_opt_t opt; ctx_event_t event; ctx_stream_t stream; ctx_wl_t wl; ctx_egl_t egl; ctx_mirror_t mirror; } ctx_t; noreturn void wlm_exit_fail(ctx_t * ctx); void wlm_cleanup(ctx_t * ctx); #endif wl-mirror-0.17.0/include/wlm/egl.h0000644000000000000000000000271014702736000013620 0ustar00#ifndef WL_MIRROR_EGL_H_ #define WL_MIRROR_EGL_H_ #include #include #include #include #include #include struct ctx; #define MAX_PLANES 4 typedef struct { uint32_t width; uint32_t height; uint32_t drm_format; size_t planes; int * fds; uint32_t * offsets; uint32_t * strides; uint64_t modifier; } dmabuf_t; typedef struct ctx_egl { EGLDisplay display; EGLContext context; EGLConfig config; EGLSurface surface; struct wl_egl_window * window; // extension functions PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; // texture size uint32_t width; uint32_t height; uint32_t format; // gl objects GLuint vbo; GLuint texture; GLuint freeze_texture; GLuint freeze_framebuffer; GLuint shader_program; GLint texture_transform_uniform; GLint invert_colors_uniform; // state flags bool texture_region_aware; bool texture_initialized; bool initialized; } ctx_egl_t; void wlm_egl_init(struct ctx * ctx); void wlm_egl_draw_texture(struct ctx * ctx); void wlm_egl_resize_viewport(struct ctx * ctx); void wlm_egl_resize_window(struct ctx * ctx); void wlm_egl_update_uniforms(struct ctx * ctx); void wlm_egl_freeze_framebuffer(struct ctx * ctx); bool wlm_egl_dmabuf_to_texture(struct ctx * ctx, dmabuf_t * dmabuf); void wlm_egl_cleanup(struct ctx * ctx); #endif wl-mirror-0.17.0/include/wlm/event.h0000644000000000000000000000145614702736000014200 0ustar00#ifndef WL_MIRROR_EVENT_H_ #define WL_MIRROR_EVENT_H_ #include #include #include struct ctx; typedef struct event_handler { struct event_handler * next; int fd; int events; int timeout_ms; void (*on_event)(struct ctx * ctx, uint32_t events); void (*on_each)(struct ctx * ctx); } event_handler_t; typedef struct ctx_event { int pollfd; event_handler_t * handlers; bool initialized; } ctx_event_t; void wlm_event_init(struct ctx * ctx); void wlm_event_cleanup(struct ctx * ctx); void wlm_event_add_fd(struct ctx * ctx, event_handler_t * handler); void wlm_event_change_fd(struct ctx * ctx, event_handler_t * handler); void wlm_event_remove_fd(struct ctx * ctx, event_handler_t * handler); void wlm_event_loop(struct ctx * ctx); #endif wl-mirror-0.17.0/include/wlm/log.h0000644000000000000000000000052714702736000013636 0ustar00#ifndef WL_MIRROR_LOG_H_ #define WL_MIRROR_LOG_H_ #include #define wlm_log_debug(ctx, fmt, ...) if ((ctx)->opt.verbose) fprintf(stderr, "debug: " fmt, ##__VA_ARGS__) #define wlm_log_warn(fmt, ...) fprintf(stderr, "warning: " fmt, ##__VA_ARGS__) #define wlm_log_error(fmt, ...) fprintf(stderr, "error: " fmt, ##__VA_ARGS__) #endif wl-mirror-0.17.0/include/wlm/mirror-backends.h0000644000000000000000000000064214702736000016135 0ustar00#ifndef WL_MIRROR_MIRROR_BACKENDS_H_ #define WL_MIRROR_MIRROR_BACKENDS_H_ #include struct ctx; #define MIRROR_BACKEND_FATAL_FAILCOUNT 10 typedef struct mirror_backend { void (*do_capture)(struct ctx * ctx); void (*do_cleanup)(struct ctx * ctx); size_t fail_count; } mirror_backend_t; void wlm_mirror_dmabuf_init(struct ctx * ctx); void wlm_mirror_screencopy_init(struct ctx * ctx); #endif wl-mirror-0.17.0/include/wlm/mirror-dmabuf.h0000644000000000000000000000127614702736000015625 0ustar00#ifndef WL_MIRROR_MIRROR_DMABUF_H_ #define WL_MIRROR_MIRROR_DMABUF_H_ #include #include #include #include typedef enum { STATE_WAIT_FRAME, STATE_WAIT_OBJECTS, STATE_WAIT_READY, STATE_READY, STATE_CANCELED } dmabuf_state_t; typedef struct { mirror_backend_t header; // dmabuf frame object struct zwlr_export_dmabuf_frame_v1 * dmabuf_frame; // frame data uint32_t x; uint32_t y; uint32_t buffer_flags; uint32_t frame_flags; dmabuf_t dmabuf; // dmabuf state flags dmabuf_state_t state; uint32_t processed_objects; } dmabuf_mirror_backend_t; #endif wl-mirror-0.17.0/include/wlm/mirror-screencopy.h0000644000000000000000000000163614702736000016541 0ustar00#ifndef WL_MIRROR_MIRROR_SCREENCOPY_H_ #define WL_MIRROR_MIRROR_SCREENCOPY_H_ #include #include #include #include typedef enum { STATE_WAIT_BUFFER, STATE_WAIT_BUFFER_DONE, STATE_WAIT_FLAGS, STATE_WAIT_READY, STATE_READY, STATE_CANCELED } screencopy_state_t; typedef struct { mirror_backend_t header; // shm state int shm_fd; size_t shm_size; void * shm_addr; // wl_shm objects struct wl_shm_pool * shm_pool; struct wl_buffer * shm_buffer; // screencopy frame object struct zwlr_screencopy_frame_v1 * screencopy_frame; // frame data uint32_t frame_width; uint32_t frame_height; uint32_t frame_stride; uint32_t frame_format; uint32_t frame_flags; // screencopy state flags screencopy_state_t state; } screencopy_mirror_backend_t; #endif wl-mirror-0.17.0/include/wlm/mirror.h0000644000000000000000000000161114702736000014362 0ustar00#ifndef WL_MIRROR_MIRROR_H_ #define WL_MIRROR_MIRROR_H_ #include #include #include #include #include #include #include struct ctx; struct output_list_node; typedef struct ctx_mirror { struct output_list_node * current_target; struct wl_callback * frame_callback; region_t current_region; bool invert_y; // backend data mirror_backend_t * backend; size_t auto_backend_index; // state flags bool initialized; } ctx_mirror_t; void wlm_mirror_init(struct ctx * ctx); void wlm_mirror_backend_init(struct ctx * ctx); void wlm_mirror_output_removed(struct ctx * ctx, struct output_list_node * node); void wlm_mirror_update_title(struct ctx * ctx); void wlm_mirror_backend_fail(struct ctx * ctx); void wlm_mirror_cleanup(struct ctx * ctx); #endif wl-mirror-0.17.0/include/wlm/options.h0000644000000000000000000000271714702736000014553 0ustar00#ifndef WL_MIRROR_OPTIONS_H_ #define WL_MIRROR_OPTIONS_H_ #include #include #include struct ctx; struct output_list_node; typedef enum { SCALE_FIT, SCALE_COVER, SCALE_EXACT } scale_t; typedef enum { SCALE_FILTER_LINEAR, SCALE_FILTER_NEAREST, } scale_filter_t; typedef enum { BACKEND_AUTO, BACKEND_DMABUF, BACKEND_SCREENCOPY } backend_t; typedef struct ctx_opt { bool verbose; bool stream; bool show_cursor; bool invert_colors; bool freeze; bool has_region; bool fullscreen; scale_t scaling; scale_filter_t scaling_filter; backend_t backend; transform_t transform; region_t region; char * output; char * fullscreen_output; char * window_title; } ctx_opt_t; void wlm_opt_init(struct ctx * ctx); void wlm_cleanup_opt(struct ctx * ctx); bool wlm_opt_parse_scaling(scale_t * scaling, scale_filter_t * scaling_filter, const char * scaling_arg); bool wlm_opt_parse_backend(backend_t * backend, const char * backend_arg); bool wlm_opt_parse_transform(transform_t * transform, const char * transform_arg); bool wlm_opt_parse_region(region_t * region, char ** output, const char * region_arg); bool wlm_opt_find_output(struct ctx * ctx, struct output_list_node ** output_handle, region_t * region_handle); void wlm_opt_usage(struct ctx * ctx); void wlm_opt_version(struct ctx * ctx); void wlm_opt_parse(struct ctx * ctx, int argc, char ** argv); #endif wl-mirror-0.17.0/include/wlm/stream.h0000644000000000000000000000070214702736000014343 0ustar00#ifndef WL_MIRROR_STREAM_H_ #define WL_MIRROR_STREAM_H_ #include #include #include struct ctx; typedef struct ctx_stream { char * line; size_t line_len; size_t line_cap; char ** args; size_t args_len; size_t args_cap; event_handler_t event_handler; bool initialized; } ctx_stream_t; void wlm_stream_init(struct ctx * ctx); void wlm_stream_cleanup(struct ctx * ctx); #endif wl-mirror-0.17.0/include/wlm/transform.h0000644000000000000000000000304314702736000015064 0ustar00#ifndef WL_MIRROR_TRANSFORM_H_ #define WL_MIRROR_TRANSFORM_H_ #include #include #include typedef enum { ROT_CW_0, ROT_CW_90, ROT_CW_180, ROT_CW_270, ROT_NORMAL = ROT_CW_0, ROT_CCW_0 = ROT_CW_0, ROT_CCW_90 = ROT_CW_270, ROT_CCW_180 = ROT_CW_180, ROT_CCW_270 = ROT_CW_90 } rotation_t; typedef struct { rotation_t rotation; bool flip_x; bool flip_y; } transform_t; typedef struct { int32_t x; int32_t y; int32_t width; int32_t height; } region_t; typedef struct { float data[3][3]; } mat3_t; void wlm_util_mat3_identity(mat3_t * mat); void wlm_util_mat3_transpose(mat3_t * mat); void wlm_util_mat3_mul(const mat3_t * mul, mat3_t * dest); void wlm_util_mat3_apply_transform(mat3_t * mat, transform_t transform); void wlm_util_mat3_apply_region_transform(mat3_t * mat, const region_t * region, const region_t * output); void wlm_util_mat3_apply_output_transform(mat3_t * mat, enum wl_output_transform transform); void wlm_util_mat3_apply_invert_y(mat3_t * mat, bool invert_y); void wlm_util_viewport_apply_transform(uint32_t * width, uint32_t * height, transform_t transform); void wlm_util_viewport_apply_output_transform(uint32_t * width, uint32_t * height, enum wl_output_transform transform); bool wlm_util_region_contains(const region_t * region, const region_t * output); void wlm_util_region_scale(region_t * region, double scale); void wlm_util_region_clamp(region_t * region, const region_t * output); #endif wl-mirror-0.17.0/include/wlm/util.h0000644000000000000000000000020214702736000014020 0ustar00#ifndef WL_MIRROR_UTIL_H_ #define WL_MIRROR_UTIL_H_ #define ARRAY_LENGTH(arr) ((sizeof ((arr))) / (sizeof (((arr))[0]))) #endif wl-mirror-0.17.0/include/wlm/wayland.h0000644000000000000000000000573314702736000014520 0ustar00#ifndef WL_MIRROR_WAYLAND_H_ #define WL_MIRROR_WAYLAND_H_ #include #include #include #include #include #include #include #include #include #ifdef WITH_LIBDECOR #include #endif struct ctx; typedef struct output_list_node { struct output_list_node * next; struct ctx * ctx; char * name; struct wl_output * output; struct zxdg_output_v1 * xdg_output; uint32_t output_id; int32_t x; int32_t y; int32_t width; int32_t height; int32_t scale; enum wl_output_transform transform; } output_list_node_t; typedef struct seat_list_node { struct seat_list_node * next; struct ctx * ctx; struct wl_seat * seat; uint32_t seat_id; } seat_list_node_t; typedef struct ctx_wl { struct wl_display * display; struct wl_registry * registry; // registry objects struct wl_compositor * compositor; struct wp_viewporter * viewporter; struct wp_fractional_scale_manager_v1 * fractional_scale_manager; struct xdg_wm_base * wm_base; struct zxdg_output_manager_v1 * output_manager; // registry ids uint32_t compositor_id; uint32_t viewporter_id; uint32_t fractional_scale_manager_id; uint32_t wm_base_id; uint32_t output_manager_id; // dmabuf backend objects struct zwlr_export_dmabuf_manager_v1 * dmabuf_manager; uint32_t dmabuf_manager_id; // screencopy backend objects struct wl_shm * shm; struct zwlr_screencopy_manager_v1 * screencopy_manager; uint32_t shm_id; uint32_t screencopy_manager_id; // output list output_list_node_t * outputs; seat_list_node_t * seats; // surface objects struct wl_surface * surface; struct wp_viewport * viewport; struct wp_fractional_scale_v1 * fractional_scale; #ifdef WITH_LIBDECOR struct libdecor * libdecor_context; struct libdecor_frame * libdecor_frame; #else struct xdg_surface * xdg_surface; struct xdg_toplevel * xdg_toplevel; #endif // buffer size output_list_node_t * current_output; uint32_t width; uint32_t height; double scale; // event handler event_handler_t event_handler; // state flags uint32_t last_surface_serial; #ifndef WITH_LIBDECOR bool xdg_surface_configured; bool xdg_toplevel_configured; #endif bool configured; bool closing; bool initialized; } ctx_wl_t; void wlm_wayland_init(struct ctx * ctx); void wlm_wayland_window_close(struct ctx * ctx); void wlm_wayland_window_set_title(struct ctx * ctx, const char * title); void wlm_wayland_window_set_fullscreen(struct ctx * ctx); void wlm_wayland_window_unset_fullscreen(struct ctx * ctx); void wlm_wayland_window_update_scale(struct ctx * ctx, double scale, bool is_fractional); void wlm_wayland_cleanup(struct ctx * ctx); #endif wl-mirror-0.17.0/man/0000755000000000000000000000000014702736000011231 5ustar00wl-mirror-0.17.0/man/CMakeLists.txt0000644000000000000000000000151314702736000013771 0ustar00# determine if documentation can be built if (${SCDOC_FOUND}) set(BUILD_DOCUMENTATION ON PARENT_SCOPE) else() set(BUILD_DOCUMENTATION OFF PARENT_SCOPE) endif() function(build_scdoc_man_page name section) file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/man/") set(man-src "${CMAKE_SOURCE_DIR}/man/${name}.${section}.scd") set(man-bin "${CMAKE_BINARY_DIR}/man/${name}.${section}") add_custom_command( OUTPUT "${man-bin}" MAIN_DEPENDENCY "${man-src}" COMMAND "${SCDOC}" < "${man-src}" > "${man-bin}" ) add_custom_target(gen-man-${name}-${section} ALL DEPENDS "${man-bin}") endfunction() function(install_scdoc_man_page name section) set(man-bin "${CMAKE_BINARY_DIR}/man/${name}.${section}") install(FILES "${man-bin}" DESTINATION "${CMAKE_INSTALL_MANDIR}/man${section}/") endfunction() wl-mirror-0.17.0/man/wl-mirror.1.scd0000644000000000000000000001013114702736000014011 0ustar00wl-mirror(1) # NAME wl-mirror - a simple Wayland output mirror client # SYNOPSIS *wl-mirror* [-h,-V,-v,-c,-i,-f,-s S,-b B,-t T,-r R,-S] # OPTIONS *-h, --help* Show help message and exit. *-V, --version* Show version information and exit. *-v, --verbose* * --no-verbose* Enable or disable debug logging. *-c, --show-cursor* * --no-show-cursor* Show the cursor on the mirrored surface (enabled by default). *-i, --invert-colors* * --no-invert-colors* Invert colors on the mirrored surface. *-f, --freeze* * --unfreeze* * --toggle-freeze* Freeze, unfreeze, or toggle freezing of the current image on the screen. *-F, --fullscreen* * --no-fullscreen* Open as a fullscreen window, or make the current window fullscreen in stream mode. * --fullscreen-output O* * --no-fullscreen-output* Set a different output to fullscreen on (default is the current output). *-s f, --scaling fit* Scale to fit and if necessary add letterboxing (enabled by default). *-s c, --scaling cover* Scale to cover and if necessary crop the image. *-s e, --scaling exact* Use exact multiple scaling and if necessary add letterboxing. *-s l, --scaling linear* Use linear scaling (enabled by default). *-s n, --scaling nearest* Use nearest-neighbor scaling. *-b B, --backend B* Use a specific screen capture backend, see *BACKENDS*. *-t T, --transform T* Apply custom transform (rotation and flipping), see *TRANSFORMS*. *-r R, --region R* Capture custom screen region R, see *REGIONS*. *-S, --stream* Accept a stream of additional options on stdin, see *STREAM MODE*. # BACKENDS *auto* Automatically try backends in order and use the first that works (enabled by default). The next backend is selected automatically when the current backend fails to capture a frame 10 times in a row. *dmabuf* Use the *wlr-export-dmabuf-unstable-v1* protocol to capture outputs (requires wlroots). This backend keeps the image data on the GPU and does not need expensive copies to the CPU and back. *screencopy* Use the *wlr-screencopy-unstable-v1* protocol to capture outputs (requires wlroots) This backend passes the image data via shared memory on the CPU, but may have better compatibility with complex GPU driver configurations (e.g., multi GPU). # TRANSFORMS Transforms are specified as a dash-separated list of flips followed by a rotation amount. Flips are applied before rotations, both flips and rotations are optional. Custom transformations are applied after adjusting for the wayland output transform, so that if no custom transformations are applied, the mirrored surface is displayed right-side-up. *normal* No transformation (default transformation). *flipX, flipY* Flip the X or Y coordinate of the image, i.e. *flipX* means the mirrored surface has left and right swapped. *0cw, 90cw, 180cw, 270cw* Apply a clockwise rotation. *0ccw, 90ccw, 180ccw, 270ccw* Apply a counter-clockwise rotation. The following transformation options are provided for compatibility with *sway-output*(5) transforms: *flipped* Same as *flipX*. *0, 90, 180, 270* Same as *0cw, 90cw, 180cw, 270cw*. # REGIONS Regions are specified in the format used by the *slurp*(1) utility: '*x*,*y* *width*x*height* [*output*]' When processing the region option, the region is translated into output coordinates, so when the output moves, the captured region moves with it. When a region is specified, the *output* positional argument is optional. # STREAM MODE In stream mode, *wl-mirror* interprets lines on stdin as additional command line options. - Arguments can be quoted with single or double quotes, but every argument must be fully quoted. - Unquoted arguments are split on whitespace. - No escape sequences are implemented. Option lines on stdin are processed asynchronously, and can override all options and the captured output. Stream mode is used by *wl-present*(1) to add interactive controls to *wl-mirror*. # AUTHORS Maintained by Ferdinand Bachmann . More information on *wl-mirror* can be found at . # SEE ALSO *wl-present*(1) *slurp*(1) *sway-output*(5) wl-mirror-0.17.0/man/wl-present.1.scd0000644000000000000000000000625414702736000014172 0ustar00wl-present(1) # NAME wl-present - an interactive client for *wl-mirror*(1) # SYNOPSIS *wl-present* [OPTIONS] [args...] # DESCRIPTION *wl-present* is an wrapper around *wl-mirror*(1) that uses its streaming mode together with *pipectl*(1) to interactively control what is being captured. The subcommands of *wl-present* are intended to be mapped to keyboard shortcuts in your wayland compositor for easy access. # COMMANDS *help* Show help message and exit. *mirror* [OUTPUT] [OPTIONS] Start *wl-mirror*(1) capturing OUTPUT. If no output is given, *slurp*(1) is used to select the output or region to capture. If any additional OPTIONS are specified, they are passed on to *wl-mirror*. *set-output* [OUTPUT] Set the captured output of a running *wl-present* session. If no output is given, *slurp*(1) is used to select the output to capture. *set-region* [REGION] *unset-region* Set the captured region of a running *wl-present* session. If no region is given, *slurp*(1) us used to select a region to capture. *set-scaling* [SCALING] Set the scaling mode of a running *wl-present* session. If no region is given, *dmenu*(1) (or a compatible replacement) is used to select a scaling mode. *freeze*, *unfreeze*, *toggle-freeze* Set the freeze state of a running *wl-present* session. *fullscreen* *unfullscreen* Set the fullscreen state of a running *wl-present* session. *fullscreen-output* [OUTPUT] *no-fullscreen-output* Set the fullscreen target output of a running *wl-present* session. *custom* [OPTIONS] Send custom options to a running *wl-present* session. If no options are given, *dmenu*(1) (or a compatible replacement) are used to select an option. # OPTIONS *-n*, *--name* Use an alternative pipe name (default is wl-present). This allows multiple instances of wl-present to run at the same time. # ENVIRONMENT VARIABLES *WL_PRESENT_DMENU* Overrides the used dmenu implementation. When set, this command is used to select options instead of the default. When unset, *wl-present* attempts to use *wofi*(1), *wmenu*(1), *fuzzel*(1), *rofi*(1), and *dmenu*(1), in this order. Example: WL_PRESENT_DMENU="rofi -dmenu -p present" *WL_PRESENT_PIPECTL* Overrides the used pipectl implementation. When set, this command is used to pipe options to *wl-mirror*(1) instead of the default. When unset, *wl-present* attempts to use *pipectl*(1) or an included pipectl shim written in bash if pipectl is not available. *WL_PRESENT_SLURP* Overrides the used slurp implementation and options. When set, this command is used to select regions and outputs instead of the default. When unset, *wl-present* attempts to use *slurp*(1) without any additional options. Example: WL_PRESENT_SLURP="slurp -c #859900 -w 2" *WL_PRESENT_PIPE_NAME* Overrides the default pipe name (default is wl-present). This is equivalent to the *--name* option above. Example: WL_PRESENT_PIPE_NAME=monitor2 wl-present mirror HDMI-A-1 # AUTHORS Maintained by Ferdinand Bachmann . More information on *wl-mirror* can be found at . # SEE ALSO *wl-mirror*(1) *pipectl*(1) *slurp*(1) *wofi*(1) *wmenu*(1) *fuzzel*(1) *rofi*(1) *dmenu*(1) wl-mirror-0.17.0/proto/0000755000000000000000000000000014702736000011621 5ustar00wl-mirror-0.17.0/proto/CMakeLists.txt0000644000000000000000000000651414702736000014367 0ustar00# detect wayland-protocols location set(wl-protocols-found FALSE) if(NOT ${FORCE_SYSTEM_WL_PROTOCOLS} AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/wayland-protocols/.git") message(STATUS "using wayland-protocols from submodule") set(WL_PROTOCOL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/wayland-protocols/") set(wl-protocols-found TRUE) endif() if (NOT ${wl-protocols-found} AND IS_DIRECTORY ${WL_PROTOCOL_DIR}) message(STATUS "using system wayland-protocols at ${WL_PROTOCOL_DIR}") set(wl-protocols-found TRUE) endif() if (NOT ${wl-protocols-found}) message(STATUS "error: wayland-protocols not found") endif() # detect wlr-protocols location set(wlr-protocols-found FALSE) if(NOT ${FORCE_SYSTEM_WLR_PROTOCOLS} AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/wlr-protocols/.git") message(STATUS "using wlr-protocols from submodule") set(WLR_PROTOCOL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/wlr-protocols/") set(wlr-protocols-found TRUE) endif() if (NOT ${wlr-protocols-found} AND IS_DIRECTORY ${WLR_PROTOCOL_DIR}) message(STATUS "using system wlr-protocols at ${WLR_PROTOCOL_DIR}") set(wlr-protocols-found TRUE) endif() if (NOT ${wlr-protocols-found}) message(STATUS "error: wlr-protocols not found") endif() if (NOT ${wl-protocols-found} OR NOT ${wlr-protocols-found}) message(FATAL_ERROR "wayland-protocols or wlr-protocols not found") endif() # wayland protocol wrapper generation with wayland-scanner set(protocols-found TRUE) add_library(protocols STATIC) target_include_directories(protocols INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/include/") target_link_libraries(protocols PRIVATE proto_deps) foreach(proto ${PROTOCOLS}) get_filename_component(proto-base "${proto}" NAME_WE) set(wl-proto-file "${WL_PROTOCOL_DIR}/${proto}") set(wlr-proto-file "${WLR_PROTOCOL_DIR}/${proto}") if(EXISTS ${wl-proto-file}) message(STATUS "using ${proto} from wayland-protocols") set(proto-file ${wl-proto-file}) elseif(EXISTS ${wlr-proto-file}) message(STATUS "using ${proto} from wlr-protocols") set(proto-file ${wlr-proto-file}) else() message(STATUS "error: protocol ${proto} not found") set(protocols-found FALSE) endif() file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include/wlm/proto") set(proto-header "${CMAKE_CURRENT_BINARY_DIR}/include/wlm/proto/${proto-base}.h") set(proto-source "${CMAKE_CURRENT_BINARY_DIR}/src/${proto-base}.c") file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include/") file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/src/") add_custom_command( OUTPUT "${proto-header}" MAIN_DEPENDENCY "${proto-file}" COMMAND "${WAYLAND_SCANNER}" client-header "${proto-file}" "${proto-header}" ) add_custom_command( OUTPUT "${proto-source}" MAIN_DEPENDENCY "${proto-file}" COMMAND "${WAYLAND_SCANNER}" private-code "${proto-file}" "${proto-source}" ) add_custom_target(gen-${proto-base} DEPENDS "${proto-header}" "${proto-source}") set_source_files_properties("${proto-header}" PROPERTIES GENERATED 1) set_source_files_properties("${proto-source}" PROPERTIES GENERATED 1) add_dependencies(protocols gen-${proto-base}) target_sources(protocols PRIVATE "${proto-source}") endforeach() if(NOT ${protocols-found}) message(FATAL_ERROR "some protocols could not be found") endif() wl-mirror-0.17.0/proto/wlr-protocols/0000755000000000000000000000000014702736000014447 5ustar00wl-mirror-0.17.0/proto/wlr-protocols/.build.yml0000644000000000000000000000025514702736000016351 0ustar00image: archlinux packages: - wayland sources: - https://gitlab.freedesktop.org/wlroots/wlr-protocols.git tasks: - protocols: | cd wlr-protocols make check wl-mirror-0.17.0/proto/wlr-protocols/.editorconfig0000644000000000000000000000031514702736000017123 0ustar00root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 indent_style = tab indent_size = 4 trim_trailing_whitespace = true [*.xml] indent_style = space indent_size = 2 tab_width = 8 wl-mirror-0.17.0/proto/wlr-protocols/.git/0000755000000000000000000000000014702736000015310 5ustar00wl-mirror-0.17.0/proto/wlr-protocols/.gitlab-ci.yml0000644000000000000000000000011614702736000017101 0ustar00include: https://git.sr.ht/~emersion/dalligi/blob/master/templates/single.yml wl-mirror-0.17.0/proto/wlr-protocols/Makefile0000644000000000000000000000235614702736000016115 0ustar00PREFIX=/usr DATADIR=$${datarootdir} DATAROOTDIR=$${prefix}/share unstable_protocols = \ unstable/wlr-data-control-unstable-v1.xml \ unstable/wlr-export-dmabuf-unstable-v1.xml \ unstable/wlr-foreign-toplevel-management-unstable-v1.xml \ unstable/wlr-gamma-control-unstable-v1.xml \ unstable/wlr-input-inhibitor-unstable-v1.xml \ unstable/wlr-layer-shell-unstable-v1.xml \ unstable/wlr-output-management-unstable-v1.xml \ unstable/wlr-output-power-management-unstable-v1.xml \ unstable/wlr-screencopy-unstable-v1.xml \ unstable/wlr-virtual-pointer-unstable-v1.xml check: $(unstable_protocols) ./check.sh $(unstable_protocols) clean: rm -f wlr-protocols.pc wlr-protocols.pc: wlr-protocols.pc.in sed \ -e 's:@prefix@:$(PREFIX):g' \ -e 's:@datadir@:$(DATADIR):g' \ -e 's:@datarootdir@:$(DATAROOTDIR):g' \ <$< >$@ install-unstable: $(unstable_protocols) mkdir -p $(DESTDIR)$(PREFIX)/share/wlr-protocols/unstable for protocol in $^ ; \ do \ install -Dm644 $$protocol \ $(DESTDIR)$(PREFIX)/share/wlr-protocols/$$protocol ; \ done install-pc: wlr-protocols.pc mkdir -p $(DESTDIR)$(PREFIX)/share/pkgconfig/ install -Dm644 wlr-protocols.pc \ $(DESTDIR)$(PREFIX)/share/pkgconfig/wlr-protocols.pc install: install-unstable install-pc wl-mirror-0.17.0/proto/wlr-protocols/README.md0000644000000000000000000000060114702736000015723 0ustar00# wlr-protocols Wayland protocols designed for use in wlroots (and other compositors). ## Submitting changes to existing protocols Please submit a merge request on GitLab. ## Submitting new protocols New protocols should not be submitted to wlr-protocols. Instead, submit them to [wayland-protocols]. [wayland-protocols]: https://gitlab.freedesktop.org/wayland/wayland-protocols wl-mirror-0.17.0/proto/wlr-protocols/check.sh0000755000000000000000000000037614702736000016071 0ustar00#!/bin/sh -eu for f in "$@" do echo >&2 "Checking $f" wayland-scanner -s client-header "$f" /dev/null wayland-scanner -s server-header "$f" /dev/null wayland-scanner -s public-code "$f" /dev/null wayland-scanner -s private-code "$f" /dev/null done wl-mirror-0.17.0/proto/wlr-protocols/unstable/0000755000000000000000000000000014702736000016264 5ustar00wl-mirror-0.17.0/proto/wlr-protocols/unstable/wlr-data-control-unstable-v1.xml0000644000000000000000000002741614702736000024350 0ustar00 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. wl-mirror-0.17.0/proto/wlr-protocols/unstable/wlr-export-dmabuf-unstable-v1.xml0000644000000000000000000002172714702736000024535 0ustar00 Copyright © 2018 Rostislav Pehlivanov 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 interface to capture surfaces in an efficient way by exporting DMA-BUFs. 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 object is a manager with which to start capturing from sources. Capture the next frame of an entire output. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object represents a single DMA-BUF frame. If the capture is successful, the compositor will first send a "frame" event, followed by one or several "object". When the frame is available for readout, the "ready" event is sent. If the capture failed, the "cancel" event is sent. This can happen anytime before the "ready" event. Once either a "ready" or a "cancel" event is received, the client should destroy the frame. Once an "object" event is received, the client is responsible for closing the associated file descriptor. All frames are read-only and may not be written into or altered. Special flags that should be respected by the client. Main event supplying the client with information about the frame. If the capture didn't fail, this event is always emitted first before any other events. This event is followed by a number of "object" as specified by the "num_objects" argument. Event which serves to supply the client with the file descriptors containing the data for each object. After receiving this event, the client must always close the file descriptor as soon as they're done with it and even if the frame fails. This event is sent as soon as the frame is presented, indicating it is available for reading. This event includes the time at which presentation happened at. The timestamp is 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]. The seconds part may have an arbitrary offset at start. After receiving this event, the client should destroy this object. Indicates reason for cancelling the frame. If the capture failed or if the frame is no longer valid after the "frame" event has been emitted, this event will be used to inform the client to scrap the frame. If the failure is temporary, the client may capture again the same source. If the failure is permanent, any further attempts to capture the same source will fail again. After receiving this event, the client should destroy this object. Unreferences the frame. This request must be called as soon as its no longer used. It can be called at any time by the client. The client will still have to close any FDs it has been given. wl-mirror-0.17.0/proto/wlr-protocols/unstable/wlr-foreign-toplevel-management-unstable-v1.xml0000644000000000000000000002644414702736000027354 0ustar00 Copyright © 2018 Ilia Bozhinov 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 purpose of this protocol is to enable the creation of taskbars and docks by providing them with a list of opened applications and letting them request certain actions on them, like maximizing, etc. After a client binds the zwlr_foreign_toplevel_manager_v1, each opened toplevel window will be sent via the toplevel event This event is emitted whenever a new toplevel window is created. It is emitted for all toplevels, regardless of the app that has created them. All initial details of the toplevel(title, app_id, states, etc.) will be sent immediately after this event via the corresponding events in zwlr_foreign_toplevel_handle_v1. Indicates the client no longer wishes to receive events for new toplevels. However the compositor may emit further toplevel_created events, until the finished event is emitted. The client must not send any more requests after this one. This event indicates that the compositor is done sending events to the zwlr_foreign_toplevel_manager_v1. The server will destroy the object immediately after sending this request, so it will become invalid and the client should free any resources associated with it. A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel window. Each app may have multiple opened toplevels. Each toplevel has a list of outputs it is visible on, conveyed to the client with the output_enter and output_leave events. This event is emitted whenever the title of the toplevel changes. This event is emitted whenever the app-id of the toplevel changes. This event is emitted whenever the toplevel becomes visible on the given output. A toplevel may be visible on multiple outputs. This event is emitted whenever the toplevel stops being visible on the given output. It is guaranteed that an entered-output event with the same output has been emitted before this event. Requests that the toplevel be maximized. If the maximized state actually changes, this will be indicated by the state event. Requests that the toplevel be unmaximized. If the maximized state actually changes, this will be indicated by the state event. Requests that the toplevel be minimized. If the minimized state actually changes, this will be indicated by the state event. Requests that the toplevel be unminimized. If the minimized state actually changes, this will be indicated by the state event. Request that this toplevel be activated on the given seat. There is no guarantee the toplevel will be actually activated. The different states that a toplevel can have. These have the same meaning as the states with the same names defined in xdg-toplevel This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 is created and each time the toplevel state changes, either because of a compositor action or because of a request in this protocol. This event is sent after all changes in the toplevel state have been sent. This allows changes to the zwlr_foreign_toplevel_handle_v1 properties to be seen as atomic, even if they happen via multiple events. Send a request to the toplevel to close itself. The compositor would typically use a shell-specific method to carry out this request, for example by sending the xdg_toplevel.close event. However, this gives no guarantees the toplevel will actually be destroyed. If and when this happens, the zwlr_foreign_toplevel_handle_v1.closed event will be emitted. The rectangle of the surface specified in this request corresponds to the place where the app using this protocol represents the given toplevel. It can be used by the compositor as a hint for some operations, e.g minimizing. The client is however not required to set this, in which case the compositor is free to decide some default value. If the client specifies more than one rectangle, only the last one is considered. The dimensions are given in surface-local coordinates. Setting width=height=0 removes the already-set rectangle. This event means the toplevel has been destroyed. It is guaranteed there won't be any more events for this zwlr_foreign_toplevel_handle_v1. The toplevel itself becomes inert so any requests will be ignored except the destroy request. Destroys the zwlr_foreign_toplevel_handle_v1 object. This request should be called either when the client does not want to use the toplevel anymore or after the closed event to finalize the destruction of the object. Requests that the toplevel be fullscreened on the given output. If the fullscreen state and/or the outputs the toplevel is visible on actually change, this will be indicated by the state and output_enter/leave events. The output parameter is only a hint to the compositor. Also, if output is NULL, the compositor should decide which output the toplevel will be fullscreened on, if at all. Requests that the toplevel be unfullscreened. If the fullscreen state actually changes, this will be indicated by the state event. This event is emitted whenever the parent of the toplevel changes. No event is emitted when the parent handle is destroyed by the client. wl-mirror-0.17.0/proto/wlr-protocols/unstable/wlr-gamma-control-unstable-v1.xml0000644000000000000000000001260314702736000024511 0ustar00 Copyright © 2015 Giulio camuffo Copyright © 2018 Simon Ser 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 set the gamma tables for outputs. 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-output gamma controls. Create a gamma control that can be used to adjust gamma tables for the provided output. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This interface allows a client to adjust gamma tables for a particular output. The client will receive the gamma size, and will then be able to set gamma tables. At any time the compositor can send a failed event indicating that this object is no longer valid. There can only be at most one gamma control object per output, which has exclusive access to this particular output. When the gamma control object is destroyed, the gamma table is restored to its original value. Advertise the size of each gamma ramp. This event is sent immediately when the gamma control object is created. Set the gamma table. The file descriptor can be memory-mapped to provide the raw gamma table, which contains successive gamma ramps for the red, green and blue channels. Each gamma ramp is an array of 16-byte unsigned integers which has the same length as the gamma size. The file descriptor data must have the same length as three times the gamma size. This event indicates that the gamma control is no longer valid. This can happen for a number of reasons, including: - The output doesn't support gamma tables - Setting the gamma tables failed - Another client already has exclusive gamma control for this output - The compositor has transferred gamma control to another client Upon receiving this event, the client should destroy this object. Destroys the gamma control object. If the object is still valid, this restores the original gamma tables. wl-mirror-0.17.0/proto/wlr-protocols/unstable/wlr-input-inhibitor-unstable-v1.xml0000644000000000000000000000651414702736000025101 0ustar00 Copyright © 2018 Drew DeVault 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. Clients can use this interface to prevent input events from being sent to any surfaces but its own, which is useful for example in lock screen software. It is assumed that access to this interface will be locked down to whitelisted clients by the compositor. Note! This protocol is deprecated and not intended for production use. For screen lockers, use the ext-session-lock-v1 protocol. Activates the input inhibitor. As long as the inhibitor is active, the compositor will not send input events to other clients. While this resource exists, input to clients other than the owner of the inhibitor resource will not receive input events. Any client which previously had focus will receive a leave event and will not be given focus again. The client that owns this resource will receive all input events normally. The compositor will also disable all of its own input processing (such as keyboard shortcuts) while the inhibitor is active. The compositor may continue to send input events to selected clients, such as an on-screen keyboard (via the input-method protocol). Destroy the inhibitor and allow other clients to receive input. wl-mirror-0.17.0/proto/wlr-protocols/unstable/wlr-layer-shell-unstable-v1.xml0000644000000000000000000004403614702736000024177 0ustar00 Copyright © 2017 Drew DeVault 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. Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and rendered with a defined z-depth respective to each other. They may also be anchored to the edges and corners of a screen and specify input handling semantics. This interface should be suitable for the implementation of many desktop shell components, and a broad number of other applications that interact with the desktop. Create a layer surface for an existing surface. This assigns the role of layer_surface, or raises a protocol error if another role is already assigned. Creating a layer 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 layer_surface.configure call must also be treated as errors. After creating a layer_surface object and setting it up, the client must perform an initial commit without any buffer attached. The compositor will reply with a layer_surface.configure event. The client must acknowledge it and is then allowed to attach a buffer to map the surface. You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. Clients can specify a namespace that defines the purpose of the layer surface. These values indicate which layers a surface can be rendered in. They are ordered by z depth, bottom-most first. Traditional shell surfaces will typically be rendered between the bottom and top layers. Fullscreen shell surfaces are typically rendered at the top layer. Multiple surfaces can share a single layer, and ordering within a single layer is undefined. This request indicates that the client will not use the layer_shell object any more. Objects that have been created through this instance are not affected. An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like environment. Layer surface state (layer, size, anchor, exclusive zone, margin, interactivity) is double-buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. Attaching a null buffer to a layer surface unmaps it. Unmapping a layer_surface means that the surface cannot be shown by the compositor until it is explicitly mapped again. The layer_surface returns to the state it had right after layer_shell.get_layer_surface. The client can re-map the surface by performing a commit without any buffer attached, waiting for a configure event and handling it as usual. Sets the size of the surface in surface-local coordinates. The compositor will display the surface centered with respect to its anchors. If you pass 0 for either value, the compositor will assign it and inform you of the assignment in the configure event. You must set your anchor to opposite edges in the dimensions you omit; not doing so is a protocol error. Both values are 0 by default. Size is double-buffered, see wl_surface.commit. Requests that the compositor anchor the surface to the specified edges and corners. 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 corner of the output); otherwise the anchor point will be centered on that edge, or in the center if none is specified. Anchor is double-buffered, see wl_surface.commit. Requests that the compositor avoids occluding an area with other surfaces. The compositor's use of this information is implementation-dependent - do not assume that this region will not actually be occluded. A positive value is only meaningful if the surface is anchored to one edge or an edge and both perpendicular edges. If the surface is not anchored, anchored to only two perpendicular edges (a corner), anchored to only two parallel edges or anchored to all edges, a positive value will be treated the same as zero. A positive zone is the distance from the edge in surface-local coordinates to consider exclusive. Surfaces that do not wish to have an exclusive zone may instead specify how they should interact with surfaces that do. If set to zero, the surface indicates that it would like to be moved to avoid occluding surfaces with a positive exclusive zone. If set to -1, the surface indicates that it would not like to be moved to accommodate for other surfaces, and the compositor should extend it all the way to the edges it is anchored to. For example, a panel might set its exclusive zone to 10, so that maximized shell surfaces are not shown on top of it. A notification might set its exclusive zone to 0, so that it is moved to avoid occluding the panel, but shell surfaces are shown underneath it. A wallpaper or lock screen might set their exclusive zone to -1, so that they stretch below or over the panel. The default value is 0. Exclusive zone is double-buffered, see wl_surface.commit. Requests that the surface be placed some distance away from the anchor point on the output, in surface-local coordinates. Setting this value for edges you are not anchored to has no effect. The exclusive zone includes the margin. Margin is double-buffered, see wl_surface.commit. Types of keyboard interaction possible for layer shell surfaces. The rationale for this is twofold: (1) some applications are not interested in keyboard events and not allowing them to be focused can improve the desktop experience; (2) some applications will want to take exclusive keyboard focus. This value indicates that this surface is not interested in keyboard events and the compositor should never assign it the keyboard focus. This is the default value, set for newly created layer shell surfaces. This is useful for e.g. desktop widgets that display information or only have interaction with non-keyboard input devices. Request exclusive keyboard focus if this surface is above the shell surface layer. For the top and overlay layers, the seat will always give exclusive keyboard focus to the top-most layer which has keyboard interactivity set to exclusive. If this layer contains multiple surfaces with keyboard interactivity set to exclusive, the compositor determines the one receiving keyboard events in an implementation- defined manner. In this case, no guarantee is made when this surface will receive keyboard focus (if ever). For the bottom and background layers, the compositor is allowed to use normal focus semantics. This setting is mainly intended for applications that need to ensure they receive all keyboard events, such as a lock screen or a password prompt. This requests the compositor to allow this surface to be focused and unfocused by the user in an implementation-defined manner. The user should be able to unfocus this surface even regardless of the layer it is on. Typically, the compositor will want to use its normal mechanism to manage keyboard focus between layer shell surfaces with this setting and regular toplevels on the desktop layer (e.g. click to focus). Nevertheless, it is possible for a compositor to require a special interaction to focus or unfocus layer shell surfaces (e.g. requiring a click even if focus follows the mouse normally, or providing a keybinding to switch focus between layers). This setting is mainly intended for desktop shell components (e.g. panels) that allow keyboard interaction. Using this option can allow implementing a desktop shell that can be fully usable without the mouse. Set how keyboard events are delivered to this surface. By default, layer shell surfaces do not receive keyboard events; this request can be used to change this. This setting is inherited by child surfaces set by the get_popup request. Layer surfaces receive pointer, touch, and tablet events normally. If you do not want to receive them, set the input region on your surface to an empty region. Keyboard interactivity is double-buffered, see wl_surface.commit. This assigns an xdg_popup's parent to this layer_surface. This popup should have been created via xdg_surface::get_popup with the parent set to NULL, and this request must be invoked before committing the popup's initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. 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. 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. This request destroys the layer surface. The configure event asks the client to resize its surface. 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. 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 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). If the client picks a smaller size and is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the surface will be centered on this axis. If the width or height arguments are zero, it means the client should decide its own window dimension. The closed event is sent by the compositor when the surface will no longer be shown. The output may have been destroyed or the user may have asked for it to be removed. Further changes to the surface will be ignored. The client should destroy the resource after receiving this event, and create a new surface if they so choose. Change the layer that the surface is rendered on. Layer is double-buffered, see wl_surface.commit. wl-mirror-0.17.0/proto/wlr-protocols/unstable/wlr-output-management-unstable-v1.xml0000644000000000000000000006226614702736000025435 0ustar00 Copyright © 2019 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 exposes interfaces to obtain and modify output device configuration. 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 reading and writing the current output device configuration. Output devices that display pixels (e.g. a physical monitor or a virtual output in a window) are represented as heads. Heads cannot be created nor destroyed by the client, but they can be enabled or disabled and their properties can be changed. Each head may have one or more available modes. Whenever a head appears (e.g. a monitor is plugged in), it will be advertised via the head event. Immediately after the output manager is bound, all current heads are advertised. Whenever a head's properties change, the relevant wlr_output_head events will be sent. Not all head properties will be sent: only properties that have changed need to. Whenever a head disappears (e.g. a monitor is unplugged), a wlr_output_head.finished event will be sent. After one or more heads appear, change or disappear, the done event will be sent. It carries a serial which can be used in a create_configuration request to update heads properties. The information obtained from this protocol should only be used for output configuration purposes. This protocol is not designed to be a generic output property advertisement protocol for regular clients. Instead, protocols such as xdg-output should be used. This event introduces a new head. This happens whenever a new head appears (e.g. a monitor is plugged in) or after the output manager is bound. This event is sent after all information has been sent after binding to the output manager object and after any subsequent changes. This applies to child head and mode objects as well. In other words, this event is sent whenever a head or mode is created or destroyed and whenever one of their properties has been changed. Not all state is re-sent each time the current configuration changes: only the actual changes are sent. This allows changes to the output configuration to be seen as atomic, even if they happen via multiple events. A serial is sent to be used in a future create_configuration request. Create a new output configuration object. This allows to update head properties. Indicates the client no longer wishes to receive events for output configuration changes. However the compositor may emit further events, until the finished event is emitted. The client must not send any more requests after this one. This event indicates that the compositor is done sending manager events. The compositor will destroy the object immediately after sending this event, so it will become invalid and the client should release any resources associated with it. A head is an output device. The difference between a wl_output object and a head is that heads are advertised even if they are turned off. A head object only advertises properties and cannot be used directly to change them. A head has some read-only properties: modes, name, description and physical_size. These cannot be changed by clients. Other properties can be updated via a wlr_output_configuration object. Properties sent via this interface are applied atomically via the wlr_output_manager.done event. No guarantees are made regarding the order in which properties are sent. This event describes the head name. The naming convention is compositor defined, but limited to alphanumeric characters and dashes (-). Each name is unique among all wlr_output_head objects, but if a wlr_output_head object 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. If the compositor implements the xdg-output protocol and this head is enabled, the xdg_output.name event must report the same name. The name event is sent after a wlr_output_head object is created. This event is only sent once per object, and the name does not change over the lifetime of the wlr_output_head object. This event describes a human-readable description of the head. 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'. However, do not assume that the name is a reflection of the make, model, serial of the underlying DRM connector or the display name of the underlying X11 connection, etc. If the compositor implements xdg-output and this head is enabled, the xdg_output.description must report the same description. The description event is sent after a wlr_output_head object is created. This event is only sent once per object, and the description does not change over the lifetime of the wlr_output_head object. This event describes the physical size of the head. This event is only sent if the head has a physical size (e.g. is not a projector or a virtual device). This event introduces a mode for this head. It is sent once per supported mode. This event describes whether the head is enabled. A disabled head is not mapped to a region of the global compositor space. When a head is disabled, some properties (current_mode, position, transform and scale) are irrelevant. This event describes the mode currently in use for this head. It is only sent if the output is enabled. This events describes the position of the head in the global compositor space. It is only sent if the output is enabled. This event describes the transformation currently applied to the head. It is only sent if the output is enabled. This events describes the scale of the head in the global compositor space. It is only sent if the output is enabled. This event indicates that the head is no longer available. The head object becomes inert. Clients should send a destroy request and release any resources associated with it. This event describes the manufacturer of the head. This must report the same make as the wl_output interface does in its geometry event. Together with the model and serial_number events the purpose is to allow clients to recognize heads from previous sessions and for example load head-specific configurations back. It is not guaranteed this event will be ever sent. A reason for that can be that the compositor does not have information about the make of the head or the definition of a make is not sensible in the current setup, for example in a virtual session. Clients can still try to identify the head by available information from other events but should be aware that there is an increased risk of false positives. It is not recommended to display the make string in UI to users. For that the string provided by the description event should be preferred. This event describes the model of the head. This must report the same model as the wl_output interface does in its geometry event. Together with the make and serial_number events the purpose is to allow clients to recognize heads from previous sessions and for example load head-specific configurations back. It is not guaranteed this event will be ever sent. A reason for that can be that the compositor does not have information about the model of the head or the definition of a model is not sensible in the current setup, for example in a virtual session. Clients can still try to identify the head by available information from other events but should be aware that there is an increased risk of false positives. It is not recommended to display the model string in UI to users. For that the string provided by the description event should be preferred. This event describes the serial number of the head. Together with the make and model events the purpose is to allow clients to recognize heads from previous sessions and for example load head- specific configurations back. It is not guaranteed this event will be ever sent. A reason for that can be that the compositor does not have information about the serial number of the head or the definition of a serial number is not sensible in the current setup. Clients can still try to identify the head by available information from other events but should be aware that there is an increased risk of false positives. It is not recommended to display the serial_number string in UI to users. For that the string provided by the description event should be preferred. This request indicates that the client will no longer use this head object. This event describes whether adaptive sync is currently enabled for the head or not. Adaptive sync is also known as Variable Refresh Rate or VRR. This object describes an output mode. Some heads don't support output modes, in which case modes won't be advertised. Properties sent via this interface are applied atomically via the wlr_output_manager.done event. No guarantees are made regarding the order in which properties are sent. This event describes the mode size. The size 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 or transformed. This event describes the mode's fixed vertical refresh rate. It is only sent if the mode has a fixed refresh rate. This event advertises this mode as preferred. This event indicates that the mode is no longer available. The mode object becomes inert. Clients should send a destroy request and release any resources associated with it. This request indicates that the client will no longer use this mode object. This object is used by the client to describe a full output configuration. First, the client needs to setup the output configuration. Each head can be either enabled (and configured) or disabled. It is a protocol error to send two enable_head or disable_head requests with the same head. It is a protocol error to omit a head in a configuration. Then, the client can apply or test the configuration. The compositor will then reply with a succeeded, failed or cancelled event. Finally the client should destroy the configuration object. Enable a head. This request creates a head configuration object that can be used to change the head's properties. Disable a head. Apply the new output configuration. In case the configuration is successfully applied, there is no guarantee that the new output state matches completely the requested configuration. For instance, a compositor might round the scale if it doesn't support fractional scaling. After this request has been sent, the compositor must respond with an succeeded, failed or cancelled event. Sending a request that isn't the destructor is a protocol error. Test the new output configuration. The configuration won't be applied, but will only be validated. Even if the compositor succeeds to test a configuration, applying it may fail. After this request has been sent, the compositor must respond with an succeeded, failed or cancelled event. Sending a request that isn't the destructor is a protocol error. Sent after the compositor has successfully applied the changes or tested them. Upon receiving this event, the client should destroy this object. If the current configuration has changed, events to describe the changes will be sent followed by a wlr_output_manager.done event. Sent if the compositor rejects the changes or failed to apply them. The compositor should revert any changes made by the apply request that triggered this event. Upon receiving this event, the client should destroy this object. Sent if the compositor cancels the configuration because the state of an output changed and the client has outdated information (e.g. after an output has been hotplugged). The client can create a new configuration with a newer serial and try again. Upon receiving this event, the client should destroy this object. Using this request a client can tell the compositor that it is not going to use the configuration object anymore. Any changes to the outputs that have not been applied will be discarded. This request also destroys wlr_output_configuration_head objects created via this object. This object is used by the client to update a single head's configuration. It is a protocol error to set the same property twice. This request sets the head's mode. This request assigns a custom mode to the head. The size is given in physical hardware units of the output device. If set to zero, the refresh rate is unspecified. It is a protocol error to set both a mode and a custom mode. This request sets the head's position in the global compositor space. This request sets the head's transform. This request sets the head's scale. This request enables/disables adaptive sync. Adaptive sync is also known as Variable Refresh Rate or VRR. wl-mirror-0.17.0/proto/wlr-protocols/unstable/wlr-output-power-management-unstable-v1.xml0000644000000000000000000001273514702736000026563 0ustar00 Copyright © 2019 Purism SPC 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 control power management modes of outputs that are currently part of the compositor space. The intent is to allow special clients like desktop shells to power down outputs when the system is idle. To modify outputs not currently part of the compositor space see wlr-output-management. 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-output power management mode controls. Create an output power management mode control that can be used to adjust the power management mode for a given output. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object offers requests to set the power management mode of an output. Set an output's power save mode to the given mode. The mode change is effective immediately. If the output does not support the given mode a failed event is sent. Report the power management mode change of an output. The mode event is sent after an output changed its power management mode. The reason can be a client using set_mode or the compositor deciding to change an output's mode. This event is also sent immediately when the object is created so the client is informed about the current power management mode. This event indicates that the output power management mode control is no longer valid. This can happen for a number of reasons, including: - The output doesn't support power management - Another client already has exclusive power management mode control for this output - The output disappeared Upon receiving this event, the client should destroy this object. Destroys the output power management mode control object. wl-mirror-0.17.0/proto/wlr-protocols/unstable/wlr-screencopy-unstable-v1.xml0000644000000000000000000002366614702736000024136 0ustar00 Copyright © 2018 Simon Ser Copyright © 2019 Andri Yngvason 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 ask the compositor to copy part of the screen content to a client 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. This object is a manager which offers requests to start capturing from a source. Capture the next frame of an entire output. Capture the next frame of an output's region. The region is given in output logical coordinates, see xdg_output.logical_size. The region will be clipped to the output's extents. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object represents a single frame. When created, a series of buffer events will be sent, each representing a supported buffer type. The "buffer_done" event is sent afterwards to indicate that all supported buffer types have been enumerated. The client will then be able to send a "copy" request. If the capture is successful, the compositor will send a "flags" followed by a "ready" event. For objects version 2 or lower, wl_shm buffers are always supported, ie. the "buffer" event is guaranteed to be sent. If the capture failed, the "failed" event is sent. This can happen anytime before the "ready" event. Once either a "ready" or a "failed" event is received, the client should destroy the frame. Provides information about wl_shm buffer parameters that need to be used for this frame. This event is sent once after the frame is created if wl_shm buffers are supported. Copy the frame to the supplied buffer. The buffer must have a the correct size, see zwlr_screencopy_frame_v1.buffer and zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a supported format. If the frame is successfully copied, a "flags" and a "ready" events are sent. Otherwise, a "failed" event is sent. Provides flags about the frame. This event is sent once before the "ready" event. Called as soon as the frame is copied, indicating it is available for reading. This event includes the time at which presentation happened at. The timestamp is 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]. The seconds part may have an arbitrary offset at start. After receiving this event, the client should destroy the object. This event indicates that the attempted frame copy has failed. After receiving this event, the client should destroy the object. Destroys the frame. This request can be sent at any time by the client. Same as copy, except it waits until there is damage to copy. This event is sent right before the ready event when copy_with_damage is requested. It may be generated multiple times for each copy_with_damage request. The arguments describe a box around an area that has changed since the last copy request that was derived from the current screencopy manager instance. The union of all regions received between the call to copy_with_damage and a ready event is the total damage since the prior ready event. Provides information about linux-dmabuf buffer parameters that need to be used for this frame. This event is sent once after the frame is created if linux-dmabuf buffers are supported. This event is sent once after all buffer events have been sent. The client should proceed to create a buffer of one of the supported types, and send a "copy" request. wl-mirror-0.17.0/proto/wlr-protocols/unstable/wlr-virtual-pointer-unstable-v1.xml0000644000000000000000000001535714702736000025126 0ustar00 Copyright © 2019 Josef Gajdusek 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 emulate a physical pointer device. The requests are mostly mirror opposites of those specified in wl_pointer. The pointer has moved by a relative amount to the previous request. Values are in the global compositor space. The pointer has moved in an absolute coordinate frame. Value of x can range from 0 to x_extent, value of y can range from 0 to y_extent. A button was pressed or released. Scroll and other axis requests. Indicates the set of events that logically belong together. Source information for scroll and other axis. Stop notification for scroll and other axes. Discrete step information for scroll and other axes. This event allows the client to extend data normally sent using the axis event with discrete value. This object allows clients to create individual virtual pointer objects. Creates a new virtual pointer. The optional seat is a suggestion to the compositor. Creates a new virtual pointer. The seat and the output arguments are optional. If the seat argument is set, the compositor should assign the input device to the requested seat. If the output argument is set, the compositor should map the input device to the requested output. wl-mirror-0.17.0/proto/wlr-protocols/wlr-protocols.pc.in0000644000000000000000000000025714702736000020232 0ustar00prefix=@prefix@ datarootdir=@datarootdir@ pkgdatadir=${pc_sysrootdir}@datadir@/wlr-protocols Name: wlroots Wayland protocols Description: Wayland protocol files Version: 1.0 wl-mirror-0.17.0/scripts/0000755000000000000000000000000014702736000012145 5ustar00wl-mirror-0.17.0/scripts/release.sh0000755000000000000000000000535014702736000014127 0ustar00#!/bin/bash set -euo pipefail # environment variables for reproducible archive SOURCE_EPOCH=$(git show --no-patch --format=%ct) TARFLAGS=" --sort=name --mtime=@$SOURCE_EPOCH --owner=0 --group=0 --numeric-owner --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime " # wrapper function for reproducible archive tar() { LC_ALL="C.UTF-8" command tar $TARFLAGS "$@" } SCRIPTDIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")") REPODIR=$(realpath "$SCRIPTDIR/..") TEMPDIR=$(mktemp -d) cleanup() { if [[ ! -z "$TEMPDIR" && -d "$TEMPDIR" ]]; then rm -rf "$TEMPDIR" fi } trap cleanup EXIT cd "$REPODIR" TAGS=( $(git tag -l --contains HEAD) ) NUM_TAGS="${#TAGS[@]}" if [[ $NUM_TAGS -eq 0 ]]; then echo "error: no tag on current commit" exit 1 elif [[ $NUM_TAGS -gt 1 ]]; then echo "error: multiple tags on current commit" echo "info: found tags ( ${TAGS[@]} )" exit 1 fi TAG="${TAGS[0]}" if [[ ! $TAG =~ v[0-9]+.[0-9]+.[0-9]+ ]]; then echo "error: tag does not match version regex" echo "info: tag was $TAG" exit 1 fi VERSION="${TAG##v}" echo ">> creating release $VERSION" echo "- cloning repo" cd "$TEMPDIR" git clone --recursive "$REPODIR" "wl-mirror-$VERSION" echo "- removing unneeded files" rm -rf "wl-mirror-$VERSION/.git" rm -rf "wl-mirror-$VERSION/.gitignore" rm -rf "wl-mirror-$VERSION/.gitmodules" rm -rf "wl-mirror-$VERSION/proto/wayland-protocols/.git" rm -rf "wl-mirror-$VERSION/proto/wlr-protocols/.git" mkdir -p "wl-mirror-$VERSION/proto/wayland-protocols/.git" mkdir -p "wl-mirror-$VERSION/proto/wlr-protocols/.git" echo "- adding version file" echo "$TAG" > "wl-mirror-$VERSION/version.txt" echo "- creating archive" mkdir -p "$REPODIR/dist" tar -caf "$REPODIR/dist/wl-mirror-$VERSION.tar.gz" "wl-mirror-$VERSION/" echo "- removing bundled wayland-protocols" rm -rf "wl-mirror-$VERSION/proto/wayland-protocols" echo "- creating archive without wayland-protocols" mkdir -p "$REPODIR/dist" tar -caf "$REPODIR/dist/wl-mirror-$VERSION-nowlp.tar.gz" "wl-mirror-$VERSION/" if [[ ! -z "${SIGKEY+z}" ]]; then echo "- signing archive" gpg --yes -u "$SIGKEY" -o "$REPODIR/dist/wl-mirror-$VERSION.tar.gz.asc" --armor --detach-sig "$REPODIR/dist/wl-mirror-$VERSION.tar.gz" gpg --yes -o "$REPODIR/dist/wl-mirror-$VERSION.tar.gz.sig" --dearmor "$REPODIR/dist/wl-mirror-$VERSION.tar.gz.asc" echo "- signing archive without wayland-protocols" gpg --yes -u "$SIGKEY" -o "$REPODIR/dist/wl-mirror-$VERSION-nowlp.tar.gz.asc" --armor --detach-sig "$REPODIR/dist/wl-mirror-$VERSION-nowlp.tar.gz" gpg --yes -o "$REPODIR/dist/wl-mirror-$VERSION-nowlp.tar.gz.sig" --dearmor "$REPODIR/dist/wl-mirror-$VERSION-nowlp.tar.gz.asc" else echo "- skipping signing archive (SIGKEY not set)" fi echo "- success" wl-mirror-0.17.0/scripts/wl-present0000755000000000000000000001443714702736000014204 0ustar00#!/bin/bash usage() { echo "usage: wl-present [options] [argument]" echo echo "start wl-mirror and control the mirrored output and region in a convenient way" echo echo "commands:" echo " help show this help" echo " mirror [output] [options] start wl-mirror on output [output] (default asks via slurp)" echo " set-output [output] set the recorded output (default asks via slurp)" echo " set-region [region] set the recorded region (default asks via slurp)" echo " unset-region unset the recorded region" echo " set-scaling [scale] set the scaling mode (default asks via rofi)" echo " freeze freeze the screen" echo " unfreeze resume the screen capture after freeze" echo " toggle-freeze toggle freeze state of screen capture" echo " fullscreen fullscreen the wl-mirror window" echo " unfullscreen unfullscreen the wl-mirror window" echo " fullscreen-output [output] set the output to fullscreen to (default asks via slurp)" echo " no-fullscreen-output set the output to fullscreen to the current output" echo " custom [options] send custom options to wl-mirror (default asks via rofi)" echo echo "options:" echo " -n, --name use an alternative pipe name (default is wl-present)" echo " this allows multiple instances of wl-present to run" echo " at the same time." echo "dependencies:" echo " wl-mirror, bash, slurp, pipectl (optional), and either wofi, wmenu, rofi, fuzzel, or dmenu" echo echo "environment variables": echo " WL_PRESENT_DMENU overrides the used dmenu implementation" echo " WL_PRESENT_PIPECTL overrides the used pipectl implementation" echo " WL_PRESENT_SLURP overrides the used slurp implementation" echo " WL_PRESENT_PIPE_NAME overrides the default pipe name (default is wl-present)" exit 0 } if [[ -n "$WL_PRESENT_DMENU" ]]; then DMENU="$WL_PRESENT_DMENU" elif type -p wofi >/dev/null; then DMENU="wofi -d" elif type -p wmenu >/dev/null; then DMENU=wmenu elif type -p fuzzel >/dev/null; then DMENU="fuzzel -d" elif type -p rofi >/dev/null; then DMENU="rofi -dmenu" else DMENU=dmenu fi if [[ -n "$WL_PRESENT_PIPECTL" ]]; then PIPECTL="$WL_PRESENT_PIPECTL" elif type -p pipectl >/dev/null; then PIPECTL=pipectl else PIPECTL=pipectl-shim fi if [[ -n "$WL_PRESENT_SLURP" ]]; then SLURP="$WL_PRESENT_SLURP" else SLURP=slurp fi if [[ -n "$WL_PRESENT_PIPE_NAME" ]]; then PRESENT_PIPE_NAME="$WL_PRESENT_PIPE_NAME" else PRESENT_PIPE_NAME=wl-present fi pipectl-shim() { PIPEPATH="${XDG_RUNTIME_DIR:-"${TMPDIR:-/tmp}"}" PIPENAME="pipectl.$(id -u).pipe" MODE= FORCE=0 while [[ $# -gt 0 && "${1:0:1}" == "-" ]]; do opt="$1" shift case "$opt" in -n|--name) arg="$1"; shift; PIPENAME="pipectl.$(id -u).$arg.pipe";; -i|--in) MODE=in;; -o|--out) MODE=out;; -f|--force) FORCE=1;; --) break;; esac done PIPE="$PIPEPATH/$PIPENAME" case "$MODE" in "in") if [[ ! -p "$PIPE" ]]; then echo "error: could not open pipe at '$PIPE': No such file or directory" >&2 return 1 else cat > "$PIPE" fi;; "out") if [[ "$FORCE" -eq 0 && -p "$PIPE" ]]; then echo "error: cannot create pipe at '$PIPE': File exists" >&2 return 1 else [[ "$FORCE" -eq 1 ]] && rm -f "$PIPE" mkfifo "$PIPE"; (tail -f "$PIPE" & echo > "$PIPE"; wait); rm -f "$PIPE" fi;; esac } slurp-output() { $SLURP -or -f '%o' 2>/dev/null } slurp-region() { $SLURP 2>/dev/null } slurp-output-or-region() { $SLURP -o -f '%o|%x,%y %wx%h' 2>/dev/null } mirror() { if [[ "$#" -eq 0 || "$1" =~ ^- ]]; then OUTPUT_REGION=$(ask-output-or-region) IFS='|' read -r OUTPUT REGION <<< "$OUTPUT_REGION" mirror "$OUTPUT" -r "$REGION" "$@" return fi OUTPUT="$1" shift $PIPECTL -n "$PRESENT_PIPE_NAME" -o | wl-mirror -S "$@" "$OUTPUT" } mirror-cmd() { $PIPECTL -n "$PRESENT_PIPE_NAME" -i <<< "$1" } set-output() { mirror-cmd "$1" } set-region() { mirror-cmd "-r '$1'" } set-scaling() { mirror-cmd "-s $1" } set-fullscreen-output() { mirror-cmd "--fullscreen-output $1" } ask-output() { slurp-output [[ $? -ne 0 ]] && exit 1 } ask-region() { slurp-region [[ $? -ne 0 ]] && exit 1 } ask-output-or-region() { slurp-output-or-region [[ $? -ne 0 ]] && exit 1 } ask-scaling() { (echo fit; echo cover; echo exact; echo linear; echo nearest) | $DMENU -p "wl-present scaling" [[ $? -ne 0 ]] && exit 1 } ask-custom() { cat < #include #include #include #include #include #include #include #include #include #include #include #include // --- buffers --- static const float vertex_array[] = { -1.0, -1.0, 0.0, 0.0, 1.0, -1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 1.0, 0.0, 1.0, 1.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0 }; // --- has_extension --- static bool has_extension(const char * extension) { size_t ext_len = strlen(extension); // try to find extension in extension list const char * extensions = (const char *)glGetString(GL_EXTENSIONS); char * match = strstr(extensions, extension); // verify match was not a substring of another extension bool found = ( match != NULL && (match == extensions || match[-1] == ' ') && (match[ext_len] == '\0' || match[ext_len] == ' ') ); return found; } // --- init_egl --- void wlm_egl_init(ctx_t * ctx) { // initialize context structure ctx->egl.display = EGL_NO_DISPLAY; ctx->egl.context = EGL_NO_CONTEXT; ctx->egl.config = EGL_NO_CONFIG_KHR; ctx->egl.surface = EGL_NO_SURFACE; ctx->egl.window = EGL_NO_SURFACE; ctx->egl.glEGLImageTargetTexture2DOES = NULL; ctx->egl.width = 1; ctx->egl.height = 1; ctx->egl.format = 0; ctx->egl.vbo = 0; ctx->egl.texture = 0; ctx->egl.freeze_texture = 0; ctx->egl.freeze_framebuffer = 0; ctx->egl.shader_program = 0; ctx->egl.texture_transform_uniform = 0; ctx->egl.invert_colors_uniform = 0; ctx->egl.texture_region_aware = false; ctx->egl.texture_initialized = false; ctx->egl.initialized = true; // create egl display ctx->egl.display = eglGetDisplay((EGLNativeDisplayType)ctx->wl.display); if (ctx->egl.display == EGL_NO_DISPLAY) { wlm_log_error("egl::init(): failed to create EGL display\n"); wlm_exit_fail(ctx); } // initialize egl display and check egl version EGLint major, minor; if (eglInitialize(ctx->egl.display, &major, &minor) != EGL_TRUE) { wlm_log_error("egl::init(): failed to initialize EGL display\n"); wlm_exit_fail(ctx); } wlm_log_debug(ctx, "egl::init(): initialized EGL %d.%d\n", major, minor); // find an egl config with // - window support // - OpenGL ES 2.0 support // - RGB888 texture support EGLint num_configs; EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_NONE }; if (eglChooseConfig(ctx->egl.display, config_attribs, &ctx->egl.config, 1, &num_configs) != EGL_TRUE) { wlm_log_error("egl::init(): failed to get EGL config\n"); wlm_exit_fail(ctx); } // default window size to 100x100 if not set if (ctx->wl.width == 0) ctx->wl.width = 100; if (ctx->wl.height == 0) ctx->wl.height = 100; // create egl window ctx->egl.window = wl_egl_window_create(ctx->wl.surface, ctx->wl.width, ctx->wl.height); if (ctx->egl.window == EGL_NO_SURFACE) { wlm_log_error("egl::init(): failed to create EGL window\n"); wlm_exit_fail(ctx); } // create egl surface ctx->egl.surface = eglCreateWindowSurface(ctx->egl.display, ctx->egl.config, (EGLNativeWindowType)ctx->egl.window, NULL); // create egl context with support for OpenGL ES 2.0 EGLint context_attribs[] = { EGL_CONTEXT_MAJOR_VERSION, 2, EGL_CONTEXT_MINOR_VERSION, 0, EGL_NONE }; ctx->egl.context = eglCreateContext(ctx->egl.display, ctx->egl.config, EGL_NO_CONTEXT, context_attribs); if (ctx->egl.context == EGL_NO_CONTEXT) { wlm_log_error("egl::init(): failed to create EGL context\n"); wlm_exit_fail(ctx); } // activate egl context if (eglMakeCurrent(ctx->egl.display, ctx->egl.surface, ctx->egl.surface, ctx->egl.context) != EGL_TRUE) { wlm_log_error("egl::init(): failed to activate EGL context\n"); wlm_exit_fail(ctx); } // check for needed extensions // - GL_OES_EGL_image: for converting EGLImages to GL textures if (!has_extension("GL_OES_EGL_image")) { wlm_log_error("egl::init(): missing EGL extension GL_OES_EGL_image\n"); wlm_exit_fail(ctx); } // get pointers to functions provided by extensions // - glEGLImageTargetTexture2DOES: for converting EGLImages to GL textures ctx->egl.glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); if (ctx->egl.glEGLImageTargetTexture2DOES == NULL) { wlm_log_error("egl::init(): failed to get pointer to glEGLImageTargetTexture2DOES\n"); wlm_exit_fail(ctx); } // create vertex buffer object glGenBuffers(1, &ctx->egl.vbo); glBindBuffer(GL_ARRAY_BUFFER, ctx->egl.vbo); glBufferData(GL_ARRAY_BUFFER, sizeof vertex_array, vertex_array, GL_STATIC_DRAW); // create texture and set scaling mode glGenTextures(1, &ctx->egl.texture); glBindTexture(GL_TEXTURE_2D, ctx->egl.texture); if (ctx->opt.scaling_filter == SCALE_FILTER_LINEAR) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } // create freeze texture and set scaling mode glGenTextures(1, &ctx->egl.freeze_texture); glBindTexture(GL_TEXTURE_2D, ctx->egl.freeze_texture); if (ctx->opt.scaling_filter == SCALE_FILTER_LINEAR) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } // create freeze framebuffer glGenFramebuffers(1, &ctx->egl.freeze_framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, ctx->egl.freeze_framebuffer); glBindTexture(GL_TEXTURE_2D, ctx->egl.texture); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ctx->egl.texture, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); // error log for shader compilation error messages GLint success; const char * shader_source = NULL; char errorLog[1024] = { 0 }; // compile vertex shader shader_source = wlm_glsl_vertex_shader; GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex_shader, 1, &shader_source, NULL); glCompileShader(vertex_shader); glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success); if (success != GL_TRUE) { glGetShaderInfoLog(vertex_shader, sizeof errorLog, NULL, errorLog); errorLog[strcspn(errorLog, "\n")] = '\0'; wlm_log_error("egl::init(): failed to compile vertex shader: %s\n", errorLog); glDeleteShader(vertex_shader); wlm_exit_fail(ctx); } // compile fragment shader shader_source = wlm_glsl_fragment_shader; GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment_shader, 1, &shader_source, NULL); glCompileShader(fragment_shader); glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success); if (success != GL_TRUE) { glGetShaderInfoLog(fragment_shader, sizeof errorLog, NULL, errorLog); errorLog[strcspn(errorLog, "\n")] = '\0'; wlm_log_error("egl::init(): failed to compile fragment shader: %s\n", errorLog); glDeleteShader(vertex_shader); glDeleteShader(fragment_shader); wlm_exit_fail(ctx); } // create shader program and get pointers to shader uniforms ctx->egl.shader_program = glCreateProgram(); glAttachShader(ctx->egl.shader_program, vertex_shader); glAttachShader(ctx->egl.shader_program, fragment_shader); glLinkProgram(ctx->egl.shader_program); glGetProgramiv(ctx->egl.shader_program, GL_LINK_STATUS, &success); if (success != GL_TRUE) { wlm_log_error("egl::init(): failed to link shader program\n"); glDeleteShader(vertex_shader); glDeleteShader(fragment_shader); glDeleteProgram(ctx->egl.shader_program); wlm_exit_fail(ctx); } ctx->egl.texture_transform_uniform = glGetUniformLocation(ctx->egl.shader_program, "uTexTransform"); ctx->egl.invert_colors_uniform = glGetUniformLocation(ctx->egl.shader_program, "uInvertColors"); glUseProgram(ctx->egl.shader_program); glDeleteShader(vertex_shader); glDeleteShader(fragment_shader); // set initial texture transform matrix mat3_t texture_transform; wlm_util_mat3_identity(&texture_transform); glUniformMatrix3fv(ctx->egl.texture_transform_uniform, 1, false, (float *)texture_transform.data); // set invert colors uniform bool invert_colors = ctx->opt.invert_colors; glUniform1i(ctx->egl.invert_colors_uniform, invert_colors); // set GL clear color to back and set GL vertex layout glClearColor(0.0, 0.0, 0.0, 1); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof (float), (void *)(0 * sizeof (float))); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof (float), (void *)(2 * sizeof (float))); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); // draw initial frame wlm_egl_draw_texture(ctx); if (eglSwapBuffers(ctx->egl.display, ctx->egl.surface) != EGL_TRUE) { wlm_log_error("egl::init(): failed to swap buffers\n"); wlm_exit_fail(ctx); } } // --- draw_texture --- void wlm_egl_draw_texture(ctx_t *ctx) { glBindTexture(GL_TEXTURE_2D, ctx->opt.freeze ? ctx->egl.freeze_texture : ctx->egl.texture); glClear(GL_COLOR_BUFFER_BIT); if (ctx->egl.texture_initialized) { glDrawArrays(GL_TRIANGLES, 0, 6); } } // --- resize_viewport void wlm_egl_resize_viewport(ctx_t * ctx) { wlm_log_debug(ctx, "egl::resize_viewport(): resizing viewport\n"); uint32_t win_width = round(ctx->wl.width * ctx->wl.scale); uint32_t win_height = round(ctx->wl.height * ctx->wl.scale); uint32_t tex_width = ctx->egl.width; uint32_t tex_height = ctx->egl.height; uint32_t view_width = win_width; uint32_t view_height = win_height; // rotate texture dimensions by output transform if (ctx->egl.texture_initialized) { wlm_util_viewport_apply_output_transform(&tex_width, &tex_height, ctx->mirror.current_target->transform); } // clamp texture dimensions to specified region region_t output_region; region_t clamp_region; if (ctx->egl.texture_initialized && ctx->opt.has_region && !ctx->egl.texture_region_aware) { output_region = (region_t){ .x = 0, .y = 0, .width = tex_width, .height = tex_height }; clamp_region = ctx->mirror.current_region; // HACK: calculate effective output fractional scale // wayland doesn't provide this information double output_scale = (double)tex_width / ctx->mirror.current_target->width; wlm_util_region_scale(&clamp_region, output_scale); wlm_util_region_clamp(&clamp_region, &output_region); tex_width = clamp_region.width; tex_height = clamp_region.height; } // rotate texture dimensions by user transform wlm_util_viewport_apply_transform(&tex_width, &tex_height, ctx->opt.transform); // calculate aspect ratio double win_aspect = (double)win_width / win_height; double tex_aspect = (double)tex_width / tex_height; if (ctx->opt.scaling == SCALE_FIT) { // select biggest width or height that fits and preserves aspect ratio if (win_aspect > tex_aspect) { view_width = view_height * tex_aspect; } else if (win_aspect < tex_aspect) { view_height = view_width / tex_aspect; } } else if (ctx->opt.scaling == SCALE_COVER) { // select biggest width or height that covers and preserves aspect ratio if (win_aspect < tex_aspect) { view_width = view_height * tex_aspect; } else if (win_aspect > tex_aspect) { view_height = view_width / tex_aspect; } } else if (ctx->opt.scaling == SCALE_EXACT) { // select biggest fitting integer scale double width_scale = (double)win_width / tex_width; double height_scale = (double)win_height / tex_height; uint32_t upscale_factor = floorf(fminf(width_scale, height_scale)); uint32_t downscale_factor = ceilf(fmaxf(1 / width_scale, 1 / height_scale)); if (upscale_factor > 1) { wlm_log_debug(ctx, "egl::resize_viewport(): upscaling by factor = %d\n", upscale_factor); view_width = tex_width * upscale_factor; view_height = tex_height * upscale_factor; } else if (downscale_factor > 1) { wlm_log_debug(ctx, "egl::resize_viewport(): downscaling by factor = %d\n", downscale_factor); view_width = tex_width / downscale_factor; view_height = tex_height / downscale_factor; } else { view_width = tex_width; view_height = tex_height; } } wlm_log_debug(ctx, "egl::resize_viewport(): win_width = %d, win_height = %d\n", win_width, win_height); wlm_log_debug(ctx, "egl::resize_viewport(): view_width = %d, view_height = %d\n", view_width, view_height); // updating GL viewport wlm_log_debug(ctx, "egl::resize_viewport(): viewport %d, %d, %d, %d\n", (int32_t)(win_width - view_width) / 2, (int32_t)(win_height - view_height) / 2, view_width, view_height ); glViewport((int32_t)(win_width - view_width) / 2, (int32_t)(win_height - view_height) / 2, view_width, view_height); // recalculate texture transform mat3_t texture_transform; wlm_util_mat3_identity(&texture_transform); if (ctx->egl.texture_initialized) { // apply transformations in reverse order as we need to transform // from OpenGL space to texture space wlm_util_mat3_apply_invert_y(&texture_transform, true); wlm_util_mat3_apply_transform(&texture_transform, ctx->opt.transform); if (ctx->opt.has_region && !ctx->egl.texture_region_aware) { wlm_util_mat3_apply_region_transform(&texture_transform, &clamp_region, &output_region); } wlm_util_mat3_apply_output_transform(&texture_transform, ctx->mirror.current_target->transform); wlm_util_mat3_apply_invert_y(&texture_transform, ctx->mirror.invert_y); } // set texture transform matrix uniform // - GL matrices are stored in column-major order, so transpose the matrix wlm_util_mat3_transpose(&texture_transform); glUniformMatrix3fv(ctx->egl.texture_transform_uniform, 1, false, (float *)texture_transform.data); } // --- resize_window --- void wlm_egl_resize_window(ctx_t * ctx) { uint32_t width = round(ctx->wl.width * ctx->wl.scale); uint32_t height = round(ctx->wl.height * ctx->wl.scale); wlm_log_debug(ctx, "egl::resize_window(): resizing EGL window to %dx%d\n", width, height); // resize window, then trigger viewport recalculation wl_egl_window_resize(ctx->egl.window, width, height, 0, 0); wp_viewport_set_source(ctx->wl.viewport, 0, 0, wl_fixed_from_int(width), wl_fixed_from_int(height)); wlm_egl_resize_viewport(ctx); // redraw frame wlm_egl_draw_texture(ctx); if (eglSwapBuffers(ctx->egl.display, ctx->egl.surface) != EGL_TRUE) { wlm_log_error("egl::resize_window(): failed to swap buffers\n"); wlm_exit_fail(ctx); } } // --- update_uniforms --- void wlm_egl_update_uniforms(ctx_t * ctx) { // trigger viewport recalculation wlm_egl_resize_viewport(ctx); // set invert colors uniform bool invert_colors = ctx->opt.invert_colors; glUniform1i(ctx->egl.invert_colors_uniform, invert_colors); // set texture scaling mode glBindTexture(GL_TEXTURE_2D, ctx->egl.texture); if (ctx->opt.scaling_filter == SCALE_FILTER_LINEAR) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } glBindTexture(GL_TEXTURE_2D, ctx->egl.freeze_texture); if (ctx->opt.scaling_filter == SCALE_FILTER_LINEAR) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } } // --- freeze_framebuffer --- void wlm_egl_freeze_framebuffer(struct ctx * ctx) { glBindFramebuffer(GL_FRAMEBUFFER, ctx->egl.freeze_framebuffer); glBindTexture(GL_TEXTURE_2D, ctx->egl.freeze_texture); glCopyTexImage2D(GL_TEXTURE_2D, 0, ctx->egl.format, 0, 0, ctx->egl.width, ctx->egl.height, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); } // --- dmabuf_to_texture --- static const EGLAttrib fd_attribs[] = { EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE1_FD_EXT, EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE3_FD_EXT }; _Static_assert(ARRAY_LENGTH(fd_attribs) == MAX_PLANES, "fd_attribs has incorrect length"); static const EGLAttrib offset_attribs[] = { EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT }; _Static_assert(ARRAY_LENGTH(offset_attribs) == MAX_PLANES, "offset_attribs has incorrect length"); static const EGLAttrib stride_attribs[] = { EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT, EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT }; _Static_assert(ARRAY_LENGTH(stride_attribs) == MAX_PLANES, "stride_attribs has incorrect length"); static const EGLAttrib modifier_low_attribs[] = { EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT }; _Static_assert(ARRAY_LENGTH(modifier_low_attribs) == MAX_PLANES, "modifier_low_attribs has incorrect length"); static const EGLAttrib modifier_high_attribs[] = { EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT }; _Static_assert(ARRAY_LENGTH(modifier_high_attribs) == MAX_PLANES, "modifier_high_attribs has incorrect length"); bool wlm_egl_dmabuf_to_texture(ctx_t * ctx, dmabuf_t * dmabuf) { if (dmabuf->planes > MAX_PLANES) { wlm_log_error("egl::dmabuf_to_texture(): too many planes, got %zd, can support at most %d\n", dmabuf->planes, MAX_PLANES); return false; } int i = 0; EGLAttrib * image_attribs = malloc((6 + 10 * dmabuf->planes + 1) * sizeof (EGLAttrib)); if (image_attribs == NULL) { wlm_log_error("egl::dmabuf_to_texture(): failed to allocate EGL image attribs\n"); return false; } image_attribs[i++] = EGL_WIDTH; image_attribs[i++] = dmabuf->width; image_attribs[i++] = EGL_HEIGHT; image_attribs[i++] = dmabuf->height; image_attribs[i++] = EGL_LINUX_DRM_FOURCC_EXT; image_attribs[i++] = dmabuf->drm_format; for (size_t j = 0; j < dmabuf->planes; j++) { image_attribs[i++] = fd_attribs[j]; image_attribs[i++] = dmabuf->fds[j]; image_attribs[i++] = offset_attribs[j]; image_attribs[i++] = dmabuf->offsets[j]; image_attribs[i++] = stride_attribs[j]; image_attribs[i++] = dmabuf->strides[j]; image_attribs[i++] = modifier_low_attribs[j]; image_attribs[i++] = (uint32_t)dmabuf->modifier; image_attribs[i++] = modifier_high_attribs[j]; image_attribs[i++] = (uint32_t)(dmabuf->modifier >> 32); } image_attribs[i++] = EGL_NONE; // create EGLImage from dmabuf with attribute array EGLImage frame_image = eglCreateImage(ctx->egl.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, image_attribs); free(image_attribs); if (frame_image == EGL_NO_IMAGE) { wlm_log_error("egl::dmabuf_to_texture(): failed to create EGL image from dmabuf: error = %x\n", eglGetError()); return false; } // convert EGLImage to GL texture glBindTexture(GL_TEXTURE_2D, ctx->egl.texture); ctx->egl.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, frame_image); // destroy temporary image eglDestroyImage(ctx->egl.display, frame_image); return true; } // --- cleanup_egl --- void wlm_egl_cleanup(ctx_t *ctx) { if (!ctx->egl.initialized) return; wlm_log_debug(ctx, "egl::cleanup(): destroying EGL objects\n"); if (ctx->egl.shader_program != 0) glDeleteProgram(ctx->egl.shader_program); if (ctx->egl.freeze_framebuffer != 0) glDeleteFramebuffers(1, &ctx->egl.freeze_framebuffer); if (ctx->egl.freeze_texture != 0) glDeleteTextures(1, &ctx->egl.freeze_texture); if (ctx->egl.texture != 0) glDeleteTextures(1, &ctx->egl.texture); if (ctx->egl.vbo != 0) glDeleteBuffers(1, &ctx->egl.vbo); if (ctx->egl.context != EGL_NO_CONTEXT) eglDestroyContext(ctx->egl.display, ctx->egl.context); if (ctx->egl.surface != EGL_NO_SURFACE) eglDestroySurface(ctx->egl.display, ctx->egl.surface); if (ctx->egl.window != EGL_NO_SURFACE) wl_egl_window_destroy(ctx->egl.window); if (ctx->egl.display != EGL_NO_DISPLAY) eglTerminate(ctx->egl.display); ctx->egl.initialized = false; } wl-mirror-0.17.0/src/event.c0000644000000000000000000000662114702736000012537 0ustar00#include #include #include #include #include static void add_handler(ctx_t * ctx, event_handler_t * handler) { handler->next = ctx->event.handlers; ctx->event.handlers = handler; } static void remove_handler(ctx_t * ctx, int fd) { event_handler_t ** pcur = &ctx->event.handlers; event_handler_t * cur = *pcur; while (cur != NULL) { if (cur->fd == fd) { *pcur = cur->next; return; } pcur = &cur->next; cur = cur->next; } } static void call_each_handler(ctx_t * ctx) { event_handler_t * cur = ctx->event.handlers; while (cur != NULL) { if (cur->on_each != NULL) { cur->on_each(ctx); } cur = cur->next; } } static event_handler_t * min_timeout(ctx_t * ctx) { event_handler_t * min = NULL; event_handler_t * cur = ctx->event.handlers; while (cur != NULL) { if (cur->timeout_ms != -1) { if (min == NULL || cur->timeout_ms < min->timeout_ms) min = cur; } cur = cur->next; } return min; } void wlm_event_add_fd(ctx_t * ctx, event_handler_t * handler) { struct epoll_event event; event.events = handler->events; event.data.ptr = handler; if (epoll_ctl(ctx->event.pollfd, EPOLL_CTL_ADD, handler->fd, &event) == -1) { wlm_log_error("event::add_fd(): failed to add fd to epoll instance\n"); wlm_exit_fail(ctx); } add_handler(ctx, handler); } void wlm_event_change_fd(ctx_t * ctx, event_handler_t * handler) { struct epoll_event event; event.events = handler->events; event.data.ptr = handler; if (epoll_ctl(ctx->event.pollfd, EPOLL_CTL_MOD, handler->fd, &event) == -1) { wlm_log_error("event::change_fd(): failed to modify fd in epoll instance\n"); wlm_exit_fail(ctx); } } void wlm_event_remove_fd(ctx_t * ctx, event_handler_t * handler) { if (epoll_ctl(ctx->event.pollfd, EPOLL_CTL_DEL, handler->fd, NULL) == -1) { wlm_log_error("event::remove_fd(): failed to remove fd from epoll instance\n"); } remove_handler(ctx, handler->fd); handler->next = NULL; } #define MAX_EVENTS 10 void wlm_event_loop(ctx_t * ctx) { struct epoll_event events[MAX_EVENTS]; int num_events; event_handler_t * timeout_handler; int timeout_ms; timeout_handler = min_timeout(ctx); timeout_ms = timeout_handler == NULL ? -1 : timeout_handler->timeout_ms; while ((num_events = epoll_wait(ctx->event.pollfd, events, MAX_EVENTS, timeout_ms)) != -1 && !ctx->wl.closing) { for (int i = 0; i < num_events; i++) { event_handler_t * handler = (event_handler_t *)events[i].data.ptr; handler->on_event(ctx, events[i].events); } if (num_events == 0 && timeout_handler != NULL) { timeout_handler->on_event(ctx, 0); } timeout_handler = min_timeout(ctx); timeout_ms = timeout_handler == NULL ? -1 : timeout_handler->timeout_ms; call_each_handler(ctx); } } void wlm_event_init(ctx_t * ctx) { ctx->event.pollfd = epoll_create(1); if (ctx->event.pollfd == -1) { wlm_log_error("event::init(): failed to create epoll instance\n"); wlm_exit_fail(ctx); return; } ctx->event.handlers = NULL; ctx->event.initialized = true; } void wlm_event_cleanup(ctx_t * ctx) { close(ctx->event.pollfd); } wl-mirror-0.17.0/src/main.c0000644000000000000000000000317414702736000012342 0ustar00#include #include #include #include #include void wlm_cleanup(ctx_t * ctx) { wlm_log_debug(ctx, "main::cleanup(): deallocating resources\n"); if (ctx->mirror.initialized) wlm_mirror_cleanup(ctx); if (ctx->egl.initialized) wlm_egl_cleanup(ctx); if (ctx->wl.initialized) wlm_wayland_cleanup(ctx); if (ctx->stream.initialized) wlm_stream_cleanup(ctx); if (ctx->event.initialized) wlm_event_cleanup(ctx); wlm_cleanup_opt(ctx); } noreturn void wlm_exit_fail(ctx_t * ctx) { wlm_cleanup(ctx); exit(1); } int main(int argc, char ** argv) { ctx_t ctx = { 0 }; ctx.event.initialized = false; ctx.stream.initialized = false; ctx.wl.initialized = false; ctx.egl.initialized = false; ctx.mirror.initialized = false; wlm_opt_init(&ctx); wlm_event_init(&ctx); if (argc > 0) { // skip program name argv++; argc--; } wlm_opt_parse(&ctx, argc, argv); wlm_log_debug(&ctx, "main::main(): initializing stream\n"); wlm_stream_init(&ctx); wlm_log_debug(&ctx, "main::main(): initializing wayland\n"); wlm_wayland_init(&ctx); wlm_log_debug(&ctx, "main::main(): initializing EGL\n"); wlm_egl_init(&ctx); wlm_log_debug(&ctx, "main::main(): initializing mirror\n"); wlm_mirror_init(&ctx); wlm_log_debug(&ctx, "main::main(): initializing mirror backend\n"); wlm_mirror_backend_init(&ctx); wlm_log_debug(&ctx, "main::main(): entering event loop\n"); wlm_event_loop(&ctx); wlm_log_debug(&ctx, "main::main(): exiting event loop\n"); wlm_cleanup(&ctx); } wl-mirror-0.17.0/src/mirror-dmabuf.c0000644000000000000000000002652414702736000014170 0ustar00#include #include #include #include #include #include #include static void dmabuf_frame_cleanup(dmabuf_mirror_backend_t * backend) { // destroy dmabuf frame object if (backend->dmabuf_frame != NULL) { zwlr_export_dmabuf_frame_v1_destroy(backend->dmabuf_frame); backend->dmabuf_frame = NULL; } // close dmabuf file descriptors for (unsigned int i = 0; i < backend->dmabuf.planes; i++) { if (backend->dmabuf.fds[i] != -1) close(backend->dmabuf.fds[i]); } free(backend->dmabuf.fds); free(backend->dmabuf.offsets); free(backend->dmabuf.strides); backend->dmabuf.width = 0; backend->dmabuf.height = 0; backend->dmabuf.drm_format = 0; backend->dmabuf.planes = 0; backend->dmabuf.fds = NULL; backend->dmabuf.offsets = NULL; backend->dmabuf.strides = NULL; backend->dmabuf.modifier = 0; } static void backend_cancel(dmabuf_mirror_backend_t * backend) { wlm_log_error("mirror-dmabuf::backend_cancel(): cancelling capture due to error\n"); dmabuf_frame_cleanup(backend); backend->state = STATE_CANCELED; backend->header.fail_count++; } // --- dmabuf_frame event handlers --- static void on_frame( void * data, struct zwlr_export_dmabuf_frame_v1 * frame, uint32_t width, uint32_t height, uint32_t x, uint32_t y, uint32_t buffer_flags, uint32_t frame_flags, uint32_t format, uint32_t mod_high, uint32_t mod_low, uint32_t num_objects ) { ctx_t * ctx = (ctx_t *)data; dmabuf_mirror_backend_t * backend = (dmabuf_mirror_backend_t *)ctx->mirror.backend; wlm_log_debug(ctx, "mirror-dmabuf::on_frame(): received %dx%d frame with %d objects\n", width, height, num_objects); if (backend->state != STATE_WAIT_FRAME) { wlm_log_error("mirror-dmabuf::on_frame(): got frame while in state %d\n", backend->state); backend_cancel(backend); return; } else if (num_objects > MAX_PLANES) { wlm_log_error("mirror-dmabuf::on_frame(): got frame with more than %d objects\n", MAX_PLANES); backend_cancel(backend); return; } uint32_t unhandled_buffer_flags = buffer_flags & ~( ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT ); if (unhandled_buffer_flags != 0) { wlm_log_warn("mirror-dmabuf::on_frame(): frame uses unhandled buffer flags, buffer_flags = {"); if (buffer_flags & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT) fprintf(stderr, "Y_INVERT, "); if (buffer_flags & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_INTERLACED) fprintf(stderr, "INTERLACED, "); if (buffer_flags & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_BOTTOM_FIRST) fprintf(stderr, "BOTTOM_FIRST, "); fprintf(stderr, "}\n"); } uint32_t unhandled_frame_flags = frame_flags & ~( ZWLR_EXPORT_DMABUF_FRAME_V1_FLAGS_TRANSIENT ); if (unhandled_frame_flags != 0) { wlm_log_warn("mirror-dmabuf::on_frame(): frame uses unhandled frame flags, frame_flags = {"); if (frame_flags & ZWLR_EXPORT_DMABUF_FRAME_V1_FLAGS_TRANSIENT) fprintf(stderr, "TRANSIENT, "); fprintf(stderr, "}\n"); } backend->dmabuf.planes = 0; backend->dmabuf.fds = malloc(num_objects * sizeof (int)); backend->dmabuf.offsets = malloc(num_objects * sizeof (uint32_t)); backend->dmabuf.strides = malloc(num_objects * sizeof (uint32_t)); backend->dmabuf.modifier = ((uint64_t)mod_high << 32) | mod_low; if (backend->dmabuf.fds == NULL || backend->dmabuf.offsets == NULL) { wlm_log_error("mirror-dmabuf::on_frame(): failed to allocate dmabuf storage\n"); backend_cancel(backend); return; } // save dmabuf frame info backend->x = x; backend->y = y; backend->buffer_flags = buffer_flags; backend->frame_flags = frame_flags; backend->dmabuf.width = width; backend->dmabuf.height = height; backend->dmabuf.drm_format = format; backend->dmabuf.planes = num_objects; wlm_log_debug(ctx, "mirror-dmabuf::on_frame(): w=%d h=%d gl_format=%x drm_format=%08x drm_modifier=%016lx\n", backend->dmabuf.width, backend->dmabuf.height, GL_RGB8_OES, backend->dmabuf.drm_format, backend->dmabuf.modifier ); for (size_t i = 0; i < num_objects; i++) { backend->dmabuf.fds[i] = -1; } // update dmabuf frame state machine backend->state = STATE_WAIT_OBJECTS; backend->processed_objects = 0; (void)frame; } static void on_object( void * data, struct zwlr_export_dmabuf_frame_v1 * frame, uint32_t index, int32_t fd, uint32_t size, uint32_t offset, uint32_t stride, uint32_t plane_index ) { ctx_t * ctx = (ctx_t *)data; dmabuf_mirror_backend_t * backend = (dmabuf_mirror_backend_t *)ctx->mirror.backend; wlm_log_debug(ctx, "mirror-dmabuf::on_object(): fd=%d offset=% 10d stride=% 10d\n", fd, offset, stride ); if (backend->state != STATE_WAIT_OBJECTS) { wlm_log_error("mirror-dmabuf::on_object(): got object while in state %d\n", backend->state); close(fd); backend_cancel(backend); return; } else if (index >= backend->dmabuf.planes) { wlm_log_error("mirror-dmabuf::on_object(): got object with out-of-bounds index %d\n", index); close(fd); backend_cancel(backend); return; } backend->dmabuf.fds[index] = fd; backend->dmabuf.offsets[index] = offset; backend->dmabuf.strides[index] = stride; backend->processed_objects++; if (backend->processed_objects == backend->dmabuf.planes) { backend->state = STATE_WAIT_READY; } (void)frame; (void)size; (void)plane_index; } static void on_ready( void * data, struct zwlr_export_dmabuf_frame_v1 * frame, uint32_t sec_hi, uint32_t sec_lo, uint32_t nsec ) { ctx_t * ctx = (ctx_t *)data; dmabuf_mirror_backend_t * backend = (dmabuf_mirror_backend_t *)ctx->mirror.backend; wlm_log_debug(ctx, "mirror-dmabuf::on_ready(): frame is ready\n"); if (backend->state != STATE_WAIT_READY) { wlm_log_error("dmabuf_frame: got ready while in state %d\n", backend->state); backend_cancel(backend); return; } if (!wlm_egl_dmabuf_to_texture(ctx, &backend->dmabuf)) { wlm_log_error("mirror-dmabuf::on_ready(): failed to import dmabuf\n"); backend_cancel(backend); } ctx->egl.format = GL_RGB8_OES; // FIXME: find out actual format ctx->egl.texture_region_aware = false; ctx->egl.texture_initialized = true; // set buffer flags only if changed bool invert_y = backend->buffer_flags & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; if (ctx->mirror.invert_y != invert_y) { ctx->mirror.invert_y = invert_y; wlm_egl_update_uniforms(ctx); } // set texture size and aspect ratio only if changed if (backend->dmabuf.width != ctx->egl.width || backend->dmabuf.height != ctx->egl.height) { ctx->egl.width = backend->dmabuf.width; ctx->egl.height = backend->dmabuf.height; wlm_egl_resize_viewport(ctx); } dmabuf_frame_cleanup(backend); backend->state = STATE_READY; backend->header.fail_count = 0; (void)frame; (void)sec_hi; (void)sec_lo; (void)nsec; } static void on_cancel( void * data, struct zwlr_export_dmabuf_frame_v1 * frame, enum zwlr_export_dmabuf_frame_v1_cancel_reason reason ) { ctx_t * ctx = (ctx_t *)data; dmabuf_mirror_backend_t * backend = (dmabuf_mirror_backend_t *)ctx->mirror.backend; wlm_log_debug(ctx, "mirror-dmabuf::on_cancel(): frame was canceled\n"); dmabuf_frame_cleanup(backend); backend->state = STATE_CANCELED; switch (reason) { case ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT: wlm_log_error("mirror-dmabuf::on_cancel(): permanent cancellation\n"); backend->header.fail_count++; break; case ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_TEMPORARY: wlm_log_error("mirror-dmabuf::on_cancel(): temporary cancellation\n"); backend->header.fail_count++; break; case ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_RESIZING: wlm_log_debug(ctx, "mirror-dmabuf::on_cancel(): cancellation due to output resize\n"); break; default: wlm_log_error("mirror-dmabuf::on_cancel(): unknown cancellation reason %d\n", reason); backend->header.fail_count++; break; } (void)frame; } static const struct zwlr_export_dmabuf_frame_v1_listener dmabuf_frame_listener = { .frame = on_frame, .object = on_object, .ready = on_ready, .cancel = on_cancel }; // --- backend event handlers --- static void do_capture(ctx_t * ctx) { dmabuf_mirror_backend_t * backend = (dmabuf_mirror_backend_t *)ctx->mirror.backend; if (backend->state == STATE_READY || backend->state == STATE_CANCELED) { // clear frame state for next frame backend->x = 0; backend->y = 0; backend->buffer_flags = 0; backend->frame_flags = 0; dmabuf_frame_cleanup(backend); backend->state = STATE_WAIT_FRAME; backend->processed_objects = 0; // create wlr_dmabuf_export_frame backend->dmabuf_frame = zwlr_export_dmabuf_manager_v1_capture_output( ctx->wl.dmabuf_manager, ctx->opt.show_cursor, ctx->mirror.current_target->output ); if (backend->dmabuf_frame == NULL) { wlm_log_error("mirror-dmabuf::do_capture(): failed to create wlr_dmabuf_export_frame\n"); wlm_mirror_backend_fail(ctx); return; } // add wlr_dmabuf_export_frame event listener // - for frame event // - for object event // - for ready event // - for cancel event zwlr_export_dmabuf_frame_v1_add_listener(backend->dmabuf_frame, &dmabuf_frame_listener, (void *)ctx); } } static void do_cleanup(ctx_t * ctx) { dmabuf_mirror_backend_t * backend = (dmabuf_mirror_backend_t *)ctx->mirror.backend; wlm_log_debug(ctx, "mirror-dmabuf::do_cleanup(): destroying mirror-dmabuf objects\n"); dmabuf_frame_cleanup(backend); free(backend); ctx->mirror.backend = NULL; } // --- init_mirror_dmabuf --- void wlm_mirror_dmabuf_init(ctx_t * ctx) { // check for required protocols if (ctx->wl.dmabuf_manager == NULL) { wlm_log_error("mirror-dmabuf::init(): missing wlr_export_dmabuf_manager protocol\n"); return; } // allocate backend context structure dmabuf_mirror_backend_t * backend = calloc(1, sizeof (dmabuf_mirror_backend_t)); if (backend == NULL) { wlm_log_error("mirror-dmabuf::init(): failed to allocate backend state\n"); return; } // initialize context structure backend->header.do_capture = do_capture; backend->header.do_cleanup = do_cleanup; backend->header.fail_count = 0; backend->dmabuf_frame = NULL; backend->x = 0; backend->y = 0; backend->buffer_flags = 0; backend->frame_flags = 0; backend->dmabuf.width = 0; backend->dmabuf.height = 0; backend->dmabuf.drm_format = 0; backend->dmabuf.planes = 0; backend->dmabuf.fds = NULL; backend->dmabuf.offsets = NULL; backend->dmabuf.strides = NULL; backend->dmabuf.modifier = 0; backend->state = STATE_READY; backend->processed_objects = 0; // set backend object as current backend ctx->mirror.backend = (mirror_backend_t *)backend; } wl-mirror-0.17.0/src/mirror-screencopy.c0000644000000000000000000003651314702736000015103 0ustar00#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include static void backend_cancel(screencopy_mirror_backend_t * backend) { wlm_log_error("mirror-screencopy::backend_cancel(): cancelling capture due to error\n"); // destroy screencopy frame object zwlr_screencopy_frame_v1_destroy(backend->screencopy_frame); backend->screencopy_frame = NULL; backend->state = STATE_CANCELED; backend->header.fail_count++; } typedef struct { uint32_t shm_format; uint32_t bpp; GLint gl_format; GLint gl_type; } shm_gl_format_t; static const shm_gl_format_t shm_gl_formats[] = { { .shm_format = WL_SHM_FORMAT_ARGB8888, .bpp = 32, .gl_format = GL_BGRA_EXT, .gl_type = GL_UNSIGNED_BYTE, }, { .shm_format = WL_SHM_FORMAT_XRGB8888, .bpp = 32, .gl_format = GL_BGRA_EXT, .gl_type = GL_UNSIGNED_BYTE, }, { .shm_format = WL_SHM_FORMAT_XBGR8888, .bpp = 32, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_BYTE, }, { .shm_format = WL_SHM_FORMAT_ABGR8888, .bpp = 32, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_BYTE, }, { .shm_format = WL_SHM_FORMAT_BGR888, .bpp = 24, .gl_format = GL_RGB, .gl_type = GL_UNSIGNED_BYTE, }, { .shm_format = WL_SHM_FORMAT_RGBX4444, .bpp = 16, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_SHORT_4_4_4_4, }, { .shm_format = WL_SHM_FORMAT_RGBA4444, .bpp = 16, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_SHORT_4_4_4_4, }, { .shm_format = WL_SHM_FORMAT_RGBX5551, .bpp = 16, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_SHORT_5_5_5_1, }, { .shm_format = WL_SHM_FORMAT_RGBA5551, .bpp = 16, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_SHORT_5_5_5_1, }, { .shm_format = WL_SHM_FORMAT_RGB565, .bpp = 16, .gl_format = GL_RGB, .gl_type = GL_UNSIGNED_SHORT_5_6_5, }, { .shm_format = WL_SHM_FORMAT_XBGR2101010, .bpp = 32, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, }, { .shm_format = WL_SHM_FORMAT_ABGR2101010, .bpp = 32, .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, }, { .shm_format = WL_SHM_FORMAT_XBGR16161616F, .bpp = 64, .gl_format = GL_RGBA, .gl_type = GL_HALF_FLOAT_OES, }, { .shm_format = WL_SHM_FORMAT_ABGR16161616F, .bpp = 64, .gl_format = GL_RGBA, .gl_type = GL_HALF_FLOAT_OES, }, { .shm_format = -1U, .bpp = -1U, .gl_format = -1, .gl_type = -1 } }; static const shm_gl_format_t * shm_gl_format_from_shm(uint32_t shm_format) { const shm_gl_format_t * format = shm_gl_formats; while (format->shm_format != -1U) { if (format->shm_format == shm_format) { return format; } format++; } return NULL; } // --- screencopy_frame event handlers --- static void on_buffer( void * data, struct zwlr_screencopy_frame_v1 * frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride ) { ctx_t * ctx = (ctx_t *)data; screencopy_mirror_backend_t * backend = (screencopy_mirror_backend_t *)ctx->mirror.backend; wlm_log_debug(ctx, "mirror-screencopy::on_buffer(): received buffer offer for %dx%d+%d frame\n", width, height, stride); if (backend->state != STATE_WAIT_BUFFER) { wlm_log_error("mirror-screencopy::on_buffer(): got buffer event while in state %d\n", backend->state); backend_cancel(backend); return; } size_t new_size = stride * height; if (new_size > backend->shm_size) { if (backend->shm_buffer != NULL) { wl_buffer_destroy(backend->shm_buffer); backend->shm_buffer = NULL; } if (ftruncate(backend->shm_fd, new_size) == -1) { wlm_log_error("mirror-screencopy::on_buffer(): failed to grow shm buffer\n"); backend_cancel(backend); return; } #if __linux__ void * new_addr = mremap(backend->shm_addr, backend->shm_size, new_size, MREMAP_MAYMOVE); if (new_addr == MAP_FAILED) { wlm_log_error("mirror-screencopy::on_buffer(): failed to remap shm buffer\n"); backend_cancel(backend); return; } #else void * new_addr = mmap(NULL, new_size, PROT_READ | PROT_WRITE, MAP_SHARED, backend->shm_fd, 0); if (new_addr == MAP_FAILED) { wlm_log_error("mirror-screencopy::on_buffer(): failed to map new shm buffer\n"); backend_cancel(backend); return; } else { munmap(backend->shm_addr, backend->shm_size); } #endif backend->shm_addr = new_addr; backend->shm_size = new_size; wl_shm_pool_resize(backend->shm_pool, new_size); } bool new_buffer_needed = backend->frame_width != width || backend->frame_height != height || backend->frame_stride != stride || backend->frame_format != format; if (backend->shm_buffer != NULL && new_buffer_needed) { wl_buffer_destroy(backend->shm_buffer); backend->shm_buffer = NULL; } if (backend->shm_buffer == NULL) { backend->shm_buffer = wl_shm_pool_create_buffer( backend->shm_pool, 0, width, height, stride, format ); if (backend->shm_buffer == NULL) { wlm_log_error("mirror-screencopy::on_buffer(): failed to create wl_buffer\n"); backend_cancel(backend); return; } } backend->frame_width = width; backend->frame_height = height; backend->frame_stride = stride; backend->frame_format = format; backend->state = STATE_WAIT_BUFFER_DONE; (void)frame; } static void on_linux_dmabuf( void * data, struct zwlr_screencopy_frame_v1 * frame, uint32_t format, uint32_t width, uint32_t height ) { (void)data; (void)frame; (void)format; (void)width; (void)height; } static void on_buffer_done( void * data, struct zwlr_screencopy_frame_v1 * frame ) { ctx_t * ctx = (ctx_t *)data; screencopy_mirror_backend_t * backend = (screencopy_mirror_backend_t *)ctx->mirror.backend; wlm_log_debug(ctx, "mirror-screencopy::on_buffer_done(): received buffer done event\n"); if (backend->state != STATE_WAIT_BUFFER_DONE) { wlm_log_error("mirror-screencopy::on_buffer_done(): received buffer_done without supported buffer offer\n"); backend_cancel(backend); return; } backend->state = STATE_WAIT_FLAGS; zwlr_screencopy_frame_v1_copy(backend->screencopy_frame, backend->shm_buffer); (void)frame; } static void on_damage( void * data, struct zwlr_screencopy_frame_v1 * frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height ) { (void)data; (void)frame; (void)x; (void)y; (void)width; (void)height; } static void on_flags( void * data, struct zwlr_screencopy_frame_v1 * frame, uint32_t flags ) { ctx_t * ctx = (ctx_t *)data; screencopy_mirror_backend_t * backend = (screencopy_mirror_backend_t *)ctx->mirror.backend; wlm_log_debug(ctx, "mirror-screencopy::on_flags(): received flags event\n"); if (backend->state != STATE_WAIT_FLAGS) { wlm_log_error("mirror-screencopy::on_flags(): received unexpected flags event\n"); backend_cancel(backend); return; } backend->frame_flags = flags; backend->state = STATE_WAIT_READY; (void)frame; } static void on_ready( void * data, struct zwlr_screencopy_frame_v1 * frame, uint32_t sec_hi, uint32_t sec_lo, uint32_t nsec ) { ctx_t * ctx = (ctx_t *)data; screencopy_mirror_backend_t * backend = (screencopy_mirror_backend_t *)ctx->mirror.backend; if (ctx->opt.verbose) { wlm_log_debug(ctx, "mirror-screencopy::on_ready(): received ready event with width: %d, height: %d, stride: %d, format: %c%c%c%c\n", backend->frame_width, backend->frame_height, backend->frame_stride, (backend->frame_format >> 24) & 0xff, (backend->frame_format >> 16) & 0xff, (backend->frame_format >> 8) & 0xff, (backend->frame_format >> 0) & 0xff ); } // find correct texture format const shm_gl_format_t * format = shm_gl_format_from_shm(backend->frame_format); if (format == NULL) { wlm_log_error("mirror-screencopy::on_ready(): failed to find GL format for shm format\n"); wlm_mirror_backend_fail(ctx); return; } // store frame data into texture glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, backend->frame_stride / (format->bpp / 8)); glTexImage2D(GL_TEXTURE_2D, 0, format->gl_format, backend->frame_width, backend->frame_height, 0, format->gl_format, format->gl_type, backend->shm_addr ); glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); ctx->egl.format = format->gl_format; ctx->egl.texture_region_aware = true; ctx->egl.texture_initialized = true; // set buffer flags bool invert_y = backend->frame_flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT; if (ctx->mirror.invert_y != invert_y) { ctx->mirror.invert_y = invert_y; wlm_egl_update_uniforms(ctx); } // set texture size and aspect ratio only if changed if (backend->frame_width != ctx->egl.width || backend->frame_height != ctx->egl.height) { ctx->egl.width = backend->frame_width; ctx->egl.height = backend->frame_height; wlm_egl_resize_viewport(ctx); } zwlr_screencopy_frame_v1_destroy(backend->screencopy_frame); backend->screencopy_frame = NULL; backend->state = STATE_READY; backend->header.fail_count = 0; (void)frame; (void)sec_hi; (void)sec_lo; (void)nsec; } static void on_failed( void * data, struct zwlr_screencopy_frame_v1 * frame ) { ctx_t * ctx = (ctx_t *)data; screencopy_mirror_backend_t * backend = (screencopy_mirror_backend_t *)ctx->mirror.backend; wlm_log_debug(ctx, "mirror-screencopy::on_failed(): received cancel event\n"); backend_cancel(backend); (void)frame; } static const struct zwlr_screencopy_frame_v1_listener screencopy_frame_listener = { .buffer = on_buffer, .linux_dmabuf = on_linux_dmabuf, .buffer_done = on_buffer_done, .damage = on_damage, .flags = on_flags, .ready = on_ready, .failed = on_failed }; // --- backend event handlers --- static void do_capture(ctx_t * ctx) { screencopy_mirror_backend_t * backend = (screencopy_mirror_backend_t *)ctx->mirror.backend; if (backend->state == STATE_READY || backend->state == STATE_CANCELED) { // clear frame state for next frame backend->frame_flags = 0; backend->state = STATE_WAIT_BUFFER; // create screencopy_frame if (ctx->opt.has_region) { backend->screencopy_frame = zwlr_screencopy_manager_v1_capture_output_region( ctx->wl.screencopy_manager, ctx->opt.show_cursor, ctx->mirror.current_target->output, ctx->mirror.current_target->x + ctx->mirror.current_region.x, ctx->mirror.current_target->y + ctx->mirror.current_region.y, ctx->mirror.current_region.width, ctx->mirror.current_region.height ); } else { backend->screencopy_frame = zwlr_screencopy_manager_v1_capture_output( ctx->wl.screencopy_manager, ctx->opt.show_cursor, ctx->mirror.current_target->output ); } if (backend->screencopy_frame == NULL) { wlm_log_error("do_capture: failed to create wlr_screencopy_frame\n"); wlm_mirror_backend_fail(ctx); } // add screencopy_frame event listener // - for buffer event // - for buffer_done event // - for flags event // - for ready event // - for failed event zwlr_screencopy_frame_v1_add_listener(backend->screencopy_frame, &screencopy_frame_listener, (void *)ctx); } } static void do_cleanup(ctx_t * ctx) { screencopy_mirror_backend_t * backend = (screencopy_mirror_backend_t *)ctx->mirror.backend; wlm_log_debug(ctx, "mirror-screencopy::do_cleanup(): destroying mirror-screencopy objects\n"); if (backend->screencopy_frame != NULL) zwlr_screencopy_frame_v1_destroy(backend->screencopy_frame); if (backend->shm_buffer != NULL) wl_buffer_destroy(backend->shm_buffer); if (backend->shm_pool != NULL) wl_shm_pool_destroy(backend->shm_pool); if (backend->shm_addr != NULL) munmap(backend->shm_addr, backend->shm_size); if (backend->shm_fd != -1) close(backend->shm_fd); free(backend); ctx->mirror.backend = NULL; } // --- init_mirror_screencopy --- void wlm_mirror_screencopy_init(ctx_t * ctx) { // check for required protocols if (ctx->wl.shm == NULL) { wlm_log_error("mirror-screencopy::init(): missing wl_shm protocol\n"); return; } else if (ctx->wl.screencopy_manager == NULL) { wlm_log_error("mirror-screencopy::init(): missing wlr_screencopy protocol\n"); return; } // allocate backend context structure screencopy_mirror_backend_t * backend = calloc(1, sizeof (screencopy_mirror_backend_t)); if (backend == NULL) { wlm_log_error("mirror-screencopy::init(): failed to allocate backend state\n"); return; } // initialize context structure backend->header.do_capture = do_capture; backend->header.do_cleanup = do_cleanup; backend->header.fail_count = 0; backend->shm_fd = -1; backend->shm_size = 0; backend->shm_addr = NULL; backend->shm_pool = NULL; backend->shm_buffer = NULL; backend->screencopy_frame = NULL; backend->frame_width = 0; backend->frame_height = 0; backend->frame_stride = 0; backend->frame_format = 0; backend->frame_flags = 0; backend->state = STATE_READY; // set backend object as current backend ctx->mirror.backend = (mirror_backend_t *)backend; // create shm fd backend->shm_fd = memfd_create("wl_shm_buffer", 0); if (backend->shm_fd == -1) { wlm_log_error("mirror-screencopy::init(): failed to create shm buffer\n"); wlm_mirror_backend_fail(ctx); } // resize shm fd to nonempty size backend->shm_size = 1; if (ftruncate(backend->shm_fd, backend->shm_size) == -1) { wlm_log_error("mirror-screencopy::init(): failed to resize shm buffer\n"); wlm_mirror_backend_fail(ctx); } // map shm fd backend->shm_addr = mmap(NULL, backend->shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, backend->shm_fd, 0); if (backend->shm_addr == MAP_FAILED) { backend->shm_addr = NULL; wlm_log_error("mirror-screencopy::init(): failed to map shm buffer\n"); wlm_mirror_backend_fail(ctx); } // create shm pool from shm fd backend->shm_pool = wl_shm_create_pool(ctx->wl.shm, backend->shm_fd, backend->shm_size); if (backend->shm_pool == NULL) { wlm_log_error("mirror-screencopy::init(): failed to create shm pool\n"); wlm_mirror_backend_fail(ctx); } } wl-mirror-0.17.0/src/mirror.c0000644000000000000000000002074414702736000012732 0ustar00#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include // --- frame_callback event handlers --- static const struct wl_callback_listener frame_callback_listener; static void on_frame( void * data, struct wl_callback * frame_callback, uint32_t msec ) { ctx_t * ctx = (ctx_t *)data; // destroy frame callback wl_callback_destroy(ctx->mirror.frame_callback); ctx->mirror.frame_callback = NULL; // don't attempt to render if window is already closing if (ctx->wl.closing) { return; } // add new frame callback listener // the wayland spec says you cannot reuse the old frame callback ctx->mirror.frame_callback = wl_surface_frame(ctx->wl.surface); wl_callback_add_listener(ctx->mirror.frame_callback, &frame_callback_listener, (void *)ctx); if (ctx->mirror.backend != NULL) { // check if backend failure count exceeded if (ctx->mirror.backend->fail_count >= MIRROR_BACKEND_FATAL_FAILCOUNT) { wlm_mirror_backend_fail(ctx); } if (!ctx->opt.freeze) { // request new screen capture from backend ctx->mirror.backend->do_capture(ctx); } } // wait for events // - screencapture events from backend wl_display_roundtrip(ctx->wl.display); // render frame, set swap interval to 0 to ensure nonblocking buffer swap wlm_egl_draw_texture(ctx); eglSwapInterval(ctx->egl.display, 0); if (eglSwapBuffers(ctx->egl.display, ctx->egl.surface) != EGL_TRUE) { wlm_log_error("mirror::on_frame(): failed to swap buffers\n"); wlm_exit_fail(ctx); } (void)frame_callback; (void)msec; } static const struct wl_callback_listener frame_callback_listener = { .done = on_frame }; // --- init_mirror --- void wlm_mirror_init(ctx_t * ctx) { // initialize context structure ctx->mirror.current_target = NULL; ctx->mirror.frame_callback = NULL; ctx->mirror.current_region = (region_t){ .x = 0, .y = 0, .width = 0, .height = 0 }; ctx->mirror.invert_y = false; ctx->mirror.backend = NULL; ctx->mirror.auto_backend_index = 0; ctx->mirror.initialized = true; // finding target output if (!wlm_opt_find_output(ctx, &ctx->mirror.current_target, &ctx->mirror.current_region)) { wlm_log_error("mirror::init(): failed to find output\n"); wlm_exit_fail(ctx); } // update window title wlm_mirror_update_title(ctx); // add frame callback listener ctx->mirror.frame_callback = wl_surface_frame(ctx->wl.surface); wl_callback_add_listener(ctx->mirror.frame_callback, &frame_callback_listener, (void *)ctx); } // --- auto backend handler typedef struct { char * name; void (*init)(ctx_t * ctx); } fallback_backend_t; static fallback_backend_t auto_fallback_backends[] = { { "dmabuf", wlm_mirror_dmabuf_init }, { "screencopy", wlm_mirror_screencopy_init }, { NULL, NULL } }; static void auto_backend_fallback(ctx_t * ctx) { while (true) { // get next backend size_t index = ctx->mirror.auto_backend_index; fallback_backend_t * next_backend = &auto_fallback_backends[index]; if (next_backend->name == NULL) { wlm_log_error("mirror::auto_backend_fallback(): no working backend found, exiting\n"); wlm_exit_fail(ctx); } if (index > 0) { wlm_log_warn("mirror::auto_backend_fallback(): falling back to backend %s\n", next_backend->name); } else { wlm_log_debug(ctx, "mirror::auto_backend_fallback(): selecting backend %s\n", next_backend->name); } // uninitialize previous backend if (ctx->mirror.backend != NULL) ctx->mirror.backend->do_cleanup(ctx); // initialize next backend next_backend->init(ctx); // increment backend index for next attempt ctx->mirror.auto_backend_index++; // break if backend loading succeeded if (ctx->mirror.backend != NULL) break; } } // --- init_mirror_backend --- void wlm_mirror_backend_init(ctx_t * ctx) { if (ctx->mirror.backend != NULL) ctx->mirror.backend->do_cleanup(ctx); switch (ctx->opt.backend) { case BACKEND_AUTO: auto_backend_fallback(ctx); break; case BACKEND_DMABUF: wlm_mirror_dmabuf_init(ctx); break; case BACKEND_SCREENCOPY: wlm_mirror_screencopy_init(ctx); break; } if (ctx->mirror.backend == NULL) wlm_exit_fail(ctx); } // --- output_removed --- void wlm_mirror_output_removed(ctx_t * ctx, output_list_node_t * node) { if (!ctx->mirror.initialized) return; if (ctx->mirror.current_target == NULL) return; if (ctx->mirror.current_target != node) return; wlm_log_error("mirror::output_removed(): output disappeared, closing\n"); wlm_exit_fail(ctx); } // --- update_title --- typedef struct { const char *specifier; char type; union { int d; const char *s; }; } specifier_t; static size_t specifier_str(ctx_t * ctx, char *dst, int n, specifier_t specifier) { switch (specifier.type) { case 'd': return snprintf(dst, n, "%d", specifier.d); case 's': return snprintf(dst, n, "%s", specifier.s); default: wlm_log_error("mirror::specifier_str(): unrecognized format type '%d'", specifier.type); wlm_exit_fail(ctx); } } static int format_title(ctx_t * ctx, char ** dst, char * fmt) { specifier_t replacements[] = { {"{x}", 'd', {.d = ctx->mirror.current_region.x}}, {"{y}", 'd', {.d = ctx->mirror.current_region.y}}, {"{width}", 'd', {.d = ctx->mirror.current_region.width}}, {"{height}", 'd', {.d = ctx->mirror.current_region.height}}, {"{target_width}", 'd', {.d = ctx->mirror.current_target->width}}, {"{target_height}", 'd', {.d = ctx->mirror.current_target->height}}, {"{target_output}", 's', {.s = ctx->mirror.current_target->name}} }; size_t length = strlen(fmt); for (size_t f = 0; f < ARRAY_LENGTH(replacements); f++) { const char *fmt_cursor = fmt; size_t spec_len = strlen(replacements[f].specifier); while ((fmt_cursor = strstr(fmt_cursor, replacements[f].specifier))) { length += specifier_str(ctx, NULL, 0, replacements[f]) - spec_len; fmt_cursor += spec_len; } } *dst = (char *) calloc(++length, sizeof(char)); if (*dst == NULL) { wlm_log_error("mirror::format_title(): failed to allocate memory\n"); wlm_exit_fail(ctx); } char *dst_cursor = *dst; const char *fmt_cursor = fmt; while (*fmt_cursor) { bool replaced = false; for (size_t f = 0; f < ARRAY_LENGTH(replacements); f++) { size_t spec_len = strlen(replacements[f].specifier); if (strncmp(fmt_cursor, replacements[f].specifier, spec_len) == 0) { size_t written = specifier_str(ctx, dst_cursor, length, replacements[f]); dst_cursor += written; fmt_cursor += spec_len; length -= written; replaced = true; break; } } if (!replaced) { *dst_cursor++ = *fmt_cursor++; length--; } } *dst_cursor = '\0'; return 0; } void wlm_mirror_update_title(ctx_t * ctx) { char * title = NULL; char * title_fmt = "Wayland Output Mirror for {target_output}"; if (ctx->opt.window_title != NULL) { title_fmt = ctx->opt.window_title; } int status = format_title(ctx, &title, title_fmt); if (status == -1) { wlm_log_error("mirror::update_title(): failed to format window title\n"); wlm_exit_fail(ctx); } wlm_wayland_window_set_title(ctx, title); free(title); } // --- backend_fail --- void wlm_mirror_backend_fail(ctx_t * ctx) { if (ctx->opt.backend == BACKEND_AUTO) { auto_backend_fallback(ctx); } else { wlm_exit_fail(ctx); } } // --- cleanup_mirror --- void wlm_mirror_cleanup(ctx_t * ctx) { if (!ctx->mirror.initialized) return; wlm_log_debug(ctx, "mirror::cleanup(): destroying mirror objects\n"); if (ctx->mirror.backend != NULL) ctx->mirror.backend->do_cleanup(ctx); if (ctx->mirror.frame_callback != NULL) wl_callback_destroy(ctx->mirror.frame_callback); ctx->mirror.initialized = false; } wl-mirror-0.17.0/src/options.c0000644000000000000000000006531714702736000013120 0ustar00#include #include #include #include void wlm_opt_init(ctx_t * ctx) { ctx->opt.verbose = false; ctx->opt.stream = false; ctx->opt.show_cursor = true; ctx->opt.invert_colors = false; ctx->opt.freeze = false; ctx->opt.has_region = false; ctx->opt.fullscreen = false; ctx->opt.scaling = SCALE_FIT; ctx->opt.scaling_filter = SCALE_FILTER_LINEAR; ctx->opt.backend = BACKEND_AUTO; ctx->opt.transform = (transform_t){ .rotation = ROT_NORMAL, .flip_x = false, .flip_y = false }; ctx->opt.region = (region_t){ .x = 0, .y = 0, .width = 0, .height = 0 }; ctx->opt.output = NULL; ctx->opt.fullscreen_output = NULL; ctx->opt.window_title = NULL; } void wlm_cleanup_opt(ctx_t * ctx) { if (ctx->opt.output != NULL) free(ctx->opt.output); if (ctx->opt.fullscreen_output != NULL) free(ctx ->opt.fullscreen_output); } bool wlm_opt_parse_scaling(scale_t * scaling, scale_filter_t * scaling_filter, const char * scaling_arg) { if (strcmp(scaling_arg, "f") == 0 || strcmp(scaling_arg, "fit") == 0) { *scaling = SCALE_FIT; return true; } else if (strcmp(scaling_arg, "c") == 0 || strcmp(scaling_arg, "cover") == 0) { *scaling = SCALE_COVER; return true; } else if (strcmp(scaling_arg, "e") == 0 || strcmp(scaling_arg, "exact") == 0) { *scaling = SCALE_EXACT; return true; } else if (strcmp(scaling_arg, "l") == 0 || strcmp(scaling_arg, "linear") == 0) { *scaling_filter = SCALE_FILTER_LINEAR; return true; } else if (strcmp(scaling_arg, "n") == 0 || strcmp(scaling_arg, "nearest") == 0) { *scaling_filter = SCALE_FILTER_NEAREST; return true; } else { return false; } } bool wlm_opt_parse_backend(backend_t * backend, const char * backend_arg) { if (strcmp(backend_arg, "auto") == 0) { *backend = BACKEND_AUTO; return true; } else if (strcmp(backend_arg, "dmabuf") == 0) { *backend = BACKEND_DMABUF; return true; } else if (strcmp(backend_arg, "screencopy") == 0) { *backend = BACKEND_SCREENCOPY; return true; } else { return false; } } bool wlm_opt_parse_transform(transform_t * transform, const char * transform_arg) { transform_t local_transform = { .rotation = ROT_NORMAL, .flip_x = false, .flip_y = false }; if (strcmp(transform_arg, "normal") == 0) { *transform = local_transform; return true; } char * transform_str = strdup(transform_arg); if (transform_str == NULL) { wlm_log_error("options::parse_transform_option(): failed to allocate copy of transform argument\n"); return false; } bool success = true; bool has_rotation = false; char * transform_spec = strtok(transform_str, "-"); while (transform_spec != NULL) { if (strcmp(transform_spec, "normal") == 0) { wlm_log_error("options::parse_transform_option(): %s must be the only transform specifier\n", transform_spec); success = false; break; } else if (strcmp(transform_spec, "flipX") == 0 || strcmp(transform_spec, "flipped") == 0) { if (local_transform.flip_x) { wlm_log_error("options::parse_transform_option(): duplicate flip specifier %s\n", transform_spec); success = false; break; } local_transform.flip_x = true; } else if (strcmp(transform_spec, "flipY") == 0) { if (local_transform.flip_y) { wlm_log_error("options::parse_transform_option(): duplicate flip specifier %s\n", transform_spec); success = false; break; } local_transform.flip_y = true; } else if (strcmp(transform_spec, "0") == 0 || strcmp(transform_spec, "0cw") == 0 || strcmp(transform_spec, "0ccw") == 0) { if (has_rotation) { wlm_log_error("options::parse_transform_option(): duplicate rotation specifier %s\n", transform_spec); success = false; break; } has_rotation = true; local_transform.rotation = ROT_NORMAL; } else if (strcmp(transform_spec, "90") == 0 || strcmp(transform_spec, "90cw") == 0 || strcmp(transform_spec, "270ccw") == 0) { if (has_rotation) { wlm_log_error("options::parse_transform_option(): duplicate rotation specifier %s\n", transform_spec); success = false; break; } has_rotation = true; local_transform.rotation = ROT_CW_90; } else if (strcmp(transform_spec, "180") == 0 || strcmp(transform_spec, "180cw") == 0 || strcmp(transform_spec, "180ccw") == 0) { if (has_rotation) { wlm_log_error("options::parse_transform_option(): duplicate rotation specifier %s\n", transform_spec); success = false; break; } has_rotation = true; local_transform.rotation = ROT_CW_180; } else if (strcmp(transform_spec, "270") == 0 || strcmp(transform_spec, "270cw") == 0 || strcmp(transform_spec, "90ccw") == 0) { if (has_rotation) { wlm_log_error("options::parse_transform_option(): duplicate rotation specifier %s\n", transform_spec); success = false; break; } has_rotation = true; local_transform.rotation = ROT_CW_270; } else { wlm_log_error("options::parse_transform_option(): invalid transform specifier %s\n", transform_spec); success = false; break; } transform_spec = strtok(NULL, "-"); } if (success) { *transform = local_transform; } free(transform_str); return success; } bool wlm_opt_parse_region(region_t * region, char ** output, const char * region_arg) { region_t local_region = { .x = 0, .y = 0, .width = 0, .height = 0 }; char * region_str = strdup(region_arg); if (region_str == NULL) { wlm_log_error("options::parse_region_option(): failed to allocate copy of region argument\n"); return false; } char * position = strtok(region_str, " "); char * size = strtok(NULL, " "); char * output_label = strtok(NULL, " "); if (position == NULL) { wlm_log_error("options::parse_region_option(): missing region position\n"); free(region_str); return false; } char * x = strtok(position, ","); char * y = strtok(NULL, ","); char * rest = strtok(NULL, ","); if (x == NULL) { wlm_log_error("options::parse_region_option(): missing x position\n"); free(region_str); return false; } else if (y == NULL) { wlm_log_error("options::parse_region_option(): missing y position\n"); free(region_str); return false; } else if (rest != NULL) { wlm_log_error("options::parse_region_option(): unexpected position component %s\n", rest); free(region_str); return false; } char * end = NULL; local_region.x = strtoul(x, &end, 10); if (*end != '\0') { wlm_log_error("options::parse_region_option(): invalid x position %s\n", x); free(region_str); return false; } end = NULL; local_region.y = strtoul(y, &end, 10); if (*end != '\0') { wlm_log_error("options::parse_region_option(): invalid y position %s\n", y); free(region_str); return false; } if (size == NULL) { wlm_log_error("options::parse_region_option(): missing size\n"); free(region_str); return false; } char * width = strtok(size, "x"); char * height = strtok(NULL, "x"); rest = strtok(NULL, "x"); if (width == NULL) { wlm_log_error("options::parse_region_option(): missing width\n"); free(region_str); return false; } else if (height == NULL) { wlm_log_error("options::parse_region_option(): missing height\n"); free(region_str); return false; } else if (rest != NULL) { wlm_log_error("options::parse_region_option(): unexpected size component %s\n", rest); free(region_str); return false; } end = NULL; local_region.width = strtoul(width, &end, 10); if (*end != '\0') { wlm_log_error("options::parse_region_option(): invalid width %s\n", width); free(region_str); return false; } else if (local_region.width == 0) { wlm_log_error("options::parse_region_option(): invalid width %d\n", local_region.width); free(region_str); return false; } end = NULL; local_region.height = strtoul(height, &end, 10); if (*end != '\0') { wlm_log_error("options::parse_region_option(): invalid height %s\n", height); free(region_str); return false; } else if (local_region.height == 0) { wlm_log_error("options::parse_region_option(): invalid height %d\n", local_region.height); free(region_str); return false; } if (output_label != NULL) { *output = strdup(output_label); if (*output == NULL) { wlm_log_error("options::parse_region_option(): failed to allocate copy of output name\n"); free(region_str); return false; } } *region = local_region; free(region_str); return true; } bool wlm_opt_find_output(ctx_t * ctx, output_list_node_t ** output_handle, region_t * region_handle) { char * output_name = ctx->opt.output; output_list_node_t * local_output_handle = NULL; region_t local_region = (region_t){ .x = 0, .y = 0, .width = 0, .height = 0 }; if (ctx->opt.output != NULL) { wlm_log_debug(ctx, "options::find_output(): searching for output by name\n"); output_list_node_t * cur = ctx->wl.outputs; while (cur != NULL) { if (cur->name != NULL && strcmp(cur->name, ctx->opt.output) == 0) { local_output_handle = cur; output_name = cur->name; break; } cur = cur->next; } } else if (ctx->opt.has_region) { wlm_log_debug(ctx, "options::find_output(): searching for output by region\n"); output_list_node_t * cur = ctx->wl.outputs; while (cur != NULL) { region_t output_region = { .x = cur->x, .y = cur->y, .width = cur->width, .height = cur->height }; if (wlm_util_region_contains(&ctx->opt.region, &output_region)) { local_output_handle = cur; output_name = cur->name; break; } cur = cur->next; } } if (local_output_handle == NULL && ctx->opt.output != NULL) { wlm_log_error("options::find_output(): output %s not found\n", ctx->opt.output); return false; } else if (local_output_handle == NULL && ctx->opt.has_region) { wlm_log_error("options::find_output(): output for region not found\n"); return false; } else if (local_output_handle == NULL) { wlm_log_error("options::find_output(): no output or region specified\n"); return false; } else { wlm_log_debug(ctx, "options::find_output(): found output with name %s\n", output_name); } if (ctx->opt.has_region) { wlm_log_debug(ctx, "options::find_output(): checking if region in output\n"); region_t output_region = { .x = local_output_handle->x, .y = local_output_handle->y, .width = local_output_handle->width, .height = local_output_handle->height }; if (!wlm_util_region_contains(&ctx->opt.region, &output_region)) { wlm_log_error("options::find_output(): output does not contain region\n"); return false; } wlm_log_debug(ctx, "options::find_output(): clamping region to output bounds\n"); local_region = ctx->opt.region; wlm_util_region_clamp(&local_region, &output_region); } *output_handle = local_output_handle; *region_handle = local_region; return true; } void wlm_opt_usage(ctx_t * ctx) { printf("usage: wl-mirror [options] \n"); printf("\n"); printf("options:\n"); printf(" -h, --help show this help\n"); printf(" -V, --version print version\n"); printf(" -v, --verbose enable debug logging\n"); printf(" --no-verbose disable debug logging (default)\n"); printf(" -c, --show-cursor show the cursor on the mirrored screen (default)\n"); printf(" --no-show-cursor don't show the cursor on the mirrored screen\n"); printf(" -i, --invert-colors invert colors in the mirrored screen\n"); printf(" --no-invert-colors don't invert colors in the mirrored screen (default)\n"); printf(" -f, --freeze freeze the current image on the screen\n"); printf(" --unfreeze resume the screen capture after a freeze\n"); printf(" --toggle-freeze toggle freeze state of screen capture\n"); printf(" -F, --fullscreen open wl-mirror as fullscreen\n"); printf(" --no-fullscreen open wl-mirror as a window (default)\n"); printf(" --fullscreen-output O open wl-mirror as fullscreen on output O\n"); printf(" --no-fullscreen-output open wl-mirror as fullscreen on the current output (default)\n"); printf(" -s f, --scaling fit scale to fit (default)\n"); printf(" -s c, --scaling cover scale to cover, cropping if needed\n"); printf(" -s e, --scaling exact only scale to exact multiples of the output size\n"); printf(" -s l, --scaling linear use linear scaling (default)\n"); printf(" -s n, --scaling nearest use nearest neighbor scaling\n"); printf(" -b B --backend B use a specific backend for capturing the screen\n"); printf(" -t T, --transform T apply custom transform T\n"); printf(" -r R, --region R capture custom region R\n"); printf(" --no-region capture the entire output (default)\n"); printf(" -S, --stream accept a stream of additional options on stdin\n"); printf(" --title N specify a custom title N for the mirror window\n"); printf("\n"); printf("backends:\n"); printf(" - auto automatically try the backends in order and use the first that works (default)\n"); printf(" - dmabuf use the wlr-export-dmabuf-unstable-v1 protocol to capture outputs\n"); printf(" - screencopy use the wlr-screencopy-unstable-v1 protocol to capture outputs\n"); printf("\n"); printf("transforms:\n"); printf(" transforms are specified as a dash-separated list of flips followed by a rotation\n"); printf(" flips are applied before rotations\n"); printf(" - normal no transformation\n"); printf(" - flipX, flipY flip the X or Y coordinate\n"); printf(" - 0cw, 90cw, 180cw, 270cw apply a clockwise rotation\n"); printf(" - 0ccw, 90ccw, 180ccw, 270ccw apply a counter-clockwise rotation\n"); printf(" the following transformation options are provided for compatibility with sway output transforms\n"); printf(" - flipped flip the X coordinate\n"); printf(" - 0, 90, 180, 270 apply a clockwise rotation\n"); printf("\n"); printf("regions:\n"); printf(" regions are specified in the format used by the slurp utility\n"); printf(" - ', x [output]'\n"); printf(" on start, the region is translated into output coordinates\n"); printf(" when the output moves, the captured region moves with it\n"); printf(" when a region is specified, the argument is optional\n"); printf("\n"); printf("stream mode:\n"); printf(" in stream mode, wl-mirror interprets lines on stdin as additional command line options\n"); printf(" - arguments can be quoted with single or double quotes, but every argument must be fully\n"); printf(" quoted or fully unquoted\n"); printf(" - unquoted arguments are split on whitespace\n"); printf(" - no escape sequences are implemented\n"); printf("\n"); printf("title placeholders:\n"); printf(" the title string supports the following placeholders:\n"); printf(" - {width}, {height}: size of the mirrored area\n"); printf(" - {x}, {y}: offsets on the screen\n"); printf(" - {target_width}, {target_height}\n"); printf(" {target_output}: info about the mirrored device\n"); printf(" a few perhaps useful examples:\n"); printf(" --title='Wayland Mirror Output {target_output}'\n"); printf(" --title='{target_output}:{width}x{height}+{x}+{y}'\n"); printf(" --title='resize set {width} {height} move position {x} {y}'\n"); wlm_cleanup(ctx); exit(0); } void wlm_opt_version(ctx_t * ctx) { printf("wl-mirror %s\n", WLM_VERSION); wlm_cleanup(ctx); exit(0); } void wlm_opt_parse(ctx_t * ctx, int argc, char ** argv) { bool is_cli_args = !ctx->opt.stream; bool was_frozen = ctx->opt.freeze; bool was_fullscreen = ctx->opt.fullscreen; bool new_backend = false; bool new_region = false; bool new_output = false; bool new_fullscreen_output = false; char * region_output = NULL; char * arg_output = NULL; while (argc > 0 && argv[0][0] == '-') { if (is_cli_args && (strcmp(argv[0], "-h") == 0 || strcmp(argv[0], "--help") == 0)) { wlm_opt_usage(ctx); } else if (strcmp(argv[0], "-V") == 0 || strcmp(argv[0], "--version") == 0) { wlm_opt_version(ctx); } else if (strcmp(argv[0], "-v") == 0 || strcmp(argv[0], "--verbose") == 0) { ctx->opt.verbose = true; } else if (strcmp(argv[0], "--no-verbose") == 0) { ctx->opt.verbose = false; } else if (strcmp(argv[0], "-c") == 0 || strcmp(argv[0], "--show-cursor") == 0) { ctx->opt.show_cursor = true; } else if (strcmp(argv[0], "--no-show-cursor") == 0) { ctx->opt.show_cursor = false; } else if (strcmp(argv[0], "-n") == 0) { wlm_log_warn("options::parse(): -n is deprecated, use --no-show-cursor\n"); ctx->opt.show_cursor = false; } else if (strcmp(argv[0], "-i") == 0 || strcmp(argv[0], "--invert-colors") == 0) { ctx->opt.invert_colors = true; } else if (strcmp(argv[0], "--no-invert-colors") == 0) { ctx->opt.invert_colors = false; } else if (strcmp(argv[0], "-f") == 0 || strcmp(argv[0], "--freeze") == 0) { ctx->opt.freeze = true; } else if (strcmp(argv[0], "--unfreeze") == 0) { ctx->opt.freeze = false; } else if (strcmp(argv[0], "--toggle-freeze") == 0) { ctx->opt.freeze ^= 1; } else if (strcmp(argv[0], "-F") == 0 || strcmp(argv[0], "--fullscreen") == 0) { ctx->opt.fullscreen = true; } else if (strcmp(argv[0], "--no-fullscreen") == 0) { ctx->opt.fullscreen = false; } else if (strcmp(argv[0], "--fullscreen-output") == 0) { if (argc < 2) { wlm_log_error("options::parse(): option %s requires an argument\n", argv[0]); if (is_cli_args) wlm_exit_fail(ctx); } else { free(ctx->opt.fullscreen_output); ctx->opt.fullscreen = true; ctx->opt.fullscreen_output = strdup(argv[1]); new_fullscreen_output = true; argv++; argc--; } } else if (strcmp(argv[0], "--no-fullscreen-output") == 0) { free(ctx->opt.fullscreen_output); ctx->opt.fullscreen_output = NULL; new_fullscreen_output = true; } else if (strcmp(argv[0], "-s") == 0 || strcmp(argv[0], "--scaling") == 0) { if (argc < 2) { wlm_log_error("options::parse(): option %s requires an argument\n", argv[0]); if (is_cli_args) wlm_exit_fail(ctx); } else { if (!wlm_opt_parse_scaling(&ctx->opt.scaling, &ctx->opt.scaling_filter, argv[1])) { wlm_log_error("options::parse(): invalid scaling mode %s\n", argv[1]); if (is_cli_args) wlm_exit_fail(ctx); } argv++; argc--; } } else if (strcmp(argv[0], "-b") == 0 || strcmp(argv[0], "--backend") == 0) { if (argc < 2) { wlm_log_error("options::parse(): option %s requires an argument\n", argv[0]); if (is_cli_args) wlm_exit_fail(ctx); } else { if (!wlm_opt_parse_backend(&ctx->opt.backend, argv[1])) { wlm_log_error("options::parse(): invalid backend %s\n", argv[1]); if (is_cli_args) wlm_exit_fail(ctx); } new_backend = true; argv++; argc--; } } else if (strcmp(argv[0], "-t") == 0 || strcmp(argv[0], "--transform") == 0) { if (argc < 2) { wlm_log_error("options::parse(): option %s requires an argument\n", argv[0]); if (is_cli_args) wlm_exit_fail(ctx); } else { if (!wlm_opt_parse_transform(&ctx->opt.transform, argv[1])) { wlm_log_error("options::parse(): invalid transform %s\n", argv[1]); if (is_cli_args) wlm_exit_fail(ctx); } argv++; argc--; } } else if (strcmp(argv[0], "-r") == 0 || strcmp(argv[0], "--region") == 0) { if (argc < 2) { wlm_log_error("options::parse(): option %s requires an argument\n", argv[0]); if (is_cli_args) wlm_exit_fail(ctx); } else { char * new_region_output = NULL; if (!wlm_opt_parse_region(&ctx->opt.region, &new_region_output, argv[1])) { wlm_log_error("options::parse(): invalid region %s\n", argv[1]); if (is_cli_args) wlm_exit_fail(ctx); } else { ctx->opt.has_region = true; free(region_output); region_output = new_region_output; new_region = true; } argv++; argc--; } } else if (strcmp(argv[0], "--no-region") == 0) { ctx->opt.has_region = false; ctx->opt.region = (region_t){ .x = 0, .y = 0, .width = 0, .height = 0 }; } else if (strcmp(argv[0], "-S") == 0 || strcmp(argv[0], "--stream") == 0) { ctx->opt.stream = true; } else if (strcmp(argv[0], "--title") == 0) { if (argc < 2) { wlm_log_error("options::parse(): option %s requires an argument\n", argv[0]); if (is_cli_args) wlm_exit_fail(ctx); } else { if (strlen(argv[1]) <= 0) { wlm_log_error("options::parse(): invalid empty title\n"); if (is_cli_args) wlm_exit_fail(ctx); } else { ctx->opt.window_title = argv[1]; } argv++; argc--; } } else if (strcmp(argv[0], "--") == 0) { argv++; argc--; break; } else { wlm_log_error("options::parse(): invalid option %s\n", argv[0]); if (is_cli_args) wlm_exit_fail(ctx); } argv++; argc--; } if (argc > 0) { arg_output = strdup(argv[0]); if (arg_output == NULL) { wlm_log_error("options::parse(): failed to allocate copy of output name\n"); if (is_cli_args) wlm_exit_fail(ctx); } else { new_output = true; } } if (new_output || new_region) { free(ctx->opt.output); ctx->opt.output = NULL; } if (new_output && !new_region) { ctx->opt.has_region = false; ctx->opt.region = (region_t){ .x = 0, .y = 0, .width = 0, .height = 0 }; } if (!new_output && new_region && region_output == NULL) { // output implicitly defined by region ctx->opt.output = NULL; } else if (!new_output && new_region && region_output != NULL) { // output explicitly defined by region ctx->opt.output = region_output; } else if (new_output && new_region && region_output == NULL) { // output defined by argument // region must be in this output ctx->opt.output = arg_output; } else if (new_output && new_region && region_output != NULL) { // output defined by both region and argument // must be the same // region must be in this output if (strcmp(region_output, arg_output) != 0) { wlm_log_error("options::parse(): region and argument output differ: %s vs %s\n", region_output, arg_output); if (is_cli_args) wlm_exit_fail(ctx); } ctx->opt.output = region_output; } else if (new_output && !new_region) { // output defined by argument ctx->opt.output = arg_output; } else if (!new_output && !new_region && is_cli_args) { // no output or region specified wlm_opt_usage(ctx); } if ( ctx->opt.output != NULL && ctx->opt.fullscreen_output != NULL && strcmp(ctx->opt.output, ctx->opt.fullscreen_output) == 0 ) { wlm_log_error("options::parse(): fullscreen_output cannot be same as the output to be mirrored"); wlm_exit_fail(ctx); } if (argc > 1) { wlm_log_error("options::parse(): unexpected trailing arguments after output name\n"); if (is_cli_args) wlm_exit_fail(ctx); } if (!is_cli_args && ctx->opt.fullscreen && (!was_fullscreen || new_fullscreen_output)) { wlm_wayland_window_set_fullscreen(ctx); } else if (!is_cli_args && !ctx->opt.fullscreen && was_fullscreen) { wlm_wayland_window_unset_fullscreen(ctx); } output_list_node_t * target_output = NULL; region_t target_region = (region_t){ .x = 0, .y = 0, .width = 0, .height = 0 }; if (!is_cli_args && wlm_opt_find_output(ctx, &target_output, &target_region)) { ctx->mirror.current_target = target_output; ctx->mirror.current_region = target_region; } if (!is_cli_args && new_backend) { wlm_mirror_backend_init(ctx); } if (!is_cli_args && !was_frozen && ctx->opt.freeze) { wlm_egl_freeze_framebuffer(ctx); } if (!is_cli_args) { wlm_egl_update_uniforms(ctx); wlm_mirror_update_title(ctx); } } wl-mirror-0.17.0/src/stream.c0000644000000000000000000001261214702736000012706 0ustar00#include #include #include #include #include #include #include #define ARGS_MIN_CAP 8 static void args_push(ctx_t * ctx, char * arg) { if (ctx->stream.args_len == ctx->stream.args_cap) { size_t new_cap = ctx->stream.args_cap * 2; if (new_cap == 0) new_cap = ARGS_MIN_CAP; char ** new_args = realloc(ctx->stream.args, sizeof (char *) * new_cap); if (new_args == NULL) { wlm_log_error("event::args_push(): failed to grow args array for option stream line\n"); wlm_exit_fail(ctx); } ctx->stream.args = new_args; ctx->stream.args_cap = new_cap; } ctx->stream.args[ctx->stream.args_len++] = arg; } #define LINE_MIN_RESERVE 1024 static void line_reserve(ctx_t * ctx) { if (ctx->stream.line_cap - ctx->stream.line_len < LINE_MIN_RESERVE) { size_t new_cap = ctx->stream.line_cap * 2; if (new_cap == 0) new_cap = LINE_MIN_RESERVE; char * new_line = realloc(ctx->stream.line, sizeof (char) * new_cap); if (new_line == NULL) { wlm_log_error("event::line_reserve(): failed to grow line buffer for option stream line\n"); wlm_exit_fail(ctx); } ctx->stream.line = new_line; ctx->stream.line_cap = new_cap; } } enum parse_state { BEFORE_ARG, ARG_START, QUOTED_ARG, UNQUOTED_ARG }; static void on_line(ctx_t * ctx, char * line) { char * arg_start = NULL; char quote_char = '\0'; wlm_log_debug(ctx, "event::on_line(): got line '%s'\n", line); ctx->stream.args_len = 0; enum parse_state state = BEFORE_ARG; while (*line != '\0') { switch (state) { case BEFORE_ARG: if (isspace(*line)) { line++; } else { state = ARG_START; } break; case ARG_START: if (*line == '"' || *line == '\'') { quote_char = *line; line++; arg_start = line; state = QUOTED_ARG; } else { arg_start = line; state = UNQUOTED_ARG; } break; case QUOTED_ARG: if (*line != quote_char) { line++; } else { *line = '\0'; args_push(ctx, arg_start); line++; state = BEFORE_ARG; } break; case UNQUOTED_ARG: if (!isspace(*line)) { line++; } else { *line = '\0'; args_push(ctx, arg_start); line++; state = BEFORE_ARG; } break; } } if (state == QUOTED_ARG) { wlm_log_error("event::on_line(): unmatched quote in argument\n"); } if (state == QUOTED_ARG || state == UNQUOTED_ARG) { args_push(ctx, arg_start); } wlm_log_debug(ctx, "event::on_line(): parsed %zd arguments\n", ctx->stream.args_len); wlm_opt_parse(ctx, ctx->stream.args_len, ctx->stream.args); } static void on_stream_data(ctx_t * ctx, uint32_t events) { // close the window if the input option stream closed if ((events & EPOLLHUP) != 0) wlm_wayland_window_close(ctx); // return early if there is nothing to read if ((events & EPOLLIN) == 0) return; while (true) { line_reserve(ctx); size_t cap = ctx->stream.line_cap; size_t len = ctx->stream.line_len; ssize_t num = read(STDIN_FILENO, ctx->stream.line + len, cap - len); if (num == -1 && errno == EWOULDBLOCK) { break; } else if (num == -1) { wlm_log_error("event::on_data(): failed to read data from stdin\n"); wlm_exit_fail(ctx); } else { ctx->stream.line_len += num; } } char * line = ctx->stream.line; size_t len = ctx->stream.line_len; for (size_t i = 0; i < len; i++) { if (line[i] == '\0') { line[i] = ' '; } else if (line[i] == '\n') { line[i] = '\0'; on_line(ctx, line); memmove(line, line + (i + 1), len - (i + 1)); ctx->stream.line_len -= i + 1; break; } } } void wlm_stream_init(ctx_t * ctx) { // initialize context structure ctx->stream.line = NULL; ctx->stream.line_len = 0; ctx->stream.line_cap = 0; ctx->stream.args = NULL; ctx->stream.args_len = 0; ctx->stream.args_cap = 0; ctx->stream.event_handler.next = NULL; ctx->stream.event_handler.fd = STDIN_FILENO; ctx->stream.event_handler.events = EPOLLIN; ctx->stream.event_handler.timeout_ms = -1; ctx->stream.event_handler.on_event = on_stream_data; ctx->stream.event_handler.on_each = NULL; if (ctx->opt.stream) { int flags = fcntl(STDIN_FILENO, F_GETFL, 0); flags |= O_NONBLOCK; fcntl(STDIN_FILENO, F_SETFL, flags); wlm_event_add_fd(ctx, &ctx->stream.event_handler); } ctx->stream.initialized = true; } void wlm_stream_cleanup(ctx_t * ctx) { free(ctx->stream.line); free(ctx->stream.args); if (ctx->opt.stream) { wlm_event_remove_fd(ctx, &ctx->stream.event_handler); } } wl-mirror-0.17.0/src/transform.c0000644000000000000000000001354214702736000013431 0ustar00#include #include #include static const mat3_t mat_identity = { .data = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }}; static const mat3_t mat_rot_ccw_90 = { .data = { { 0, 1, 0 }, { -1, 0, 1 }, { 0, 0, 1 } }}; static const mat3_t mat_rot_ccw_180 = { .data = { { -1, 0, 1 }, { 0, -1, 1 }, { 0, 0, 1 } }}; static const mat3_t mat_rot_ccw_270 = { .data = { { 0, -1, 1 }, { 1, 0, 0 }, { 0, 0, 1 } }}; static const mat3_t mat_flip_x = { .data = { { -1, 0, 1 }, { 0, 1, 0 }, { 0, 0, 1 } }}; static const mat3_t mat_flip_y = { .data = { { 1, 0, 0 }, { 0, -1, 1 }, { 0, 0, 1 } }}; void wlm_util_mat3_identity(mat3_t * mat) { *mat = mat_identity; } void wlm_util_mat3_transpose(mat3_t * mat) { for (size_t row = 0; row < 3; row++) { for (size_t col = row + 1; col < 3; col++) { float temp = mat->data[row][col]; mat->data[row][col] = mat->data[col][row]; mat->data[col][row] = temp; } } } void wlm_util_mat3_mul(const mat3_t * mul, mat3_t * dest) { mat3_t src = *dest; for (size_t row = 0; row < 3; row++) { for (size_t col = 0; col < 3; col++) { dest->data[row][col] = 0; for (size_t i = 0; i < 3; i++) { dest->data[row][col] += mul->data[row][i] * src.data[i][col]; } } } } void wlm_util_mat3_apply_transform(mat3_t * mat, transform_t transform) { // apply inverse transformation matrix as we need to transform // from OpenGL space to dmabuf space switch (transform.rotation) { case ROT_NORMAL: break; case ROT_CW_90: wlm_util_mat3_mul(&mat_rot_ccw_90, mat); break; case ROT_CW_180: wlm_util_mat3_mul(&mat_rot_ccw_180, mat); break; case ROT_CW_270: wlm_util_mat3_mul(&mat_rot_ccw_270, mat); break; } if (transform.flip_x) wlm_util_mat3_mul(&mat_flip_x, mat); if (transform.flip_y) wlm_util_mat3_mul(&mat_flip_y, mat); } void wlm_util_mat3_apply_region_transform(mat3_t * mat, const region_t * region, const region_t * output) { mat3_t region_transform; wlm_util_mat3_identity(®ion_transform); region_transform.data[0][2] = (float)region->x / output->width; region_transform.data[1][2] = (float)region->y / output->height; region_transform.data[0][0] = (float)region->width / output->width; region_transform.data[1][1] = (float)region->height / output->height; wlm_util_mat3_mul(®ion_transform, mat); } void wlm_util_mat3_apply_output_transform(mat3_t * mat, enum wl_output_transform transform) { // wl_output transform is already inverted switch (transform) { case WL_OUTPUT_TRANSFORM_NORMAL: break; case WL_OUTPUT_TRANSFORM_90: wlm_util_mat3_mul(&mat_rot_ccw_90, mat); break; case WL_OUTPUT_TRANSFORM_180: wlm_util_mat3_mul(&mat_rot_ccw_180, mat); break; case WL_OUTPUT_TRANSFORM_270: wlm_util_mat3_mul(&mat_rot_ccw_270, mat); break; case WL_OUTPUT_TRANSFORM_FLIPPED: wlm_util_mat3_mul(&mat_flip_x, mat); break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: wlm_util_mat3_mul(&mat_flip_x, mat); wlm_util_mat3_mul(&mat_rot_ccw_90, mat); break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: wlm_util_mat3_mul(&mat_flip_x, mat); wlm_util_mat3_mul(&mat_rot_ccw_180, mat); break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: wlm_util_mat3_mul(&mat_flip_x, mat); wlm_util_mat3_mul(&mat_rot_ccw_270, mat); break; } } void wlm_util_mat3_apply_invert_y(mat3_t * mat, bool invert_y) { if (invert_y) { wlm_util_mat3_mul(&mat_flip_y, mat); } } void wlm_util_viewport_apply_transform(uint32_t * width, uint32_t * height, transform_t transform) { uint32_t w = *width; uint32_t h = *height; switch (transform.rotation) { case ROT_CCW_90: case ROT_CCW_270: *height = w; *width = h; break; default: break; } } void wlm_util_viewport_apply_output_transform(uint32_t * width, uint32_t * height, enum wl_output_transform transform) { uint32_t w = *width; uint32_t h = *height; switch (transform) { case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_FLIPPED_270: *height = w; *width = h; break; default: break; } } bool wlm_util_region_contains(const region_t * region, const region_t * output) { if (region->x + region->width <= output->x) return false; if (region->x >= output->x + output->width) return false; if (region->y + region->height <= output->y) return false; if (region->y >= output->y + output->height) return false; return true; } void wlm_util_region_scale(region_t * region, double scale) { region->x *= scale; region->y *= scale; region->width *= scale; region->height *= scale; } void wlm_util_region_clamp(region_t * region, const region_t * output) { if (region->x < output->x) { region->width -= output->x - region->x; region->x = 0; } else { region->x -= output->x; } if (region->x + region->width > output->width) { region->width = output->width - region->x; } if (region->y < output->y) { region->height -= output->y - region->y; region->y = 0; } else { region->y -= output->y; } if (region->y + region->height > output->height) { region->height = output->height - region->y; } } wl-mirror-0.17.0/src/wayland.c0000644000000000000000000011252014702736000013051 0ustar00#include #include #include #include // --- output event handlers --- static void on_output_geometry( void * data, struct wl_output * output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char * make, const char * model, int32_t transform ) { output_list_node_t * node = (output_list_node_t *)data; ctx_t * ctx = node->ctx; // update transform only if changed if (node->transform != (uint32_t)transform) { if (ctx->opt.verbose) { wlm_log_debug(ctx, "wayland::on_output_geometry(): updating output %s (transform = ", node->name); switch (transform) { case WL_OUTPUT_TRANSFORM_NORMAL: fprintf(stderr, "normal"); break; case WL_OUTPUT_TRANSFORM_90: fprintf(stderr, "90ccw"); break; case WL_OUTPUT_TRANSFORM_180: fprintf(stderr, "180ccw"); break; case WL_OUTPUT_TRANSFORM_270: fprintf(stderr, "270ccw"); break; case WL_OUTPUT_TRANSFORM_FLIPPED: fprintf(stderr, "flipX"); break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: fprintf(stderr, "flipX-90ccw"); break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: fprintf(stderr, "flipX-180ccw"); break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: fprintf(stderr, "flipX-270ccw"); break; default: fprintf(stderr, "unknown"); break; } fprintf(stderr, ", id = %d)\n", node->output_id); } node->transform = transform; // update egl viewport only if this is the target output if (ctx->mirror.initialized && ctx->mirror.current_target->output == output) { wlm_egl_resize_viewport(ctx); } } (void)x; (void)y; (void)physical_width; (void)physical_height; (void)subpixel; (void)make; (void)model; (void)transform; } static void on_output_mode( void * data, struct wl_output * output, uint32_t flags, int32_t width, int32_t height, int32_t refresh ) { (void)data; (void)output; (void)flags; (void)width; (void)height; (void)refresh; } static void on_output_scale( void * data, struct wl_output * output, int32_t scale ) { output_list_node_t * node = (output_list_node_t *)data; ctx_t * ctx = node->ctx; // update scale only if changed if (node->scale != scale) { wlm_log_debug(ctx, "wayland::on_output_scale(): updating output %s (scale = %d, id = %d)\n", node->name, scale, node->output_id); node->scale = scale; // update buffer scale only if this is the current output if (ctx->wl.current_output != NULL && ctx->wl.current_output->output == output) { wlm_log_debug(ctx, "wayland::on_output_scale(): updating window scale\n"); wlm_wayland_window_update_scale(ctx, scale, false); } } } static void on_output_done( void * data, struct wl_output * output ) { (void)data; (void)output; } static const struct wl_output_listener output_listener = { .geometry = on_output_geometry, .mode = on_output_mode, .scale = on_output_scale, .done = on_output_done }; // --- xdg_output event handlers --- static void on_xdg_output_description( void * data, struct zxdg_output_v1 * xdg_output, const char * description ) { (void)data; (void)xdg_output; (void)description; } static void on_xdg_output_logical_position( void * data, struct zxdg_output_v1 * xdg_output, int32_t x, int32_t y ) { output_list_node_t * node = (output_list_node_t *)data; ctx_t * ctx = node->ctx; // update position only if changed if (node->x != x || node->y != y) { wlm_log_debug(ctx, "wayland::on_xdg_output_logical_position(): updating output %s (position = %d,%d, id = %d)\n", node->name, x, y, node->output_id); node->x = x; node->y = y; } (void)xdg_output; } static void on_xdg_output_logical_size( void * data, struct zxdg_output_v1 * xdg_output, int32_t width, int32_t height ) { output_list_node_t * node = (output_list_node_t *)data; ctx_t * ctx = node->ctx; // update size only if changed if (node->width != width || node->height != height) { wlm_log_debug(ctx, "wayland::on_xdg_output_logical_size(): updating output %s (size = %dx%d, id = %d)\n", node->name, width, height, node->output_id); node->width = width; node->height = height; } (void)xdg_output; } static void on_xdg_output_name( void * data, struct zxdg_output_v1 * xdg_output, const char * name ) { output_list_node_t * node = (output_list_node_t *)data; ctx_t * ctx = node->ctx; // update name only if changed if (node->name == NULL || strcmp(node->name, name) != 0) { wlm_log_debug(ctx, "wayland::on_xdg_output_name(): updating output %s (id = %d)\n", name, node->output_id); // allocate copy of name since name is owned by libwayland free(node->name); node->name = strdup(name); if (node->name == NULL) { wlm_log_error("wayland::on_xdg_output_name(): failed to allocate output name\n"); wlm_exit_fail(ctx); } } (void)xdg_output; } static void on_xdg_output_done( void * data, struct zxdg_output_v1 * xdg_output ) { (void)data; (void)xdg_output; } static const struct zxdg_output_v1_listener xdg_output_listener = { .description = on_xdg_output_description, .logical_position = on_xdg_output_logical_position, .logical_size = on_xdg_output_logical_size, .name = on_xdg_output_name, .done = on_xdg_output_done }; // --- registry event handlers --- static void on_registry_add( void * data, struct wl_registry * registry, uint32_t id, const char * interface, uint32_t version ) { ctx_t * ctx = (ctx_t *)data; wlm_log_debug(ctx, "wayland::on_registry_add(): %s (version = %d, id = %d)\n", interface, version, id); // bind proxy object for each protocol we need // bind proxy object for each output if (strcmp(interface, wl_compositor_interface.name) == 0) { if (ctx->wl.compositor != NULL) { wlm_log_error("wayland::on_registry_add(): duplicate compositor\n"); wlm_exit_fail(ctx); } // bind compositor object ctx->wl.compositor = (struct wl_compositor *)wl_registry_bind( registry, id, &wl_compositor_interface, 4 ); ctx->wl.compositor_id = id; } else if (strcmp(interface, wp_viewporter_interface.name) == 0) { if (ctx->wl.viewporter != NULL) { wlm_log_error("wayland::on_registry_add(): duplicate wp_viewporter\n"); wlm_exit_fail(ctx); } // bind wp_viewporter object ctx->wl.viewporter = (struct wp_viewporter *)wl_registry_bind( registry, id, &wp_viewporter_interface, 1 ); } else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { if (ctx->wl.fractional_scale_manager != NULL) { wlm_log_error("wayland::on_registry_add(): duplicate wp_fractional_scale_manager\n"); wlm_exit_fail(ctx); } // bind wp_fractional_scale_manager_v1 object ctx->wl.fractional_scale_manager = (struct wp_fractional_scale_manager_v1 *)wl_registry_bind( registry, id, &wp_fractional_scale_manager_v1_interface, 1 ); } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { if (ctx->wl.wm_base != NULL) { wlm_log_error("wayland::on_registry_add(): duplicate wm_base\n"); wlm_exit_fail(ctx); } // bind wm_base object ctx->wl.wm_base = (struct xdg_wm_base *)wl_registry_bind( registry, id, &xdg_wm_base_interface, 2 ); ctx->wl.wm_base_id = id; } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { if (ctx->wl.output_manager != NULL) { wlm_log_error("wayland::on_registry_add(): duplicate output_manager\n"); wlm_exit_fail(ctx); } // bind output_manager object ctx->wl.output_manager = (struct zxdg_output_manager_v1 *)wl_registry_bind( registry, id, &zxdg_output_manager_v1_interface, 2 ); ctx->wl.output_manager_id = id; } else if (strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name) == 0) { if (ctx->wl.dmabuf_manager != NULL) { wlm_log_error("wayland::on_registry_add(): duplicate dmabuf_manager\n"); wlm_exit_fail(ctx); } // bind dmabuf manager object // - for mirror-dmabuf backend ctx->wl.dmabuf_manager = (struct zwlr_export_dmabuf_manager_v1 *)wl_registry_bind( registry, id, &zwlr_export_dmabuf_manager_v1_interface, 1 ); ctx->wl.dmabuf_manager_id = id; } else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) { if (ctx->wl.screencopy_manager != NULL) { wlm_log_error("wayland::on_registry_add(): duplicate screencopy_manager\n"); wlm_exit_fail(ctx); } // bind screencopy manager object // - for mirror-screencopy backend ctx->wl.screencopy_manager = (struct zwlr_screencopy_manager_v1 *)wl_registry_bind( registry, id, &zwlr_screencopy_manager_v1_interface, 3 ); ctx->wl.screencopy_manager_id = id; } else if (strcmp(interface, wl_shm_interface.name) == 0) { if (ctx->wl.shm != NULL) { wlm_log_error("wayland::on_registry_add(): duplicate shm\n"); wlm_exit_fail(ctx); } // bind shm object // - for mirror-screencopy backend ctx->wl.shm = (struct wl_shm *)wl_registry_bind( registry, id, &wl_shm_interface, 1 ); ctx->wl.shm_id = id; } else if (strcmp(interface, wl_output_interface.name) == 0) { // allocate output node output_list_node_t * node = malloc(sizeof (output_list_node_t)); if (node == NULL) { wlm_log_error("wayland::on_registry_add(): failed to allocate output node\n"); wlm_exit_fail(ctx); } // initialize output node node->ctx = ctx; node->name = NULL; node->xdg_output = NULL; node->x = 0; node->y = 0; node->width = 0; node->height = 0; node->scale = 1; node->transform = 0; // prepend output node to output list node->next = ctx->wl.outputs; ctx->wl.outputs = node; // bind wl_output node->output = (struct wl_output *)wl_registry_bind( registry, id, &wl_output_interface, 3 ); node->output_id = id; // add output event listener // - for geometry event // - for scale event wl_output_add_listener(node->output, &output_listener, (void *)node); // check for xdg_output_manager // - sway always sends outputs after protocol extensions // - for simplicity, only this event order is supported if (ctx->wl.output_manager == NULL) { wlm_log_error("wayland::on_registry_add(): wl_output received before xdg_output_manager\n"); wlm_exit_fail(ctx); } // create xdg_output object node->xdg_output = (struct zxdg_output_v1 *)zxdg_output_manager_v1_get_xdg_output( ctx->wl.output_manager, node->output ); if (node->xdg_output == NULL) { wlm_log_error("wayland::on_registry_add(): failed to create xdg_output\n"); wlm_exit_fail(ctx); } // add xdg_output event listener // - for logical_position event // - for logical_size event // - for name event zxdg_output_v1_add_listener(node->xdg_output, &xdg_output_listener, (void *)node); } else if (strcmp(interface, wl_seat_interface.name) == 0) { // allocate seat node seat_list_node_t * node = malloc(sizeof (seat_list_node_t)); if (node == NULL) { wlm_log_error("wayland::on_registry_add(): failed to allocate seat node\n"); wlm_exit_fail(ctx); } // initialize seat node node->ctx = ctx; // prepend seat node to seat list node->next = ctx->wl.seats; ctx->wl.seats = node; // bind wl_seat node->seat = (struct wl_seat *)wl_registry_bind( registry, id, &wl_seat_interface, 1 ); node->seat_id = id; } (void)version; } static void on_registry_remove( void * data, struct wl_registry * registry, uint32_t id ) { ctx_t * ctx = (ctx_t *)data; // ensure protocols we need aren't removed // remove removed outputs from the output list if (id == ctx->wl.compositor_id) { wlm_log_error("wayland::on_registry_remove(): compositor disappeared\n"); wlm_exit_fail(ctx); } else if (id == ctx->wl.viewporter_id) { wlm_log_error("wayland::on_registry_remove(): viewporter disappeared\n"); wlm_exit_fail(ctx); } else if (id == ctx->wl.fractional_scale_manager_id) { wlm_log_error("wayland::on_registry_remove(): fractional_scale_manager disappeared\n"); wlm_exit_fail(ctx); } else if (id == ctx->wl.wm_base_id) { wlm_log_error("wayland::on_registry_remove(): wm_base disappeared\n"); wlm_exit_fail(ctx); } else if (id == ctx->wl.output_manager_id) { wlm_log_error("wayland::on_registry_remove(): output_manager disappeared\n"); wlm_exit_fail(ctx); } else if (id == ctx->wl.dmabuf_manager_id) { wlm_log_error("wayland::on_registry_remove(): dmabuf_manager disappeared\n"); wlm_exit_fail(ctx); } else { { output_list_node_t ** link = &ctx->wl.outputs; output_list_node_t * cur = ctx->wl.outputs; output_list_node_t * prev = NULL; while (cur != NULL) { if (id == cur->output_id) { wlm_log_debug(ctx, "wayland::on_registry_remove(): output %s removed (id = %d)\n", cur->name, id); // notify mirror code of removed outputs // - triggers exit if the target output disappears wlm_mirror_output_removed(ctx, cur); // remove output node from linked list *link = cur->next; prev = cur; cur = cur->next; // deallocate output node zxdg_output_v1_destroy(prev->xdg_output); wl_output_destroy(prev->output); free(prev->name); free(prev); // return because the removed object was found return; } else { link = &cur->next; cur = cur->next; } } } // output not found // id must have been a seat { seat_list_node_t ** link = &ctx->wl.seats; seat_list_node_t * cur = ctx->wl.seats; seat_list_node_t * prev = NULL; while (cur != NULL) { if (id == cur->seat_id) { wlm_log_debug(ctx, "wayland::on_registry_remove(): seat removed (id = %d)\n", id); // remove seat node from linked list *link = cur->next; prev = cur; cur = cur->next; // deallocate seat node wl_seat_destroy(prev->seat); free(prev); // return because the removed object was found return; } else { link = &cur->next; cur = cur->next; } } } } (void)registry; } static const struct wl_registry_listener registry_listener = { .global = on_registry_add, .global_remove = on_registry_remove }; // --- wm_base event handlers --- static void on_wm_base_ping( void * data, struct xdg_wm_base * xdg_wm_base, uint32_t serial ) { xdg_wm_base_pong(xdg_wm_base, serial); (void)data; } static const struct xdg_wm_base_listener wm_base_listener = { .ping = on_wm_base_ping }; // --- surface event handlers --- static void on_surface_enter( void * data, struct wl_surface * surface, struct wl_output * output ) { ctx_t * ctx = (ctx_t *)data; // find output list node for the entered output output_list_node_t * node = NULL; output_list_node_t * cur = ctx->wl.outputs; while (cur != NULL) { if (cur->output == output) { node = cur; break; } cur = cur->next; } // verify an output was found if (node == NULL) { // when multiple registries exist and different parts of the application // bind separately to the same wl_output, on_surface_enter will be called // multiple times. // // only one of those calls will give us an output in our list of outputs. // we should ignore all other calls, instead of bailing completely wlm_log_debug(ctx, "wayland::on_surface_enter(): entered output not in output list, possibly bound from another registry?\n"); return; } // update current output bool first_output = ctx->wl.current_output == NULL; ctx->wl.current_output = node; // set window fullscreen now if no specific output requested if (first_output && ctx->opt.fullscreen) { wlm_wayland_window_set_fullscreen(ctx); } wlm_log_debug(ctx, "wayland::on_surface_enter(): updating window scale\n"); wlm_wayland_window_update_scale(ctx, node->scale, false); (void)surface; } static void on_surface_leave( void * data, struct wl_surface * surface, struct wl_output * output ) { (void)data; (void)surface; (void)output; } static const struct wl_surface_listener surface_listener = { .enter = on_surface_enter, .leave = on_surface_leave }; // --- configure callbacks --- static void on_surface_configure_finished(ctx_t * ctx) { // acknowledge configure and commit surface to finish configure sequence wlm_log_debug(ctx, "wayland::on_surface_configure_finished(): window configured\n"); #ifdef WITH_LIBDECOR #else xdg_surface_ack_configure(ctx->wl.xdg_surface, ctx->wl.last_surface_serial); #endif wl_surface_commit(ctx->wl.surface); // reset configure sequence state machine #ifndef WITH_LIBDECOR ctx->wl.xdg_surface_configured = false; ctx->wl.xdg_toplevel_configured = false; #endif ctx->wl.configured = true; } #ifdef WITH_LIBDECOR // --- libdecor event handlers --- // HACK: needed because on_libdecor_error does not have a userdata parameter static ctx_t * libdecor_error_context = NULL; static void on_libdecor_error( struct libdecor * libdecor_context, enum libdecor_error error, const char * message ) { ctx_t * ctx = libdecor_error_context; wlm_log_error("wayland::on_libdecor_error(): error %d, %s\n", error, message); wlm_exit_fail(ctx); (void)libdecor_context; } static struct libdecor_interface libdecor_listener = { .error = on_libdecor_error, }; // --- libdecor_frame event handlers --- static void on_libdecor_frame_configure( struct libdecor_frame * frame, struct libdecor_configuration * configuration, void * data ) { ctx_t * ctx = (ctx_t *)data; int width = 0; int height = 0; if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { if (ctx->wl.width == 0 || ctx->wl.height == 0) { wlm_log_debug(ctx, "wayland::on_libdecor_frame_configure(): falling back to default width\n"); width = 100; height = 100; } else { wlm_log_debug(ctx, "wayland::on_libdecor_frame_configure(): falling back to previous width\n"); width = ctx->wl.width; height = ctx->wl.height; } } // check fullscreen state bool is_fullscreen = false; enum libdecor_window_state window_state; if (libdecor_configuration_get_window_state(configuration, &window_state)) { if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) { is_fullscreen = true; } } // reset fullscreen option if the window is not fullscreen // but only if have already had the chance to fullscreen on the current output if (is_fullscreen != ctx->opt.fullscreen && ctx->wl.current_output != NULL) { ctx->opt.fullscreen = is_fullscreen; } struct libdecor_state * state = libdecor_state_new(width, height); libdecor_frame_commit(frame, state, configuration); libdecor_state_free(state); // update size only if changed if (ctx->wl.width != (uint32_t)width || ctx->wl.height != (uint32_t)height) { wlm_log_debug(ctx, "wayland::on_libdecor_frame_configure(): window resized to %dx%d\n", width, height); ctx->wl.width = width; ctx->wl.height = height; // resize viewport wp_viewport_set_destination(ctx->wl.viewport, width, height); // resize window to reflect new surface size if (ctx->egl.initialized) { wlm_egl_resize_window(ctx); } } // update configure sequence state machine on_surface_configure_finished(ctx); } static void on_libdecor_frame_commit( struct libdecor_frame * frame, void * data ) { ctx_t * ctx = (ctx_t *)data; (void)frame; (void)ctx; } static void on_libdecor_frame_close( struct libdecor_frame * frame, void * data ) { ctx_t * ctx = (ctx_t *)data; wlm_log_debug(ctx, "wayland::on_libdecor_frame_close(): close request received\n"); ctx->wl.closing = true; (void)frame; } static struct libdecor_frame_interface libdecor_frame_listener = { .configure = on_libdecor_frame_configure, .commit = on_libdecor_frame_commit, .close = on_libdecor_frame_close, }; #else // --- xdg_surface event handlers --- static void on_xdg_surface_configure( void * data, struct xdg_surface * xdg_surface, uint32_t serial ) { ctx_t * ctx = (ctx_t *)data; // save serial for configure acknowledgement ctx->wl.last_surface_serial = serial; // update configure sequence state machine ctx->wl.xdg_surface_configured = true; if (ctx->wl.xdg_surface_configured && ctx->wl.xdg_toplevel_configured) { on_surface_configure_finished(ctx); } (void)xdg_surface; } static const struct xdg_surface_listener xdg_surface_listener = { .configure = on_xdg_surface_configure, }; // --- xdg_toplevel event handlers --- static void on_xdg_toplevel_configure( void * data, struct xdg_toplevel * xdg_toplevel, int32_t width, int32_t height, struct wl_array * states ) { ctx_t * ctx = (ctx_t *)data; // set default size of 100x100 if compositor does not have a preference if (width == 0) width = 100; if (height == 0) height = 100; // check fullscreen state bool is_fullscreen = false; uint32_t * state; wl_array_for_each(state, states) { if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN) { is_fullscreen = true; } } // reset fullscreen option if the window is not fullscreen // but only if have already had the chance to fullscreen on the current output if (is_fullscreen != ctx->opt.fullscreen && ctx->wl.current_output != NULL) { ctx->opt.fullscreen = is_fullscreen; } // update size only if changed if (ctx->wl.width != (uint32_t)width || ctx->wl.height != (uint32_t)height) { wlm_log_debug(ctx, "wayland::on_xdg_toplevel_configure(): window resized to %dx%d\n", width, height); ctx->wl.width = width; ctx->wl.height = height; // resize viewport wp_viewport_set_destination(ctx->wl.viewport, width, height); // resize window to reflect new surface size if (ctx->egl.initialized) { wlm_egl_resize_window(ctx); } } // update configure sequence state machine ctx->wl.xdg_toplevel_configured = true; if (ctx->wl.xdg_surface_configured && ctx->wl.xdg_toplevel_configured) { on_surface_configure_finished(ctx); } (void)xdg_toplevel; (void)states; } static void on_xdg_toplevel_close( void * data, struct xdg_toplevel * xdg_toplevel ) { ctx_t * ctx = (ctx_t *)data; wlm_log_debug(ctx, "wayland::on_xdg_toplevel_close(): close request received\n"); ctx->wl.closing = true; (void)xdg_toplevel; } static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = on_xdg_toplevel_configure, .close = on_xdg_toplevel_close }; #endif // --- wayland event loop handlers --- static void on_wayland_event(ctx_t * ctx, uint32_t events) { (void)events; if (wl_display_dispatch(ctx->wl.display) == -1) { ctx->wl.closing = true; } #ifdef WITH_LIBDECOR if (libdecor_dispatch(ctx->wl.libdecor_context, 0) < 0) { ctx->wl.closing = true; } #endif } static void on_wayland_each(ctx_t * ctx) { wl_display_flush(ctx->wl.display); } // --- fractional scale event handlers --- static void on_fractional_scale_preferred_scale(void * data, struct wp_fractional_scale_v1 * fractional_scale, uint32_t scale_times_120) { ctx_t * ctx = (ctx_t *)data; double scale = scale_times_120 / 120.0; wlm_log_debug(ctx, "wayland::on_fractional_scale_preferred_scale(): scale = %.4f\n", scale); // fractionally scaled surfaces have buffer scale of 1 wlm_wayland_window_update_scale(ctx, scale, true); (void)fractional_scale; } static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { .preferred_scale = on_fractional_scale_preferred_scale }; // --- find_output --- bool wlm_wayland_find_output(ctx_t * ctx, char * output_name, struct wl_output ** output) { bool found = false; output_list_node_t * cur = ctx->wl.outputs; while (cur != NULL) { if (cur->name != NULL && strcmp(cur->name, output_name) == 0) { output_name = cur->name; break; } cur = cur->next; } if (cur != NULL) { found = true; *output = cur->output; } return found; } // --- init_wl --- void wlm_wayland_init(ctx_t * ctx) { // initialize context structure ctx->wl.display = NULL; ctx->wl.registry = NULL; ctx->wl.compositor = NULL; ctx->wl.compositor_id = 0; ctx->wl.viewporter = NULL; ctx->wl.viewporter_id = 0; ctx->wl.fractional_scale_manager = NULL; ctx->wl.fractional_scale_manager_id = 0; ctx->wl.wm_base = NULL; ctx->wl.wm_base_id = 0; ctx->wl.output_manager = NULL; ctx->wl.output_manager_id = 0; ctx->wl.dmabuf_manager = NULL; ctx->wl.dmabuf_manager_id = 0; ctx->wl.shm = NULL; ctx->wl.shm_id = 0; ctx->wl.screencopy_manager = NULL; ctx->wl.screencopy_manager_id = 0; ctx->wl.outputs = NULL; ctx->wl.seats = NULL; ctx->wl.surface = NULL; ctx->wl.viewport = NULL; ctx->wl.fractional_scale = NULL; #ifdef WITH_LIBDECOR ctx->wl.libdecor_context = NULL; ctx->wl.libdecor_frame = NULL; #else ctx->wl.xdg_surface = NULL; ctx->wl.xdg_toplevel = NULL; #endif ctx->wl.current_output = NULL; ctx->wl.width = 0; ctx->wl.height = 0; ctx->wl.scale = 1.0; ctx->wl.event_handler.next = NULL; ctx->wl.event_handler.fd = -1; ctx->wl.event_handler.events = EPOLLIN; ctx->wl.event_handler.timeout_ms = -1; ctx->wl.event_handler.on_event = on_wayland_event; ctx->wl.event_handler.on_each = on_wayland_each; ctx->wl.last_surface_serial = 0; #ifndef WITH_LIBDECOR ctx->wl.xdg_surface_configured = false; ctx->wl.xdg_toplevel_configured = false; #endif ctx->wl.configured = false; ctx->wl.closing = false; ctx->wl.initialized = true; // connect to display ctx->wl.display = wl_display_connect(NULL); if (ctx->wl.display == NULL) { wlm_log_error("wayland::init(): failed to connect to wayland\n"); wlm_exit_fail(ctx); } // register event loop ctx->wl.event_handler.fd = wl_display_get_fd(ctx->wl.display); wlm_event_add_fd(ctx, &ctx->wl.event_handler); // get registry handle ctx->wl.registry = wl_display_get_registry(ctx->wl.display); if (ctx->wl.registry == NULL) { wlm_log_error("wayland::init(): failed to get registry handle\n"); wlm_exit_fail(ctx); } // add registry event listener // - for add global event // - for remove global event wl_registry_add_listener(ctx->wl.registry, ®istry_listener, (void *)ctx); // wait for registry events // - expecting add global events for all required protocols // - expecting add global events for all outputs wl_display_roundtrip(ctx->wl.display); // check for missing required protocols if (ctx->wl.compositor == NULL) { wlm_log_error("wayland::init(): compositor missing\n"); wlm_exit_fail(ctx); } else if (ctx->wl.viewporter == NULL) { wlm_log_error("wayland::init(): viewporter missing\n"); wlm_exit_fail(ctx); } else if (ctx->wl.wm_base == NULL) { wlm_log_error("wayland::init(): wm_base missing\n"); wlm_exit_fail(ctx); } else if (ctx->wl.output_manager == NULL) { wlm_log_error("wayland::init(): output_manager missing\n"); wlm_exit_fail(ctx); } // add wm_base event listener // - for ping event xdg_wm_base_add_listener(ctx->wl.wm_base, &wm_base_listener, (void *)ctx); // create surface ctx->wl.surface = wl_compositor_create_surface(ctx->wl.compositor); if (ctx->wl.surface == NULL) { wlm_log_error("wayland::init(): failed to create surface\n"); wlm_exit_fail(ctx); } // add surface event listener // - for enter event // - for leave event wl_surface_add_listener(ctx->wl.surface, &surface_listener, (void *)ctx); // create viewport ctx->wl.viewport = wp_viewporter_get_viewport(ctx->wl.viewporter, ctx->wl.surface); if (ctx->wl.viewport == NULL) { wlm_log_error("wayland::init(): failed to create viewport\n"); wlm_exit_fail(ctx); } // create fractional scale if supported if (ctx->wl.fractional_scale_manager != NULL) { ctx->wl.fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(ctx->wl.fractional_scale_manager, ctx->wl.surface); wp_fractional_scale_v1_add_listener(ctx->wl.fractional_scale, &fractional_scale_listener, (void *)ctx); } #if WITH_LIBDECOR // create libdecor context // - for error event libdecor_error_context = ctx; ctx->wl.libdecor_context = libdecor_new(ctx->wl.display, &libdecor_listener); // create libdecor frame // - for configure event // - for commit event // - for close event ctx->wl.libdecor_frame = libdecor_decorate(ctx->wl.libdecor_context, ctx->wl.surface, &libdecor_frame_listener, ctx); // set libdecor app properties libdecor_frame_set_app_id(ctx->wl.libdecor_frame, "at.yrlf.wl_mirror"); libdecor_frame_set_title(ctx->wl.libdecor_frame, "Wayland Output Mirror"); // map libdecor frame libdecor_frame_map(ctx->wl.libdecor_frame); // commit surface to trigger configure sequence wl_surface_commit(ctx->wl.surface); // wait for events // - expecting libdecor frame configure event wl_display_roundtrip(ctx->wl.display); #else // create xdg surface ctx->wl.xdg_surface = xdg_wm_base_get_xdg_surface(ctx->wl.wm_base, ctx->wl.surface); if (ctx->wl.xdg_surface == NULL) { wlm_log_error("wayland::init(): failed to create xdg_surface\n"); wlm_exit_fail(ctx); } // add xdg surface event listener // - for configure event xdg_surface_add_listener(ctx->wl.xdg_surface, &xdg_surface_listener, (void *)ctx); // create xdg toplevel ctx->wl.xdg_toplevel = xdg_surface_get_toplevel(ctx->wl.xdg_surface); if (ctx->wl.xdg_toplevel == NULL) { wlm_log_error("wayland::init(): failed to create xdg_toplevel\n"); wlm_exit_fail(ctx); } // add xdg toplevel event listener // - for toplevel configure event // - for close event xdg_toplevel_add_listener(ctx->wl.xdg_toplevel, &xdg_toplevel_listener, (void *)ctx); // set xdg toplevel properties xdg_toplevel_set_app_id(ctx->wl.xdg_toplevel, "at.yrlf.wl_mirror"); xdg_toplevel_set_title(ctx->wl.xdg_toplevel, "Wayland Output Mirror"); // commit surface to trigger configure sequence wl_surface_commit(ctx->wl.surface); // wait for events // - expecting surface configure event // - expecting xdg toplevel configure event wl_display_roundtrip(ctx->wl.display); #endif // set fullscreen on xdg_toplevel if (ctx->opt.fullscreen && ctx->opt.fullscreen_output != NULL) { wlm_wayland_window_set_fullscreen(ctx); } // check if surface is configured // - expecting surface to be configured at this point if (!ctx->wl.configured) { wlm_log_error("wayland::init(): surface not configured\n"); wlm_exit_fail(ctx); } } // --- close_window --- void wlm_wayland_window_close(struct ctx * ctx) { ctx->wl.closing = true; } // --- set_window_title --- void wlm_wayland_window_set_title(ctx_t * ctx, const char * title) { #ifdef WITH_LIBDECOR libdecor_frame_set_title(ctx->wl.libdecor_frame, title); #else xdg_toplevel_set_title(ctx->wl.xdg_toplevel, title); #endif } // --- set_window_fullscreen --- void wlm_wayland_window_set_fullscreen(ctx_t * ctx) { struct wl_output * output = NULL; if (ctx->opt.fullscreen_output == NULL) { output = ctx->wl.current_output->output; } else if (!wlm_wayland_find_output(ctx, ctx->opt.fullscreen_output, &output)) { wlm_log_error("wayland::init(): output %s not found\n", ctx->opt.fullscreen_output); wlm_exit_fail(ctx); } #ifdef WITH_LIBDECOR libdecor_frame_set_fullscreen(ctx->wl.libdecor_frame, output); #else xdg_toplevel_set_fullscreen(ctx->wl.xdg_toplevel, output); #endif } void wlm_wayland_window_unset_fullscreen(ctx_t * ctx) { #ifdef WITH_LIBDECOR libdecor_frame_unset_fullscreen(ctx->wl.libdecor_frame); #else xdg_toplevel_unset_fullscreen(ctx->wl.xdg_toplevel); #endif } // --- update_window_scale void wlm_wayland_window_update_scale(ctx_t * ctx, double scale, bool is_fractional) { bool resize = false; // don't update scale from other sources if fractional scaling supported if (ctx->wl.fractional_scale != NULL && !is_fractional) return; if (ctx->wl.scale != scale) { wlm_log_debug(ctx, "wayland::update_window_scale(): setting window scale to %.4f\n", scale); ctx->wl.scale = scale; resize = true; } // resize egl window to reflect new scale if (resize && ctx->egl.initialized) { wlm_egl_resize_window(ctx); } } // --- cleanup_wl --- void wlm_wayland_cleanup(ctx_t *ctx) { if (!ctx->wl.initialized) return; wlm_log_debug(ctx, "wayland::cleanup(): destroying wayland objects\n"); // deregister event handler wlm_event_remove_fd(ctx, &ctx->wl.event_handler); { // free every output in output list output_list_node_t * cur = ctx->wl.outputs; output_list_node_t * prev = NULL; while (cur != NULL) { prev = cur; cur = cur->next; // deallocate output node zxdg_output_v1_destroy(prev->xdg_output); wl_output_destroy(prev->output); free(prev->name); free(prev); } ctx->wl.outputs = NULL; } { // free every seat in seat list seat_list_node_t * cur = ctx->wl.seats; seat_list_node_t * prev = NULL; while (cur != NULL) { prev = cur; cur = cur->next; // deallocate seat node wl_seat_destroy(prev->seat); free(prev); } ctx->wl.seats = NULL; } if (ctx->wl.dmabuf_manager != NULL) zwlr_export_dmabuf_manager_v1_destroy(ctx->wl.dmabuf_manager); if (ctx->wl.screencopy_manager != NULL) zwlr_screencopy_manager_v1_destroy(ctx->wl.screencopy_manager); if (ctx->wl.shm != NULL) wl_shm_destroy(ctx->wl.shm); #ifdef WITH_LIBDECOR if (ctx->wl.libdecor_frame != NULL) libdecor_frame_unref(ctx->wl.libdecor_frame); if (ctx->wl.libdecor_context != NULL) libdecor_unref(ctx->wl.libdecor_context); #else if (ctx->wl.xdg_toplevel != NULL) xdg_toplevel_destroy(ctx->wl.xdg_toplevel); if (ctx->wl.xdg_surface != NULL) xdg_surface_destroy(ctx->wl.xdg_surface); #endif if (ctx->wl.fractional_scale != NULL) wp_fractional_scale_v1_destroy(ctx->wl.fractional_scale); if (ctx->wl.viewport != NULL) wp_viewport_destroy(ctx->wl.viewport); if (ctx->wl.surface != NULL) wl_surface_destroy(ctx->wl.surface); if (ctx->wl.output_manager != NULL) zxdg_output_manager_v1_destroy(ctx->wl.output_manager); if (ctx->wl.wm_base != NULL) xdg_wm_base_destroy(ctx->wl.wm_base); if (ctx->wl.fractional_scale_manager != NULL) wp_fractional_scale_manager_v1_destroy(ctx->wl.fractional_scale_manager); if (ctx->wl.viewporter != NULL) wp_viewporter_destroy(ctx->wl.viewporter); if (ctx->wl.compositor != NULL) wl_compositor_destroy(ctx->wl.compositor); if (ctx->wl.registry != NULL) wl_registry_destroy(ctx->wl.registry); if (ctx->wl.display != NULL) wl_display_disconnect(ctx->wl.display); ctx->wl.initialized = false; } wl-mirror-0.17.0/version/0000755000000000000000000000000014702736000012143 5ustar00wl-mirror-0.17.0/version/CMakeLists.txt0000644000000000000000000000114614702736000014705 0ustar00add_library(version INTERFACE) target_include_directories(version INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/include/") file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include/wlm") set(version-header "${CMAKE_CURRENT_BINARY_DIR}/include/wlm/version.h") set(version-template "${CMAKE_CURRENT_SOURCE_DIR}/version.h.in") add_custom_target(gen-version COMMAND "${CMAKE_COMMAND}" "-DREPO_DIR=${CMAKE_SOURCE_DIR}" -P "${CMAKE_CURRENT_SOURCE_DIR}/version.cmake" "${version-header}" "${version-template}" ) set_source_files_properties("${version-header}" PROPERTIES GENERATED 1) add_dependencies(version gen-version) wl-mirror-0.17.0/version/version.cmake0000644000000000000000000000124314702736000014632 0ustar00if(NOT ${CMAKE_ARGC} EQUAL 6 OR NOT DEFINED REPO_DIR) message(FATAL_ERROR "usage: cmake -DREPO_DIR= -P version.cmake ") endif() set(version-header "${CMAKE_ARGV4}") set(version-template "${CMAKE_ARGV5}") set(repo-dir "${REPO_DIR}") if(IS_DIRECTORY "${repo-dir}/.git") execute_process( COMMAND git describe --long --tags WORKING_DIRECTORY "${repo-dir}" OUTPUT_VARIABLE VERSION ) elseif(EXISTS "${repo-dir}/version.txt") file(READ "${repo-dir}/version.txt" VERSION) else() set(VERSION "") endif() string(STRIP "${VERSION}" VERSION) configure_file("${version-template}" "${version-header}" @ONLY) wl-mirror-0.17.0/version/version.h.in0000644000000000000000000000014314702736000014404 0ustar00#ifndef WL_MIRROR_VERSION_H_ #define WL_MIRROR_VERSION_H_ #define WLM_VERSION "@VERSION@" #endif wl-mirror-0.17.0/version.txt0000644000000000000000000000001014702736000012673 0ustar00v0.17.0