# HG changeset patch # User tsukasa # Date 1783763801 -28800 # Node ID 50bab568625d9803c8da2138ab9ffc4000a15866 # Parent 733566def44f7203fdf9aee535cbc863bec8deb6 lol diff -r 733566def44f -r 50bab568625d LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,693 @@ +swindle - wayland compositor base wlroots + +Copyright © 2026 kantiankant + +See also the files licenses/LICENSE.dwl, licenses/LICENSE.tinywl, +licenses/LICENSE.dwm, licenses/LICENSE.sway, and licenses/LICENSE.mangowm + +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. + +---- + + 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 + + 1. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff -r 733566def44f -r 50bab568625d Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,99 @@ +.POSIX: +.SUFFIXES: + +include config.mk + +DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) \ + -I. -Iinclude -Isrc -Iparser -DWLR_USE_UNSTABLE -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" \ + $(XWAYLAND) -g -Wall -Wextra -Wno-unused-parameter -O1 -std=c11 $(CFLAGS) +LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + +SCANNER = `$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner` +PROTOCOLS = `$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols` + +SMSG_CFLAGS = `$(PKG_CONFIG) --cflags wayland-client` -Wall -Wextra -Wno-unused-parameter +SMSG_LDLIBS = `$(PKG_CONFIG) --libs wayland-client` + +all: swindle smsg/smsg + +swindle: src/dwl.o src/util.o parser/parser.o dwl-ipc-unstable-v2-protocol.o src/wlr_ext_workspace_v1.o ext-workspace-v1-protocol.o + $(CC) $^ $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ + +src/dwl.o: src/swindle.c include/client.h config.h include/dwl-ipc.h dwl-ipc-unstable-v2-protocol.h \ + include/ext-workspace.h src/wlr_ext_workspace_v1.h ext-workspace-v1-protocol.h \ + cursor-shape-v1-protocol.h pointer-constraints-unstable-v1-protocol.h \ + wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ + parser/parser.h + $(CC) $(DWLCFLAGS) -o $@ -c $< + +src/util.o: src/util.c src/util.h + $(CC) $(DWLCFLAGS) -o $@ -c $< + +src/wlr_ext_workspace_v1.o: src/wlr_ext_workspace_v1.c src/wlr_ext_workspace_v1.h ext-workspace-v1-protocol.h + $(CC) $(DWLCFLAGS) -o $@ -c $< + +parser/parser.o: parser/parser.c parser/parser.h + $(CC) $(DWLCFLAGS) -o $@ -c $< + +dwl-ipc-unstable-v2-protocol.o: dwl-ipc-unstable-v2-protocol.c + +ext-workspace-v1-protocol.o: ext-workspace-v1-protocol.c + +# protocol headers and glue +cursor-shape-v1-protocol.h: + $(SCANNER) enum-header $(PROTOCOLS)/staging/cursor-shape/cursor-shape-v1.xml $@ +pointer-constraints-unstable-v1-protocol.h: + $(SCANNER) enum-header $(PROTOCOLS)/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml $@ +wlr-layer-shell-unstable-v1-protocol.h: + $(SCANNER) enum-header protocols/wlr-layer-shell-unstable-v1.xml $@ +wlr-output-power-management-unstable-v1-protocol.h: + $(SCANNER) server-header protocols/wlr-output-power-management-unstable-v1.xml $@ +xdg-shell-protocol.h: + $(SCANNER) server-header $(PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ +dwl-ipc-unstable-v2-protocol.h: + $(SCANNER) server-header protocols/dwl-ipc-unstable-v2.xml $@ +dwl-ipc-unstable-v2-protocol.c: + $(SCANNER) private-code protocols/dwl-ipc-unstable-v2.xml $@ + +# smsg +smsg/dwl-ipc-unstable-v2-protocol.h: + $(SCANNER) client-header protocols/dwl-ipc-unstable-v2.xml $@ + +smsg/dwl-ipc-unstable-v2-protocol.c: + $(SCANNER) private-code protocols/dwl-ipc-unstable-v2.xml $@ + +smsg/dwl-ipc-unstable-v2-protocol.o: smsg/dwl-ipc-unstable-v2-protocol.c + $(CC) $(SMSG_CFLAGS) -o $@ -c $< + +smsg/smsg.o: smsg/smsg.c smsg/dwl-ipc-unstable-v2-protocol.h + $(CC) $(SMSG_CFLAGS) -o $@ -c $< + +smsg/smsg: smsg/smsg.o smsg/dwl-ipc-unstable-v2-protocol.o + $(CC) $^ $(SMSG_CFLAGS) $(SMSG_LDLIBS) -o $@ + +ext-workspace-v1-protocol.h: + $(SCANNER) server-header protocols/ext-workspace-v1.xml $@ +ext-workspace-v1-protocol.c: + $(SCANNER) private-code protocols/ext-workspace-v1.xml $@ + +clean: + rm -f swindle smsg/smsg src/*.o parser/*.o smsg/*.o smsg/*-protocol.h smsg/*-protocol.c *.o *-protocol.h *-protocol.c + +install: swindle smsg/smsg + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f swindle $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/swindle + cp -f smsg/smsg $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/smsg + mkdir -p $(DESTDIR)/etc/swindle + cp -r example/* $(DESTDIR)/etc/swindle + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/swindle + rm -f $(DESTDIR)$(PREFIX)/bin/smsg + rm -rf $(DESTDIR)/etc/swindle + +.SUFFIXES: .c .o +.c.o: + $(CC) $(DWLCFLAGS) -o $@ -c $< diff -r 733566def44f -r 50bab568625d README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.md Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,56 @@ +## Swindle + + + +> "It's so bad it's not even funny!" -kantiankant, 2026 + + +Swindle is a fork of [dwl](https://codeberg.org/dwl/dwl) that was designed from the start with one goal: to make it feel like the poor man's Hyprland. It has the following: + +* NO cool vfx +* nearly NO documentation +* ZERO good original code +* only ONE tiling layout (dwindle) + +> Fun fact: I actually took some parts of [MangoWM](https://github.com/mangowm/mango) (such as the ext-workspaces implementation). hence why the LICENSE.mangowm exists + +## Dependencies + +* libinput +* wayland +* wlroots-0.20 (compiled with the libinput backend) +* xkbcommon +* wayland-protocols (compile-time only) +* pkg-config (compile-time only) + +Install these (and their `-devel` versions if your distro has separate +development packages) and run `make` followed by `doas/sudo make install`, if you wish to install it (installs to /usr/local/bin/ by default). + +## Things that are good to have + +* [foot](https://codeberg.org/dnkl/foot) + + +## Known Issues + +N/A + +## Configuration + +read example/config.lua. It should give you a basic idea of how configuring swindle works. + +> note: it's best to copy /etc/swindle/config.lua into XDG_HOME_DIR/.config/swindle/ before you attempt to +start swindle because it (swindle) won't start without it + +## Checklist + +- [ ] separate the trackpad scroll from the mousee scroll +- [ ] Add ability to change your keymap +- [ ] Add animations via [scenefx](https://github.com/wlrfx/scenefx) when 0.5 drops (in another branch) +- [ ] Add the ability for the compositor to read a custom path for configs +- [ ] Add /etc/swindle/config.lua as a fallback path if $HOME/.config/swindle/config.lua is not found + + +## License + +GPL-v3, probably diff -r 733566def44f -r 50bab568625d assets/log.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/assets/log.txt Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,4 @@ + ████████ ██ ██ + ██ ██ ██ ██ + ██ ██ ██ ██ +████ ██████████ diff -r 733566def44f -r 50bab568625d config.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/config.h Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,6 @@ +static void dwindle(Monitor *m); + +static const Layout layouts[] = { + {"><>", NULL}, + {"[T]", dwindle}, +}; diff -r 733566def44f -r 50bab568625d config.mk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/config.mk Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,31 @@ +_VERSION = 0.1 +VERSION = `git describe --tags --dirty 2>/dev/null || echo $(_VERSION)` + +PKG_CONFIG = pkg-config + +# paths +PREFIX = /usr/local +MANDIR = $(PREFIX)/share/man +DATDIR = $(PREFIX)/share + +WLR_INCS = `$(PKG_CONFIG) --cflags wlroots-0.20` +WLR_LIBS = `$(PKG_CONFIG) --libs wlroots-0.20` + +# Allow using an alternative wlroots installation +#WLR_INCS = -I/usr/include/pixman-1 -I/usr/include/elogind -I/usr/include/libdrm \ +# -I$(PWD)/wlroots/include +#WLR_LIBS = -Wl,-rpath,$(PWD)/wlroots/build -L$(PWD)/wlroots/build -lwlroots-0.19 + +#XWAYLAND = +#XLIBS = +# Uncomment to build XWayland support +XWAYLAND = -DXWAYLAND +XLIBS = xcb xcb-icccm + +# lua +LUA_PKG = lua5.4 + +PKGS = wayland-server xkbcommon libinput $(LUA_PKG) $(XLIBS) + +# Requires C11: _Alignas and anonymous unions in wlroots headers. +CC = cc diff -r 733566def44f -r 50bab568625d example/config.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/example/config.lua Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,97 @@ +sloppy_focus = true +bypass_surface_visibility = false +log_level = "error" -- "silent", "error", "info", "debug" + +appearance = { + border_px = 2, + gaps = 10, -- 0 = no gaps. simple as. + smart_gaps = false, + + root_color = 0x222222ff, + border_color = 0x00000000, + focus_color = 0xe8e8e8ff, + urgent_color = 0xff0000ff, + + fullscreen_bg = 0x000000ff, +} + + +-- Note: the entire input section requires you to restart +-- the compositor once changed +input = { + repeat_rate = 50, + repeat_delay = 150, + tap_to_click = true, + tap_and_drag = true, + drag_lock = true, + natural_scrolling = false, + disable_while_typing = true, + left_handed = false, + middle_button_emulation = false, + scroll_method = "2fg", -- "2fg", "edge", "button" + click_method = "button_areas", -- "button_areas", "clickfinger" + accel_profile = "adaptive", -- "adaptive", "flat" + accel_speed = 0.0, +} + +rules = { +-- { app_id = "Gimp", floating = true, monitor = -1 }, +-- { app_id = "firefox", tags = 1 << 8, floating = false, monitor = -1 }, +} + +monitors = { + -- catch-all rule; name = nil means match anything + { name = nil, mfact = 0.55, nmaster = 1, scale = 1.0, + layout = "dwindle", x = -1, y = -1 }, + + -- HiDPI laptop example: + -- { name = "eDP-1", mfact = 0.5, nmaster = 1, scale = 2.0, + -- layout = "dwindle", x = -1, y = -1 }, +} + +-- autostart: it starts stuff in sequence +autostart = { + "dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_SESSION_TYPE XDG_CURRENT_DESKTOP", + "systemctl --user import-environment WAYLAND_DISPLAY XDG_SESSION_TYPE XDG_CURRENT_DESKTOP", + "waybar", +} + + +keybinds = { + { mods = {"logo"}, key = "q", action = "spawn", args = {"alacritty"} }, + { mods = {"logo"}, key = "space", action = "spawn", args = {"wofi", "--show", "drun"} }, + { mods = {"logo"}, key = "w", action = "killclient" }, + { mods = {"logo"}, key = "v", action = "togglefloating" }, + { mods = {"logo"}, key = "f", action = "togglefullscreen" }, + { mods = {"logo"}, key = "g", action = "togglegaps" }, + { mods = {"logo"}, key = "h", action = "focusdir", args = {"left"} }, + { mods = {"logo"}, key = "j", action = "focusdir", args = {"down"} }, + { mods = {"logo"}, key = "k", action = "focusdir", args = {"up"} }, + { mods = {"logo"}, key = "l", action = "focusdir", args = {"right"} }, + { mods = {"logo", "shift"}, key = "H", action = "swapdir", args = {"left"} }, + { mods = {"logo", "shift"}, key = "J", action = "swapdir", args = {"down"} }, + { mods = {"logo", "shift"}, key = "K", action = "swapdir", args = {"up"} }, + { mods = {"logo", "shift"}, key = "L", action = "swapdir", args = {"right"} }, + { mods = {"logo"}, key = "Tab", action = "view" }, + { mods = {"logo"}, key = "0", action = "view", args = {"all"} }, + { mods = {"logo"}, key = "comma", action = "focusmon", args = {"left"} }, + { mods = {"logo"}, key = "period", action = "focusmon", args = {"right"} }, + { mods = {"logo", "shift"}, key = "less", action = "tagmon", args = {"left"} }, + { mods = {"logo", "shift"}, key = "greater", action = "tagmon", args = {"right"} }, + { mods = {"logo", "shift"}, key = "e", action = "quit" }, +} + +for i = 1, 9 do + local key = tostring(i) + local mask = 1 << (i - 1) + table.insert(keybinds, { mods = {"logo"}, key = key, action = "view", args = {tostring(mask)} }) + table.insert(keybinds, { mods = {"logo", "ctrl"}, key = key, action = "toggleview", args = {tostring(mask)} }) + table.insert(keybinds, { mods = {"logo", "shift"}, key = key, action = "tag", args = {tostring(mask)} }) + table.insert(keybinds, { mods = {"logo", "ctrl", "shift"}, key = key, action = "toggletag", args = {tostring(mask)} }) +end + +buttons = { + { mods = {"logo"}, button = "left", action = "moveresize", args = {"move"} }, + { mods = {"logo"}, button = "middle", action = "togglefloating" }, + { mods = {"logo"}, button = "right", action = "moveresize", args = {"resize"} }, +} diff -r 733566def44f -r 50bab568625d flake.lock --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flake.lock Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1783224372, + "narHash": "sha256-8i/87eeoqiGE4yOTjwSA3Eh/ziJRQEmd/unYU+K27sk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d407951447dcd00442e97087bf374aad70c04cea", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff -r 733566def44f -r 50bab568625d flake.nix --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flake.nix Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,65 @@ +{ + description = "swindle: a dwl fork with Lua config, IPC, and ext-workspace support, plus its smsg client"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { + packages.default = pkgs.stdenv.mkDerivation rec { + pname = "swindle"; + version = "0.1"; + + src = self; + + nativeBuildInputs = with pkgs; [ + pkg-config + wayland-scanner + wayland-protocols + ]; + + buildInputs = with pkgs; [ + wayland + wlroots_0_20 + pixman + libxkbcommon + libinput + lua5_4 + libxcb + libxcb-wm + ]; + + enableParallelBuilding = true; + + postPatch = '' + substituteInPlace Makefile \ + --replace '$(DESTDIR)/etc/swindle' '$(DESTDIR)$(PREFIX)/etc/swindle' + ''; + + makeFlags = [ "PREFIX=${placeholder "out"}" ]; + + meta = with pkgs.lib; { + description = "A dwl fork with Lua configuration, IPC support, and ext-workspace-v1"; + license = with licenses; [ gpl3Only mit isc ]; + platforms = platforms.linux; + mainProgram = "swindle"; + }; + }; + + devShells.default = pkgs.mkShell { + inputsFrom = [ self.packages.${system}.default ]; + packages = with pkgs; [ pkg-config gdb ]; + }; + + apps.default = flake-utils.lib.mkApp { + drv = self.packages.${system}.default; + }; + }); +} + diff -r 733566def44f -r 50bab568625d include/client.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/client.h Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,407 @@ +/* + * Attempt to consolidate unavoidable suck into one file, away from dwl.c. This + * file is not meant to be pretty. We use a .h file with static inline + * functions instead of a separate .c module, or function pointers like sway, so + * that they will simply compile out if the chosen #defines leave them unused. + */ + +/* Leave these functions first; they're used in the others */ +static inline int +client_is_x11(Client *c) +{ +#ifdef XWAYLAND + return c->type == X11; +#endif + return 0; +} + +static inline struct wlr_surface * +client_surface(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->surface; +#endif + return c->surface.xdg->surface; +} + +static inline int +toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl) +{ + struct wlr_xdg_surface *xdg_surface, *tmp_xdg_surface; + struct wlr_surface *root_surface; + struct wlr_layer_surface_v1 *layer_surface; + Client *c = NULL; + LayerSurface *l = NULL; + int type = -1; +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface; +#endif + + if (!s) + return -1; + root_surface = wlr_surface_get_root_surface(s); + +#ifdef XWAYLAND + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(root_surface))) { + c = xsurface->data; + type = c->type; + goto end; + } +#endif + + if ((layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(root_surface))) { + l = layer_surface->data; + type = LayerShell; + goto end; + } + + xdg_surface = wlr_xdg_surface_try_from_wlr_surface(root_surface); + while (xdg_surface) { + tmp_xdg_surface = NULL; + switch (xdg_surface->role) { + case WLR_XDG_SURFACE_ROLE_POPUP: + if (!xdg_surface->popup || !xdg_surface->popup->parent) + return -1; + + tmp_xdg_surface = wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent); + + if (!tmp_xdg_surface) + return toplevel_from_wlr_surface(xdg_surface->popup->parent, pc, pl); + + xdg_surface = tmp_xdg_surface; + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + c = xdg_surface->data; + type = c->type; + goto end; + case WLR_XDG_SURFACE_ROLE_NONE: + return -1; + } + } + +end: + if (pl) + *pl = l; + if (pc) + *pc = c; + return type; +} + +/* The others */ +static inline void +client_activate_surface(struct wlr_surface *s, int activated) +{ + struct wlr_xdg_toplevel *toplevel; +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface; + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) { + wlr_xwayland_surface_activate(xsurface, activated); + return; + } +#endif + if ((toplevel = wlr_xdg_toplevel_try_from_wlr_surface(s))) + wlr_xdg_toplevel_set_activated(toplevel, activated); +} + +static inline uint32_t +client_set_bounds(Client *c, int32_t width, int32_t height) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return 0; +#endif + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) >= + XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION && width >= 0 && height >= 0 + && (c->bounds.width != width || c->bounds.height != height)) { + c->bounds.width = width; + c->bounds.height = height; + return wlr_xdg_toplevel_set_bounds(c->surface.xdg->toplevel, width, height); + } + return 0; +} + +static inline const char * +client_get_appid(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->class ? c->surface.xwayland->class : "broken"; +#endif + return c->surface.xdg->toplevel->app_id ? c->surface.xdg->toplevel->app_id : "broken"; +} + +static inline void +client_get_clip(Client *c, struct wlr_box *clip) +{ + *clip = (struct wlr_box){ + .x = 0, + .y = 0, + .width = c->geom.width - c->bw, + .height = c->geom.height - c->bw, + }; + +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + + clip->x = c->surface.xdg->geometry.x; + clip->y = c->surface.xdg->geometry.y; +} + +static inline void +client_get_geometry(Client *c, struct wlr_box *geom) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + geom->x = c->surface.xwayland->x; + geom->y = c->surface.xwayland->y; + geom->width = c->surface.xwayland->width; + geom->height = c->surface.xwayland->height; + return; + } +#endif + *geom = c->surface.xdg->geometry; +} + +static inline Client * +client_get_parent(Client *c) +{ + Client *p = NULL; +#ifdef XWAYLAND + if (client_is_x11(c)) { + if (c->surface.xwayland->parent) + toplevel_from_wlr_surface(c->surface.xwayland->parent->surface, &p, NULL); + return p; + } +#endif + if (c->surface.xdg->toplevel->parent) + toplevel_from_wlr_surface(c->surface.xdg->toplevel->parent->base->surface, &p, NULL); + return p; +} + +static inline int +client_has_children(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return !wl_list_empty(&c->surface.xwayland->children); +#endif + /* surface.xdg->link is never empty because it always contains at least the + * surface itself. */ + return wl_list_length(&c->surface.xdg->link) > 1; +} + +static inline const char * +client_get_title(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->title ? c->surface.xwayland->title : "broken"; +#endif + return c->surface.xdg->toplevel->title ? c->surface.xdg->toplevel->title : "broken"; +} + +static inline int +client_is_float_type(Client *c) +{ + struct wlr_xdg_toplevel *toplevel; + struct wlr_xdg_toplevel_state state; + +#ifdef XWAYLAND + if (client_is_x11(c)) { + struct wlr_xwayland_surface *surface = c->surface.xwayland; + xcb_size_hints_t *size_hints = surface->size_hints; + if (surface->modal) + return 1; + + if (wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DIALOG) + || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_SPLASH) + || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_TOOLBAR) + || wlr_xwayland_surface_has_window_type(surface, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_UTILITY)) { + return 1; + } + + return size_hints && size_hints->min_width > 0 && size_hints->min_height > 0 + && (size_hints->max_width == size_hints->min_width + || size_hints->max_height == size_hints->min_height); + } +#endif + + toplevel = c->surface.xdg->toplevel; + state = toplevel->current; + return toplevel->parent || (state.min_width != 0 && state.min_height != 0 + && (state.min_width == state.max_width + || state.min_height == state.max_height)); +} + +static inline int +client_is_rendered_on_mon(Client *c, Monitor *m) +{ + /* This is needed for when you don't want to check formal assignment, + * but rather actual displaying of the pixels. + * Usually VISIBLEON suffices and is also faster. */ + struct wlr_surface_output *s; + int unused_lx, unused_ly; + if (!wlr_scene_node_coords(&c->scene->node, &unused_lx, &unused_ly)) + return 0; + wl_list_for_each(s, &client_surface(c)->current_outputs, link) + if (s->output == m->wlr_output) + return 1; + return 0; +} + +static inline int +client_is_stopped(Client *c) +{ + int pid; + siginfo_t in = {0}; +#ifdef XWAYLAND + if (client_is_x11(c)) + return 0; +#endif + + wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); + if (waitid(P_PID, pid, &in, WNOHANG|WCONTINUED|WSTOPPED|WNOWAIT) < 0) { + /* This process is not our child process, while is very unlikely that + * it is stopped, in order to do not skip frames, assume that it is. */ + if (errno == ECHILD) + return 1; + } else if (in.si_pid) { + if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED) + return 1; + if (in.si_code == CLD_CONTINUED) + return 0; + } + + return 0; +} + +static inline int +client_is_unmanaged(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->override_redirect; +#endif + return 0; +} + +static inline void +client_notify_enter(struct wlr_surface *s, struct wlr_keyboard *kb) +{ + if (kb) + wlr_seat_keyboard_notify_enter(seat, s, kb->keycodes, + kb->num_keycodes, &kb->modifiers); + else + wlr_seat_keyboard_notify_enter(seat, s, NULL, 0, NULL); +} + +static inline void +client_send_close(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_close(c->surface.xwayland); + return; + } +#endif + wlr_xdg_toplevel_send_close(c->surface.xdg->toplevel); +} + +static inline void +client_set_border_color(Client *c, const float inner[static 4], const float outer[static 4]) +{ + int i; + for (i = 0; i < 4; i++) { + wlr_scene_rect_set_color(c->inner_border[i], inner); + wlr_scene_rect_set_color(c->outer_border[i], outer); + } +} + +static inline void +client_set_fullscreen(Client *c, int fullscreen) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_set_fullscreen(c->surface.xwayland, fullscreen); + return; + } +#endif + wlr_xdg_toplevel_set_fullscreen(c->surface.xdg->toplevel, fullscreen); +} + +static inline void +client_set_scale(struct wlr_surface *s, float scale) +{ + wlr_fractional_scale_v1_notify_scale(s, scale); + wlr_surface_set_preferred_buffer_scale(s, (int32_t)ceilf(scale)); +} + +static inline uint32_t +client_set_size(Client *c, uint32_t width, uint32_t height) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_configure(c->surface.xwayland, + c->geom.x + c->bw, c->geom.y + c->bw, width, height); + return 0; + } +#endif + if ((int32_t)width == c->surface.xdg->toplevel->current.width + && (int32_t)height == c->surface.xdg->toplevel->current.height) + return 0; + return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)height); +} + +static inline void +client_set_tiled(Client *c, uint32_t edges) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_set_maximized(c->surface.xwayland, + edges != WLR_EDGE_NONE, edges != WLR_EDGE_NONE); + return; + } +#endif + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) + >= XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION) { + wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges); + } else { + wlr_xdg_toplevel_set_maximized(c->surface.xdg->toplevel, edges != WLR_EDGE_NONE); + } +} + +static inline void +client_set_suspended(Client *c, int suspended) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + + wlr_xdg_toplevel_set_suspended(c->surface.xdg->toplevel, suspended); +} + +static inline int +client_wants_focus(Client *c) +{ +#ifdef XWAYLAND + return client_is_unmanaged(c) + && wlr_xwayland_surface_override_redirect_wants_focus(c->surface.xwayland) + && wlr_xwayland_surface_icccm_input_model(c->surface.xwayland) != WLR_ICCCM_INPUT_MODEL_NONE; +#endif + return 0; +} + +static inline int +client_wants_fullscreen(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->fullscreen; +#endif + return c->surface.xdg->toplevel->requested.fullscreen; +} + diff -r 733566def44f -r 50bab568625d include/dwl-ipc.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/dwl-ipc.h Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,284 @@ +#include "dwl-ipc-unstable-v2-protocol.h" + +/* Forward declarations for dwl.c static functions */ + +static void arrange(Monitor *m); +static void focusclient(Client *c, int lift); +static Client *focustop(Monitor *m); +static void printstatus(void); + +/* globals */ + +static struct wl_global *ipc_global = NULL; +/* All bound manager objects */ +static struct wl_list ipc_managers; /* IpcManager.link */ + +typedef struct { + struct wl_resource *resource; + struct wl_list link; /* ipc_managers */ + struct wl_list outputs; /* IpcOutput.link */ +} IpcManager; + +typedef struct { + struct wl_resource *resource; + Monitor *mon; + struct wl_list link; /* IpcManager.outputs */ +} IpcOutput; + +/* helpers */ + +static IpcOutput * +ipc_output_for_mon(IpcManager *mgr, Monitor *m) +{ + IpcOutput *o; + wl_list_for_each(o, &mgr->outputs, link) + if (o->mon == m) + return o; + return NULL; +} + +/* Send the full state of monitor m to one IpcOutput resource */ +static void +ipc_send_output_state(IpcOutput *ipc_out) +{ + Monitor *m = ipc_out->mon; + struct wl_resource *res = ipc_out->resource; + Client *c; + uint32_t occ = 0, urg = 0, sel_tags = 0; + int tag, focused; + + /* Compute occupancy/urgency (same logic as printstatus, I think) */ + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + + c = focustop(m); + sel_tags = c ? c->tags : 0; + + /* active */ + zdwl_ipc_output_v2_send_active(res, m == selmon ? 1 : 0); + + /* per-tag state */ + for (tag = 0; tag < TAGCOUNT; tag++) { + uint32_t mask = 1u << tag; + uint32_t state = ZDWL_IPC_OUTPUT_V2_TAG_STATE_NONE; + uint32_t clients_on_tag = 0; + + if (m->tagset[m->seltags] & mask) + state = ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE; + if (urg & mask) + state = ZDWL_IPC_OUTPUT_V2_TAG_STATE_URGENT; + if (occ & mask) + clients_on_tag = 1; /* report ≥1, not exact count */ + + focused = (sel_tags & mask) ? 1 : 0; + zdwl_ipc_output_v2_send_tag(res, tag, state, clients_on_tag, focused); + } + + /* layout index */ + { + uint32_t i; + for (i = 0; i < LENGTH(layouts); i++) + if (&layouts[i] == m->lt[m->sellt]) + break; + zdwl_ipc_output_v2_send_layout(res, i); + } + + /* layout symbol */ + zdwl_ipc_output_v2_send_layout_symbol(res, m->ltsymbol); + + /* title/appid/fullscreen/floating */ + if (c) { + zdwl_ipc_output_v2_send_title(res, client_get_title(c)); + zdwl_ipc_output_v2_send_appid(res, client_get_appid(c)); + if (wl_resource_get_version(res) >= 2) { + zdwl_ipc_output_v2_send_fullscreen(res, c->isfullscreen ? 1 : 0); + zdwl_ipc_output_v2_send_floating(res, c->isfloating ? 1 : 0); + } + } else { + zdwl_ipc_output_v2_send_title(res, ""); + zdwl_ipc_output_v2_send_appid(res, ""); + if (wl_resource_get_version(res) >= 2) { + zdwl_ipc_output_v2_send_fullscreen(res, 0); + zdwl_ipc_output_v2_send_floating(res, 0); + } + } + + /* frame — signals end of this batch of events */ + zdwl_ipc_output_v2_send_frame(res); +} + +/* Called instead of (or alongside) the old printstatus() */ +static void +ipc_printstatus(void) +{ + IpcManager *mgr; + Monitor *m; + + wl_list_for_each(mgr, &ipc_managers, link) { + wl_list_for_each(m, &mons, link) { + IpcOutput *out = ipc_output_for_mon(mgr, m); + if (out) + ipc_send_output_state(out); + } + } +} + +/* zdwl_ipc_output_v2 requests */ + +static void +ipc_output_handle_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +ipc_output_handle_set_tags(struct wl_client *client, struct wl_resource *resource, + uint32_t tagmask, uint32_t toggle_tagset) +{ + IpcOutput *out = wl_resource_get_user_data(resource); + Monitor *m = out->mon; + if (!m) return; + + if (toggle_tagset) + m->seltags ^= 1; + m->tagset[m->seltags] = tagmask & TAGMASK; + focusclient(focustop(m), 1); + arrange(m); + printstatus(); +} + +static void +ipc_output_handle_set_client_tags(struct wl_client *client, + struct wl_resource *resource, uint32_t and_tags, uint32_t xor_tags) +{ + IpcOutput *out = wl_resource_get_user_data(resource); + Monitor *m = out->mon; + Client *sel; + uint32_t newtags; + if (!m) return; + + sel = focustop(m); + if (!sel) return; + + newtags = (sel->tags & and_tags) ^ xor_tags; + if (!newtags) return; + sel->tags = newtags; + focusclient(focustop(m), 1); + arrange(m); + printstatus(); +} + +static void +ipc_output_handle_set_layout(struct wl_client *client, + struct wl_resource *resource, uint32_t idx) +{ + IpcOutput *out = wl_resource_get_user_data(resource); + Monitor *m = out->mon; + if (!m || idx >= LENGTH(layouts)) return; + + m->lt[m->sellt] = &layouts[idx]; + strncpy(m->ltsymbol, layouts[idx].symbol, LENGTH(m->ltsymbol)); + arrange(m); + printstatus(); +} + +static const struct zdwl_ipc_output_v2_interface ipc_output_impl = { + .release = ipc_output_handle_release, + .set_tags = ipc_output_handle_set_tags, + .set_client_tags = ipc_output_handle_set_client_tags, + .set_layout = ipc_output_handle_set_layout, +}; + +static void +ipc_output_destroy(struct wl_resource *resource) +{ + IpcOutput *out = wl_resource_get_user_data(resource); + wl_list_remove(&out->link); + free(out); +} + +/* zdwl_ipc_manager_v2 requests */ + +static void +ipc_manager_handle_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +ipc_manager_handle_get_output(struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *output_resource) +{ + IpcManager *mgr = wl_resource_get_user_data(resource); + struct wlr_output *wlr_out = wlr_output_from_resource(output_resource); + Monitor *m = (wlr_out && wlr_out->data) ? wlr_out->data : NULL; + + IpcOutput *out = ecalloc(1, sizeof(*out)); + out->mon = m; + out->resource = wl_resource_create(client, &zdwl_ipc_output_v2_interface, + wl_resource_get_version(resource), id); + if (!out->resource) { + free(out); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(out->resource, &ipc_output_impl, + out, ipc_output_destroy); + wl_list_insert(&mgr->outputs, &out->link); + + if (m) + ipc_send_output_state(out); +} + +static const struct zdwl_ipc_manager_v2_interface ipc_manager_impl = { + .release = ipc_manager_handle_release, + .get_output = ipc_manager_handle_get_output, +}; + +static void +ipc_manager_destroy(struct wl_resource *resource) +{ + IpcManager *mgr = wl_resource_get_user_data(resource); + IpcOutput *out, *tmp; + wl_list_for_each_safe(out, tmp, &mgr->outputs, link) + wl_resource_destroy(out->resource); + wl_list_remove(&mgr->link); + free(mgr); +} + +/* global bind */ + +static void +ipc_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) +{ + IpcManager *mgr = ecalloc(1, sizeof(*mgr)); + wl_list_init(&mgr->outputs); + mgr->resource = wl_resource_create(client, &zdwl_ipc_manager_v2_interface, + version, id); + if (!mgr->resource) { + free(mgr); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(mgr->resource, &ipc_manager_impl, + mgr, ipc_manager_destroy); + wl_list_insert(&ipc_managers, &mgr->link); + + /* Announce tags and layouts */ + zdwl_ipc_manager_v2_send_tags(mgr->resource, TAGCOUNT); + for (uint32_t i = 0; i < LENGTH(layouts); i++) + zdwl_ipc_manager_v2_send_layout(mgr->resource, layouts[i].symbol); +} + +static void +ipc_init(void) +{ + wl_list_init(&ipc_managers); + ipc_global = wl_global_create(dpy, &zdwl_ipc_manager_v2_interface, + 2, NULL, ipc_bind); +} diff -r 733566def44f -r 50bab568625d include/ext-workspace.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/ext-workspace.h Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,210 @@ +/* My shitty port of Mango's ext-workspace.h for my compositor; + * thanks DreamMaoMao for doing most of the heavy lifting + * by actually writing the thing, it didn't work the first + * few tries and I barely have any idea how this code works anyway. + * To say that it's the eighth wonder of the world + * would be an understatement +*/ + +#include "wlr_ext_workspace_v1.h" + +#define EXT_WORKSPACE_CAPS \ + (EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE | \ + EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE) + +/* types */ + +struct ext_workspace { + struct wl_list link; + uint32_t tag; + Monitor *m; + struct wlr_ext_workspace_handle_v1 *handle; +}; + +/* global */ + +static struct wlr_ext_workspace_manager_v1 *ext_workspace_manager; +static struct wl_list ext_workspaces; +static struct wl_listener ext_commit_listener; +static struct wl_listener ext_destroy_listener; + +/* helpers */ + + +static int +get_tag_status(uint32_t tag, Monitor *m) +{ + Client *c; + uint32_t mask = 1 << (tag - 1); + + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + if (!(c->tags & mask)) + continue; + if (c->isurgent) + return 2; + return 1; + } + return 0; +} + +static const char * +tag_name(uint32_t tag) +{ + static const char *names[] = { + "1", "2", "3", "4", "5", "6", "7", "8", "9", + }; + if (tag >= 1 && tag <= LENGTH(names)) + return names[tag - 1]; + return "?"; +} + +/* workspace lifecycle */ + +static void +ext_workspace_destroy(struct ext_workspace *ws) +{ + wlr_ext_workspace_handle_v1_destroy(ws->handle); + wl_list_remove(&ws->link); + free(ws); +} + +static void +ext_workspace_create(uint32_t tag, Monitor *m) +{ + const char *name = tag_name(tag); + + struct ext_workspace *ws = ecalloc(1, sizeof(*ws)); + ws->tag = tag; + ws->m = m; + ws->handle = wlr_ext_workspace_handle_v1_create( + ext_workspace_manager, name, EXT_WORKSPACE_CAPS); + ws->handle->data = ws; + + wlr_ext_workspace_handle_v1_set_group(ws->handle, m->ext_group); + wlr_ext_workspace_handle_v1_set_name(ws->handle, name); + + wl_list_insert(ext_workspaces.prev, &ws->link); +} + +/* monitor integration */ + +static void +ext_workspace_createmon(Monitor *m) +{ + uint32_t i; + + m->ext_group = wlr_ext_workspace_group_handle_v1_create( + ext_workspace_manager, 0); + wlr_ext_workspace_group_handle_v1_output_enter( + m->ext_group, m->wlr_output); + + for (i = 1; i <= TAGCOUNT; i++) + ext_workspace_create(i, m); +} + +static void +ext_workspace_cleanupmon(Monitor *m) +{ + struct ext_workspace *ws, *tmp; + wl_list_for_each_safe(ws, tmp, &ext_workspaces, link) + if (ws->m == m) + ext_workspace_destroy(ws); + + wlr_ext_workspace_group_handle_v1_output_leave( + m->ext_group, m->wlr_output); + wlr_ext_workspace_group_handle_v1_destroy(m->ext_group); + m->ext_group = NULL; +} + +/* status */ + +static void +ext_workspace_printstatus(Monitor *m) +{ + struct ext_workspace *ws; + + wl_list_for_each(ws, &ext_workspaces, link) { + if (ws->m != m) + continue; + + int status = get_tag_status(ws->tag, m); + int active = !!(m->tagset[m->seltags] & (1 << (ws->tag - 1)) & TAGMASK); + + wlr_ext_workspace_handle_v1_set_urgent(ws->handle, status == 2); + wlr_ext_workspace_handle_v1_set_hidden(ws->handle, 0); + wlr_ext_workspace_handle_v1_set_active(ws->handle, active); + } +} + +/* commit handler */ + +static void +handle_ext_commit(struct wl_listener *listener, void *data) +{ + struct wlr_ext_workspace_v1_commit_event *event = data; + struct wlr_ext_workspace_v1_request *req; + + wl_list_for_each(req, event->requests, link) { + struct ext_workspace *ws = NULL; + struct ext_workspace *w; + + switch (req->type) { + case WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE: + if (!req->activate.workspace) + break; + wl_list_for_each(w, &ext_workspaces, link) { + if (w->handle == req->activate.workspace) { + ws = w; + break; + } + } + if (!ws) + break; + view(&(Arg){.ui = 1 << (ws->tag - 1)}); + break; + + case WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE: + if (!req->deactivate.workspace) + break; + wl_list_for_each(w, &ext_workspaces, link) { + if (w->handle == req->deactivate.workspace) { + ws = w; + break; + } + } + if (!ws) + break; + toggleview(&(Arg){.ui = 1 << (ws->tag - 1)}); + break; + + default: + break; + } + } +} + +/* init */ + +static void +handle_ext_destroy(struct wl_listener *listener, void *data) +{ + wl_list_remove(&ext_commit_listener.link); + wl_list_remove(&ext_destroy_listener.link); +} + +static void +workspaces_init(void) +{ + ext_workspace_manager = wlr_ext_workspace_manager_v1_create(dpy, 1); + wl_list_init(&ext_workspaces); + + ext_commit_listener.notify = handle_ext_commit; + wl_signal_add(&ext_workspace_manager->events.commit, + &ext_commit_listener); + + ext_destroy_listener.notify = handle_ext_destroy; + wl_signal_add(&ext_workspace_manager->events.destroy, + &ext_destroy_listener); +} diff -r 733566def44f -r 50bab568625d include/lua5.4/lauxlib.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/lua5.4/lauxlib.h Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,1 @@ +/nix/store/0i6c2awwzk0padnbmx1fi8akh0g76wr0-lua-5.4.7/include/lauxlib.h \ No newline at end of file diff -r 733566def44f -r 50bab568625d include/lua5.4/lua.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/lua5.4/lua.h Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,1 @@ +/nix/store/0i6c2awwzk0padnbmx1fi8akh0g76wr0-lua-5.4.7/include/lua.h \ No newline at end of file diff -r 733566def44f -r 50bab568625d include/lua5.4/luaconf.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/lua5.4/luaconf.h Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,1 @@ +/nix/store/0i6c2awwzk0padnbmx1fi8akh0g76wr0-lua-5.4.7/include/luaconf.h \ No newline at end of file diff -r 733566def44f -r 50bab568625d include/lua5.4/lualib.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/lua5.4/lualib.h Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,1 @@ +/nix/store/0i6c2awwzk0padnbmx1fi8akh0g76wr0-lua-5.4.7/include/lualib.h \ No newline at end of file diff -r 733566def44f -r 50bab568625d licenses/LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/licenses/LICENSE Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,351 @@ +swindle - wayland compositor + +Copyright © 2026 kantiankant + +See also the files LICENSE.dwl, LICENSE.dwm, +LICENSE.tinywl, LICENSE.sway, LICENSE.wlroots, LICENSE.mangowm. + +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, version 2 of the License. + +---- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 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 licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Moe Ghoul, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r 733566def44f -r 50bab568625d licenses/LICENSE.dwl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/licenses/LICENSE.dwl Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,692 @@ +dwl - dwm for Wayland + +Copyright © 2020 dwl team + +See also the files LICENSE.tinywl, LICENSE.dwm and LICENSE.sway. + +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. + +---- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff -r 733566def44f -r 50bab568625d licenses/LICENSE.dwlmsg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/licenses/LICENSE.dwlmsg Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,12 @@ +Copyright (C) 2025 by notchoc + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff -r 733566def44f -r 50bab568625d licenses/LICENSE.dwm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/licenses/LICENSE.dwm Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,39 @@ +Portions of dwl based on dwm code are used under the following license: + +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2009 Jukka Salmi +© 2006-2007 Sander van Dijk +© 2007-2011 Peter Hartlich +© 2007-2009 Szabolcs Nagy +© 2007-2009 Christof Musik +© 2007-2009 Premysl Hruby +© 2007-2008 Enno Gottox Boland +© 2008 Martin Hurton +© 2008 Neale Pickett +© 2009 Mate Nagy +© 2010-2016 Hiltjo Posthuma +© 2010-2012 Connor Lane Smith +© 2011 Christoph Lohmann <20h@r-36.net> +© 2015-2016 Quentin Rameau +© 2015-2016 Eric Pruitt +© 2016-2017 Markus Teich + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff -r 733566def44f -r 50bab568625d licenses/LICENSE.mangowm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/licenses/LICENSE.mangowm Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,692 @@ +mango - wayland compositor base wlroots + +Copyright © 2025 DreamMaoMao + +See also the files LICENSE.dwl, LICENSE.tinywl, LICENSE.dwm and LICENSE.sway. + +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. + +---- + + 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 + + 1. 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 +. \ No newline at end of file diff -r 733566def44f -r 50bab568625d licenses/LICENSE.sway --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/licenses/LICENSE.sway Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,19 @@ +Copyright (c) 2016-2017 Drew DeVault + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff -r 733566def44f -r 50bab568625d licenses/LICENSE.tinywl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/licenses/LICENSE.tinywl Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,127 @@ +dwl is originally based on TinyWL, which is used under the following license: + +This work is licensed under CC0, which effectively puts it in the public domain. + +--- + +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff -r 733566def44f -r 50bab568625d licenses/LICENSE.wlroots --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/licenses/LICENSE.wlroots Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,21 @@ +Copyright (c) 2017, 2018 Drew DeVault +Copyright (c) 2014 Jari Vetoniemi +Copyright (c) 2023 The wlroots contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff -r 733566def44f -r 50bab568625d parser/parser.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/parser/parser.c Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,727 @@ +/* Here be a small dragon. + * Not as large as the monster + * in src/swindle.c + * but you get the idea */ + +#include "parser.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Lua */ +#include +#include +#include + +/* wlroots */ +#include +#include +#include + +/* Forward declarations from src/swindle.c */ +extern struct wl_list mons; +extern struct wl_list clients; +void arrange(void *m); + +/* Internal watcher state */ + +typedef struct { + int inotify_fd; + int watch_fd; + char path[1024]; + config_reload_cb cb; + void *userdata; + struct wl_event_source *event_src; +} WatchState; + +/* Helpers */ + +static void +lua_log_error(lua_State *L, const char *context) +{ + fprintf(stderr, "swindle config [%s]: %s\n", + context, lua_tostring(L, -1)); + lua_pop(L, 1); +} + +/* Get a string field from the table at the top of the stack + * Writes into buf and returns false and leaves buf untouched on failure + */ + +static bool +lua_get_string(lua_State *L, const char *key, char *buf, size_t bufsz) +{ + lua_getfield(L, -1, key); + bool ok = lua_isstring(L, -1); + if (ok) + strncpy(buf, lua_tostring(L, -1), bufsz - 1); + lua_pop(L, 1); + return ok; +} + +/* Get an integer field. It falls back to `def` if missing or wrong type */ + +static int +lua_get_int(lua_State *L, const char *key, int def) +{ + lua_getfield(L, -1, key); + int val = lua_isnumber(L, -1) ? (int)lua_tointeger(L, -1) : def; + lua_pop(L, 1); + return val; +} + +static double +lua_get_double(lua_State *L, const char *key, double def) +{ + lua_getfield(L, -1, key); + double val = lua_isnumber(L, -1) ? lua_tonumber(L, -1) : def; + lua_pop(L, 1); + return val; +} + +static bool +lua_get_bool(lua_State *L, const char *key, bool def) +{ + lua_getfield(L, -1, key); + bool val = lua_isboolean(L, -1) ? lua_toboolean(L, -1) : def; + lua_pop(L, 1); + return val; +} + +static void +parse_color_hex(uint32_t hex, float out[4]) +{ + out[0] = ((hex >> 24) & 0xFF) / 255.0f; + out[1] = ((hex >> 16) & 0xFF) / 255.0f; + out[2] = ((hex >> 8) & 0xFF) / 255.0f; + out[3] = ( hex & 0xFF) / 255.0f; +} + +static void +lua_get_color(lua_State *L, const char *key, float out[4], + float r, float g, float b, float a) +{ + lua_getfield(L, -1, key); + if (lua_isnumber(L, -1)) + parse_color_hex((uint32_t)lua_tointeger(L, -1), out); + else { + out[0] = r; out[1] = g; out[2] = b; out[3] = a; + } + lua_pop(L, 1); +} + +static uint32_t +parse_modifier(const char *name) +{ + if (!strcmp(name, "shift")) return WLR_MODIFIER_SHIFT; + if (!strcmp(name, "ctrl")) return WLR_MODIFIER_CTRL; + if (!strcmp(name, "alt")) return WLR_MODIFIER_ALT; + if (!strcmp(name, "logo")) return WLR_MODIFIER_LOGO; + if (!strcmp(name, "mod2")) return WLR_MODIFIER_MOD2; + if (!strcmp(name, "mod3")) return WLR_MODIFIER_MOD3; + if (!strcmp(name, "mod5")) return WLR_MODIFIER_MOD5; + fprintf(stderr, "swindle config: unknown modifier '%s', ignoring\n", name); + return 0; +} + +/* Section parsers */ + +static void +parse_appearance(lua_State *L, CfgAppearance *a) +{ + lua_getglobal(L, "appearance"); + if (!lua_istable(L, -1)) { + fprintf(stderr, "swindle config: 'appearance' table missing' \n"); + lua_pop(L, 1); + /* defaults */ + a->inner_border_px = 1; + a->outer_border_px = 1; + a->gaps = 10; + a->smart_gaps = false; + parse_color_hex(0x222222ff, a->root_color); + parse_color_hex(0x00000000, a->inner_border_color); + parse_color_hex(0x00000000, a->outer_border_color); + parse_color_hex(0xe8e8e8ff, a->inner_focus_color); + parse_color_hex(0xe8e8e8ff, a->outer_focus_color); + parse_color_hex(0xff0000ff, a->inner_urgent_color); + parse_color_hex(0xff0000ff, a->outer_urgent_color); + a->fullscreen_bg[3] = 1.0f; + return; + } + + a->inner_border_px = (unsigned int)lua_get_int(L, "inner_border_px", 1); + a->outer_border_px = (unsigned int)lua_get_int(L, "outer_border_px", 1); + a->gaps = (unsigned int)lua_get_int(L, "gaps", 10); + a->smart_gaps = lua_get_bool(L, "smart_gaps", false); + + lua_get_color(L, "root_color", a->root_color, 0.133f,0.133f,0.133f,1.f); + lua_get_color(L, "inner_border_color", a->inner_border_color, 0.f,0.f,0.f,0.f); + lua_get_color(L, "outer_border_color", a->outer_border_color, 0.f,0.f,0.f,0.f); + lua_get_color(L, "inner_focus_color", a->inner_focus_color, 0.91f,0.91f,0.91f,1.f); + lua_get_color(L, "outer_focus_color", a->outer_focus_color, 0.91f,0.91f,0.91f,1.f); + lua_get_color(L, "inner_urgent_color", a->inner_urgent_color, 1.f,0.f,0.f,1.f); + lua_get_color(L, "outer_urgent_color", a->outer_urgent_color, 1.f,0.f,0.f,1.f); + + lua_getfield(L, -1, "fullscreen_bg"); + if (lua_isnumber(L, -1)) + parse_color_hex((uint32_t)lua_tointeger(L, -1), a->fullscreen_bg); + else { + a->fullscreen_bg[0] = 0.f; + a->fullscreen_bg[1] = 0.f; + a->fullscreen_bg[2] = 0.f; + a->fullscreen_bg[3] = 1.f; + } + lua_pop(L, 1); + + lua_pop(L, 1); /* pop appearance table */ +} + +static void +parse_input(lua_State *L, CfgInput *inp) +{ + lua_getglobal(L, "input"); + if (!lua_istable(L, -1)) { + fprintf(stderr, "swindle config: 'input' table missing, " + "using defaults. You'll feel the repeat delay.\n"); + lua_pop(L, 1); + inp->repeat_rate = 50; + inp->repeat_delay = 150; + inp->tap_to_click = true; + inp->accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; + return; + } + + inp->repeat_rate = lua_get_int (L, "repeat_rate", 50); + inp->repeat_delay = lua_get_int (L, "repeat_delay", 150); + inp->tap_to_click = lua_get_bool(L, "tap_to_click", true); + inp->tap_and_drag = lua_get_bool(L, "tap_and_drag", true); + inp->drag_lock = lua_get_bool(L, "drag_lock", true); + inp->natural_scrolling = lua_get_bool(L, "natural_scrolling", false); + inp->disable_while_typing = lua_get_bool(L, "disable_while_typing", true); + inp->left_handed = lua_get_bool(L, "left_handed", false); + inp->middle_button_emulation= lua_get_bool(L, "middle_button_emulation",false); + inp->accel_speed = lua_get_double(L, "accel_speed", 0.0); + + char scratch[CFG_MAX_STRLEN] = {0}; + + lua_get_string(L, "scroll_method", scratch, sizeof(scratch)); + if (!strcmp(scratch, "2fg")) inp->scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + else if (!strcmp(scratch, "edge")) inp->scroll_method = LIBINPUT_CONFIG_SCROLL_EDGE; + else if (!strcmp(scratch, "button")) inp->scroll_method = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; + else inp->scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + + lua_get_string(L, "accel_profile", scratch, sizeof(scratch)); + if (!strcmp(scratch, "flat")) inp->accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; + else inp->accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; + + lua_get_string(L, "click_method", scratch, sizeof(scratch)); + if (!strcmp(scratch, "clickfinger")) inp->click_method = LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; + else if (!strcmp(scratch, "button_areas")) inp->click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + else inp->click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + + lua_get_string(L, "send_events_mode", scratch, sizeof(scratch)); + if (!strcmp(scratch, "disabled")) inp->send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; + else if (!strcmp(scratch, "disabled_on_external")) inp->send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; + else inp->send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + + lua_get_string(L, "tap_button_map", scratch, sizeof(scratch)); + if (!strcmp(scratch, "lmr")) inp->tap_button_map = LIBINPUT_CONFIG_TAP_MAP_LMR; + else inp->tap_button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; + + lua_pop(L, 1); +} + +static void +parse_rules(lua_State *L, Config *cfg) +{ + lua_getglobal(L, "rules"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return; + } + + cfg->nrules = 0; + int n = (int)lua_rawlen(L, -1); + for (int i = 1; i <= n && cfg->nrules < CFG_MAX_RULES; i++) { + lua_rawgeti(L, -1, i); + if (!lua_istable(L, -1)) { lua_pop(L, 1); continue; } + + CfgRule *r = &cfg->rules[cfg->nrules++]; + memset(r, 0, sizeof(*r)); + lua_get_string(L, "app_id", r->app_id, sizeof(r->app_id)); + lua_get_string(L, "title", r->title, sizeof(r->title)); + r->tags = (uint32_t)lua_get_int(L, "tags", 0); + r->floating = lua_get_bool(L, "floating", false); + r->monitor = lua_get_int(L, "monitor", -1); + + lua_pop(L, 1); + } + lua_pop(L, 1); +} + +static void +parse_monitors(lua_State *L, Config *cfg) +{ + lua_getglobal(L, "monitors"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + cfg->nmonitors = 1; + CfgMonitorRule *m = &cfg->monitors[0]; + memset(m, 0, sizeof(*m)); + m->mfact = 0.55f; + m->nmaster = 1; + m->scale = 1.0f; + strcpy(m->layout, "dwindle"); + m->x = m->y = -1; + return; + } + + cfg->nmonitors = 0; + int n = (int)lua_rawlen(L, -1); + for (int i = 1; i <= n && cfg->nmonitors < CFG_MAX_MONITORS; i++) { + lua_rawgeti(L, -1, i); + if (!lua_istable(L, -1)) { lua_pop(L, 1); continue; } + + CfgMonitorRule *m = &cfg->monitors[cfg->nmonitors++]; + memset(m, 0, sizeof(*m)); + + lua_getfield(L, -1, "name"); + if (lua_isstring(L, -1)) + strncpy(m->name, lua_tostring(L, -1), sizeof(m->name)-1); + lua_pop(L, 1); + + m->mfact = (float)lua_get_double(L, "mfact", 0.55); + m->nmaster = lua_get_int (L, "nmaster", 1); + m->scale = (float)lua_get_double(L, "scale", 1.0); + m->x = lua_get_int (L, "x", -1); + m->y = lua_get_int (L, "y", -1); + m->transform = lua_get_int (L, "transform", 0); + lua_get_string(L, "layout", m->layout, sizeof(m->layout)); + if (!m->layout[0]) strcpy(m->layout, "dwindle"); + + lua_pop(L, 1); + } + lua_pop(L, 1); +} + +static void +parse_keybinds(lua_State *L, Config *cfg) +{ + lua_getglobal(L, "keybinds"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return; + } + + cfg->nkeybinds = 0; + struct xkb_context *xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + int n = (int)lua_rawlen(L, -1); + for (int i = 1; i <= n && cfg->nkeybinds < CFG_MAX_KEYBINDS; i++) { + lua_rawgeti(L, -1, i); + if (!lua_istable(L, -1)) { lua_pop(L, 1); continue; } + + CfgKeybind *kb = &cfg->keybinds[cfg->nkeybinds]; + memset(kb, 0, sizeof(*kb)); + + /* mods: array of strings */ + kb->mods = 0; + lua_getfield(L, -1, "mods"); + if (lua_istable(L, -1)) { + int nm = (int)lua_rawlen(L, -1); + for (int m = 1; m <= nm; m++) { + lua_rawgeti(L, -1, m); + if (lua_isstring(L, -1)) + kb->mods |= parse_modifier(lua_tostring(L, -1)); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + /* key: string keysym name, e.g. "q", "Return", "XF86AudioMute" */ + char keyname[CFG_MAX_STRLEN] = {0}; + lua_get_string(L, "key", keyname, sizeof(keyname)); + kb->key = xkb_keysym_from_name(keyname, XKB_KEYSYM_CASE_INSENSITIVE); + if (kb->key == XKB_KEY_NoSymbol) { + fprintf(stderr, "swindle config: unknown key '%s', skipping bind\n", + keyname); + lua_pop(L, 1); /* keybind entry table */ + continue; + } + + lua_get_string(L, "action", kb->action, sizeof(kb->action)); + + /* args: optional array of strings */ + kb->nargs = 0; + lua_getfield(L, -1, "args"); + if (lua_istable(L, -1)) { + int na = (int)lua_rawlen(L, -1); + for (int a = 1; a <= na && kb->nargs < CFG_MAX_ARGS; a++) { + lua_rawgeti(L, -1, a); + if (lua_isstring(L, -1)) + strncpy(kb->args[kb->nargs++], + lua_tostring(L, -1), + CFG_MAX_STRLEN - 1); + lua_pop(L, 1); + } + } + lua_pop(L, 1); /* args */ + + cfg->nkeybinds++; + lua_pop(L, 1); /* keybind entry */ + } + + xkb_context_unref(xkb_ctx); + lua_pop(L, 1); /* keybinds table */ +} + +static void +parse_buttons(lua_State *L, Config *cfg) +{ + lua_getglobal(L, "buttons"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return; + } + + cfg->nbuttons = 0; + int n = (int)lua_rawlen(L, -1); + for (int i = 1; i <= n && cfg->nbuttons < CFG_MAX_KEYBINDS; i++) { + lua_rawgeti(L, -1, i); + if (!lua_istable(L, -1)) { lua_pop(L, 1); continue; } + + CfgButton *b = &cfg->buttons[cfg->nbuttons++]; + memset(b, 0, sizeof(*b)); + + b->mods = 0; + lua_getfield(L, -1, "mods"); + if (lua_istable(L, -1)) { + int nm = (int)lua_rawlen(L, -1); + for (int m = 1; m <= nm; m++) { + lua_rawgeti(L, -1, m); + if (lua_isstring(L, -1)) + b->mods |= parse_modifier(lua_tostring(L, -1)); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + char btnname[CFG_MAX_STRLEN] = {0}; + lua_get_string(L, "button", btnname, sizeof(btnname)); + if (!strcmp(btnname, "left")) b->button = BTN_LEFT; + else if (!strcmp(btnname, "right")) b->button = BTN_RIGHT; + else if (!strcmp(btnname, "middle")) b->button = BTN_MIDDLE; + else b->button = (uint32_t)atoi(btnname); + + lua_get_string(L, "action", b->action, sizeof(b->action)); + + b->nargs = 0; + lua_getfield(L, -1, "args"); + if (lua_istable(L, -1)) { + int na = (int)lua_rawlen(L, -1); + for (int a = 1; a <= na && b->nargs < CFG_MAX_ARGS; a++) { + lua_rawgeti(L, -1, a); + if (lua_isstring(L, -1)) + strncpy(b->args[b->nargs++], + lua_tostring(L, -1), + CFG_MAX_STRLEN - 1); + lua_pop(L, 1); + } + } + lua_pop(L, 1); /* args */ + + lua_pop(L, 1); + } + lua_pop(L, 1); +} + +static void +parse_autostart(lua_State *L, Config *cfg) +{ + lua_getglobal(L, "autostart"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return; + } + + cfg->nautostart = 0; + int n = (int)lua_rawlen(L, -1); + for (int i = 1; i <= n && cfg->nautostart < CFG_MAX_AUTOSTART; i++) { + lua_rawgeti(L, -1, i); + if (!lua_isstring(L, -1)) { + lua_pop(L, 1); + continue; + } + strncpy(cfg->autostart[cfg->nautostart].cmd, + lua_tostring(L, -1), + CFG_MAX_AUTOSTART_CMD - 1); + cfg->nautostart++; + lua_pop(L, 1); + } + lua_pop(L, 1); +} + +/* Public API */ + +char * +config_get_path(char *buf, size_t bufsz) +{ + const char *xdg = getenv("XDG_CONFIG_HOME"); + if (xdg && *xdg) + snprintf(buf, bufsz, "%s/swindle/config.lua", xdg); + else { + const char *home = getenv("HOME"); + if (!home) home = "/root"; /* you're probably root, aren't you */ + snprintf(buf, bufsz, "%s/.config/swindle/config.lua", home); + } + return buf; +} + +int +config_load(const char *path, Config *cfg) +{ + memset(cfg, 0, sizeof(*cfg)); + + lua_State *L = luaL_newstate(); + if (!L) { + fprintf(stderr, "swindle config: failed to create Lua state. " + "Truly rock bottom.\n"); + return -1; + } + luaL_openlibs(L); + + if (luaL_dofile(L, path) != LUA_OK) { + lua_log_error(L, "load"); + lua_close(L); + return -1; + } + + /* Top-level booleans */ + lua_getglobal(L, "sloppy_focus"); + cfg->sloppyfocus = lua_isboolean(L, -1) ? lua_toboolean(L, -1) : true; + lua_pop(L, 1); + + lua_getglobal(L, "bypass_surface_visibility"); + cfg->bypass_surface_visibility = lua_isboolean(L, -1) + ? lua_toboolean(L, -1) : false; + lua_pop(L, 1); + + lua_getglobal(L, "log_level"); + if (lua_isstring(L, -1)) { + const char *ll = lua_tostring(L, -1); + if (!strcmp(ll, "silent")) cfg->log_level = WLR_SILENT; + else if (!strcmp(ll, "info")) cfg->log_level = WLR_INFO; + else if (!strcmp(ll, "debug")) cfg->log_level = WLR_DEBUG; + else cfg->log_level = WLR_ERROR; + } else { + cfg->log_level = WLR_ERROR; + } + lua_pop(L, 1); + + parse_appearance(L, &cfg->appearance); + parse_input (L, &cfg->input); + parse_rules (L, cfg); + parse_monitors (L, cfg); + parse_keybinds (L, cfg); + parse_buttons (L, cfg); + parse_autostart (L, cfg); + + lua_close(L); + return 0; +} + +/* Apply functions */ + +void +config_apply_appearance(const Config *cfg) +{ + (void)cfg; +} + +void +config_apply_input(const Config *cfg) +{ + (void)cfg; +} + +void +config_apply_keybinds(const Config *cfg) +{ + (void)cfg; +} + +void +config_autostart_run(const Config *cfg) +{ + if (cfg->nautostart == 0) + return; + + pid_t pid = fork(); + if (pid < 0) { + perror("swindle autostart: fork"); + return; + } + if (pid != 0) + return; /* compositor continues (sequencer runs in child) */ + + /* The sequencer child resets any inherited signal handlers and becomes + * a new session so the compositor's SIGCHLD handling doesn't + * interfere with our waitpid calls. */ + signal(SIGCHLD, SIG_DFL); + setsid(); + + for (int i = 0; i < cfg->nautostart; i++) { + const char *cmd = cfg->autostart[i].cmd; + + pid_t cpid = fork(); + if (cpid < 0) { + fprintf(stderr, "swindle autostart: fork for '%s': %s\n", + cmd, strerror(errno)); + continue; + } + if (cpid == 0) { + execl("/bin/sh", "sh", "-c", cmd, NULL); + fprintf(stderr, "swindle autostart: exec '%s': %s\n", + cmd, strerror(errno)); + _exit(1); + } + + /* wait a second because one shotters will fly past, but + * daemon processes (some, like swaybg) won't because of + * daemon black magic. To say the least, it's good for the soul */ + struct timespec deadline, now; + clock_gettime(CLOCK_MONOTONIC, &deadline); + deadline.tv_sec += 0.5; + + int status; + int done = 0; + while (!done) { + pid_t r = waitpid(cpid, &status, WNOHANG); + if (r == cpid) { + done = 1; + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) + fprintf(stderr, "swindle autostart: '%s' exited with status %d\n", + cmd, WEXITSTATUS(status)); + } else { + clock_gettime(CLOCK_MONOTONIC, &now); + if (now.tv_sec > deadline.tv_sec || + (now.tv_sec == deadline.tv_sec && + now.tv_nsec >= deadline.tv_nsec)) + break; /* looks like a daemon, move on */ + struct timespec sl = { .tv_sec = 0, .tv_nsec = 50 * 1000 * 1000 }; + nanosleep(&sl, NULL); + } + } + } + + _exit(0); +} + +/* inotify watcher */ + +static int +watch_dispatch(int fd, uint32_t mask, void *data) +{ + WatchState *ws = data; + + /* Drain the inotify events */ + _Alignas(struct inotify_event) char buf[4096]; + ssize_t len = read(fd, buf, sizeof(buf)); + if (len < 0) { + if (errno != EAGAIN) + perror("swindle config: inotify read"); + return 0; + } + + /* Re-parse */ + Config *fresh = calloc(1, sizeof(Config)); + if (!fresh) return 0; + + if (config_load(ws->path, fresh) == 0) { + fprintf(stderr, "swindle config: reloaded '%s'\n", ws->path); + if (ws->cb) + ws->cb(fresh, ws->userdata); + } else { + fprintf(stderr, "swindle config: reload failed, keeping old config. " + "Story of my life.\n"); + } + + free(fresh); + + /* + * Some editors (vim, nano) delete-and-recreate files on save, + * which removes our watch (which is a pain in the ass. + * Hence, re-add it defensively. It's good for the soul) + */ + inotify_rm_watch(ws->inotify_fd, ws->watch_fd); + ws->watch_fd = inotify_add_watch(ws->inotify_fd, ws->path, + IN_CLOSE_WRITE | IN_MOVED_TO | IN_CREATE); + + return 0; +} + +struct wl_event_source * +config_watch_start(struct wl_event_loop *loop, const char *path, + config_reload_cb cb, void *userdata) +{ + WatchState *ws = calloc(1, sizeof(*ws)); + if (!ws) return NULL; + + ws->inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + if (ws->inotify_fd < 0) { + perror("swindle config: inotify_init1"); + free(ws); + return NULL; + } + + ws->watch_fd = inotify_add_watch(ws->inotify_fd, path, + IN_CLOSE_WRITE | IN_MOVED_TO | IN_CREATE); + if (ws->watch_fd < 0) { + perror("swindle config: inotify_add_watch"); + close(ws->inotify_fd); + free(ws); + return NULL; + } + + strncpy(ws->path, path, sizeof(ws->path) - 1); + ws->cb = cb; + ws->userdata = userdata; + + struct wl_event_source *src = wl_event_loop_add_fd( + loop, ws->inotify_fd, + WL_EVENT_READABLE, + watch_dispatch, ws + ); + + if (!src) { + fprintf(stderr, "swindle config: failed to add inotify to event loop. " + "Classic.\n"); + close(ws->inotify_fd); + free(ws); + return NULL; + } + + ws->event_src = src; + fprintf(stderr, "swindle config: watching '%s'\n", path); + return (struct wl_event_source *)ws; +} + +void +config_watch_stop(struct wl_event_source *src) +{ + if (!src) return; + WatchState *ws = (WatchState *)src; + wl_event_source_remove(ws->event_src); + close(ws->inotify_fd); + free(ws); +} diff -r 733566def44f -r 50bab568625d parser/parser.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/parser/parser.h Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,155 @@ +#ifndef SWINDLE_PARSER_H +#define SWINDLE_PARSER_H + +#include +#include +#include + +/* Maximum lengths. Good for the soul, they say */ +#define CFG_MAX_RULES 64 +#define CFG_MAX_MONITORS 16 +#define CFG_MAX_KEYBINDS 256 +#define CFG_MAX_ARGS 16 +#define CFG_MAX_STRLEN 256 +#define CFG_MAX_AUTOSTART 32 +#define CFG_MAX_AUTOSTART_CMD 512 + +/* Appearance */ + +typedef struct { + unsigned int inner_border_px; + unsigned int outer_border_px; + unsigned int gaps; + bool smart_gaps; + float inner_border_color[4]; + float outer_border_color[4]; + float inner_focus_color[4]; + float outer_focus_color[4]; + float inner_urgent_color[4]; + float outer_urgent_color[4]; + float root_color[4]; + float fullscreen_bg[4]; +} CfgAppearance; + +/* Input */ + +typedef struct { + int repeat_rate; + int repeat_delay; + bool tap_to_click; + bool tap_and_drag; + bool drag_lock; + bool natural_scrolling; + bool disable_while_typing; + bool left_handed; + bool middle_button_emulation; + int scroll_method; /* libinput enum value */ + int click_method; /* libinput enum value */ + int send_events_mode; + int accel_profile; /* libinput enum value */ + double accel_speed; + int tap_button_map; /* libinput enum value */ +} CfgInput; + +/* Window Rules */ + +typedef struct { + char app_id[CFG_MAX_STRLEN]; + char title[CFG_MAX_STRLEN]; /* empty string = wildcard */ + uint32_t tags; + bool floating; + int monitor; /* -1 = current */ +} CfgRule; + +/* Monitor Rules */ + +typedef struct { + char name[CFG_MAX_STRLEN]; /* empty = catch-all */ + float mfact; + int nmaster; + float scale; + char layout[CFG_MAX_STRLEN]; /* "dwindle", "floating", etc. */ + int transform; /* WL_OUTPUT_TRANSFORM_* */ + int x, y; /* -1,-1 = autoconfigure */ +} CfgMonitorRule; + +/* keybinds */ + +typedef struct { + uint32_t mods; /* WLR_MODIFIER_* bitmask */ + uint32_t key; /* XKB keysym */ + char action[CFG_MAX_STRLEN]; /* "spawn", "killclient", etc. */ + char args[CFG_MAX_ARGS][CFG_MAX_STRLEN]; + int nargs; +} CfgKeybind; + +/* mouse buttons */ + +typedef struct { + uint32_t mods; + uint32_t button; + char action[CFG_MAX_STRLEN]; + char args[CFG_MAX_ARGS][CFG_MAX_STRLEN]; + int nargs; +} CfgButton; + +/* Autostart */ + +typedef struct { + char cmd[CFG_MAX_AUTOSTART_CMD]; +} CfgAutostart; + +/* the whole shebang */ + +typedef struct { + CfgAppearance appearance; + + CfgInput input; + + CfgRule rules[CFG_MAX_RULES]; + int nrules; + + CfgMonitorRule monitors[CFG_MAX_MONITORS]; + int nmonitors; + + CfgKeybind keybinds[CFG_MAX_KEYBINDS]; + int nkeybinds; + + CfgButton buttons[CFG_MAX_KEYBINDS]; + int nbuttons; + + bool sloppyfocus; + bool bypass_surface_visibility; + int log_level; /* WLR_* log level */ + + CfgAutostart autostart[CFG_MAX_AUTOSTART]; + int nautostart; +} Config; + +/* Public API */ + +int config_load(const char *path, Config *cfg); + +char *config_get_path(char *buf, size_t bufsz); + +void config_apply_appearance(const Config *cfg); + +void config_apply_input(const Config *cfg); + +void config_apply_keybinds(const Config *cfg); + +void config_autostart_run(const Config *cfg); + +typedef void (*config_reload_cb)(const Config *cfg, void *userdata); + +struct wl_event_source *config_watch_start( + struct wl_event_loop *loop, + const char *path, + config_reload_cb cb, + void *userdata +); + +void config_watch_stop(struct wl_event_source *src); + +#endif + diff -r 733566def44f -r 50bab568625d protocols/dwl-ipc-unstable-v2.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/protocols/dwl-ipc-unstable-v2.xml Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,184 @@ + + + + Copyright (C) 2022 Russell Newcomb + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This interface is exposed as a global in the Wayland registry and allows + clients to query and set dwl state. + + 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 is reset. + + + + + Indicates that the client will not use the manager object anymore. + Objects created through this interface remain valid. + + + + + + Get a dwl_ipc_output for the given wl_output. + + + + + + + + This event is sent after binding and announces the number of tags. + + + + + + + This event is sent after binding and announces a layout. + Layouts are announced in order. + + + + + + + + Observe and control a dwl output. + + + + + + + + + + + Indicates that the client will not use the dwl_ipc_output object + anymore. Objects created through this interface remain valid. + + + + + + set the active tags of this output. + + + + + + + + Set the tags of the focused client. + + + + + + + + Set the layout of this output. + + + + + + + Indicates that dwl visibility has been toggled. + + + + + + Indicates the output is active (selected monitor). + + + + + + + Indicates a tag state change. + + + + + + + + + + Indicates the layout changed. + + + + + + + Indicates the focused client title changed. + + + + + + + Indicates the focused client appid changed. + + + + + + + Indicates the layout symbol changed. + + + + + + + Sent after all state events have been sent for a given state change. + + + + + + Indicates the fullscreen state changed. + + + + + + + Indicates the floating state changed. + + + + + diff -r 733566def44f -r 50bab568625d protocols/ext-workspace-v1.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/protocols/ext-workspace-v1.xml Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,422 @@ + + + + Copyright © 2019 Christopher Billington + Copyright © 2020 Ilia Bozhinov + Copyright © 2022 Victoria Brekenfeld + + 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. + + + + + Workspaces, also called virtual desktops, are groups of surfaces. A + compositor with a concept of workspaces may only show some such groups of + surfaces (those of 'active' workspaces) at a time. 'Activating' a + workspace is a request for the compositor to display that workspace's + surfaces as normal, whereas the compositor may hide or otherwise + de-emphasise surfaces that are associated only with 'inactive' workspaces. + Workspaces are grouped by which sets of outputs they correspond to, and + may contain surfaces only from those outputs. In this way, it is possible + for each output to have its own set of workspaces, or for all outputs (or + any other arbitrary grouping) to share workspaces. Compositors may + optionally conceptually arrange each group of workspaces in an + N-dimensional grid. + + The purpose of this protocol is to enable the creation of taskbars and + docks by providing them with a list of workspaces and their properties, + and allowing them to activate and deactivate workspaces. + + After a client binds the ext_workspace_manager_v1, each workspace will be + sent via the workspace event. + + + + + This event is emitted whenever a new workspace group has been created. + + All initial details of the workspace group (outputs) will be + sent immediately after this event via the corresponding events in + ext_workspace_group_handle_v1 and ext_workspace_handle_v1. + + + + + + + This event is emitted whenever a new workspace has been created. + + All initial details of the workspace (name, coordinates, state) will + be sent immediately after this event via the corresponding events in + ext_workspace_handle_v1. + + Workspaces start off unassigned to any workspace group. + + + + + + + The client must send this request after it has finished sending other + requests. The compositor must process a series of requests preceding a + commit request atomically. + + This allows changes to the workspace properties to be seen as atomic, + even if they happen via multiple events, and even if they involve + multiple ext_workspace_handle_v1 objects, for example, deactivating one + workspace and activating another. + + + + + + This event is sent after all changes in all workspaces and workspace groups have been + sent. + + This allows changes to one or more ext_workspace_group_handle_v1 + properties and ext_workspace_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + In particular, an output moving from one workspace group to + another sends an output_enter event and an output_leave event to the two + ext_workspace_group_handle_v1 objects in question. The compositor sends + the done event only after updating the output information in both + workspace groups. + + + + + + This event indicates that the compositor is done sending events to the + ext_workspace_manager_v1. The server will destroy the object + immediately after sending this request. + + + + + + Indicates the client no longer wishes to receive events for new + workspace groups. However the compositor may emit further workspace + events, until the finished event is emitted. The compositor is expected + to send the finished event eventually once the stop request has been processed. + + The client must not send any requests after this one, doing so will raise a wl_display + invalid_object error. + + + + + + + + A ext_workspace_group_handle_v1 object represents a workspace group + that is assigned a set of outputs and contains a number of workspaces. + + The set of outputs assigned to the workspace group is conveyed to the client via + output_enter and output_leave events, and its workspaces are conveyed with + workspace events. + + For example, a compositor which has a set of workspaces for each output may + advertise a workspace group (and its workspaces) per output, whereas a compositor + where a workspace spans all outputs may advertise a single workspace group for all + outputs. + + + + + + + + + This event advertises the capabilities supported by the compositor. If + a capability isn't supported, clients should hide or disable the UI + elements that expose this functionality. For instance, if the + compositor doesn't advertise support for creating workspaces, a button + triggering the create_workspace request should not be displayed. + + The compositor will ignore requests it doesn't support. For instance, + a compositor which doesn't advertise support for creating workspaces will ignore + create_workspace requests. + + Compositors must send this event once after creation of an + ext_workspace_group_handle_v1. When the capabilities change, compositors + must send this event again. + + + + + + + This event is emitted whenever an output is assigned to the workspace + group or a new `wl_output` object is bound by the client, which was already + assigned to this workspace_group. + + + + + + + This event is emitted whenever an output is removed from the workspace + group. + + + + + + + This event is emitted whenever a workspace is assigned to this group. + A workspace may only ever be assigned to a single group at a single point + in time, but can be re-assigned during its lifetime. + + + + + + + This event is emitted whenever a workspace is removed from this group. + + + + + + + This event is send when the group associated with the ext_workspace_group_handle_v1 + has been removed. After sending this request the compositor will immediately consider + the object inert. Any requests will be ignored except the destroy request. + It is guaranteed there won't be any more events referencing this + ext_workspace_group_handle_v1. + + The compositor must remove all workspaces belonging to a workspace group + via a workspace_leave event before removing the workspace group. + + + + + + Request that the compositor create a new workspace with the given name + and assign it to this group. + + There is no guarantee that the compositor will create a new workspace, + or that the created workspace will have the provided name. + + + + + + + Destroys the ext_workspace_group_handle_v1 object. + + This request should be send either when the client does not want to + use the workspace group object any more or after the removed event to finalize + the destruction of the object. + + + + + + + A ext_workspace_handle_v1 object represents a workspace that handles a + group of surfaces. + + Each workspace has: + - a name, conveyed to the client with the name event + - potentially an id conveyed with the id event + - a list of states, conveyed to the client with the state event + - and optionally a set of coordinates, conveyed to the client with the + coordinates event + + The client may request that the compositor activate or deactivate the workspace. + + Each workspace can belong to only a single workspace group. + Depending on the compositor policy, there might be workspaces with + the same name in different workspace groups, but these workspaces are still + separate (e.g. one of them might be active while the other is not). + + + + + If this event is emitted, it will be send immediately after the + ext_workspace_handle_v1 is created or when an id is assigned to + a workspace (at most once during its lifetime). + + An id will never change during the lifetime of the `ext_workspace_handle_v1` + and is guaranteed to be unique during its lifetime. + + Ids are not human-readable and shouldn't be displayed, use `name` for that purpose. + + Compositors are expected to only send ids for workspaces likely stable across multiple + sessions and can be used by clients to store preferences for workspaces. Workspaces without + ids should be considered temporary and any data associated with them should be deleted once + the respective object is lost. + + + + + + + This event is emitted immediately after the ext_workspace_handle_v1 is + created and whenever the name of the workspace changes. + + A name is meant to be human-readable and can be displayed to a user. + Unlike the id it is neither stable nor unique. + + + + + + + This event is used to organize workspaces into an N-dimensional grid + within a workspace group, and if supported, is emitted immediately after + the ext_workspace_handle_v1 is created and whenever the coordinates of + the workspace change. Compositors may not send this event if they do not + conceptually arrange workspaces in this way. If compositors simply + number workspaces, without any geometric interpretation, they may send + 1D coordinates, which clients should not interpret as implying any + geometry. Sending an empty array means that the compositor no longer + orders the workspace geometrically. + + Coordinates have an arbitrary number of dimensions N with an uint32 + position along each dimension. By convention if N > 1, the first + dimension is X, the second Y, the third Z, and so on. The compositor may + chose to utilize these events for a more novel workspace layout + convention, however. No guarantee is made about the grid being filled or + bounded; there may be a workspace at coordinate 1 and another at + coordinate 1000 and none in between. Within a workspace group, however, + workspaces must have unique coordinates of equal dimensionality. + + + + + + + The different states that a workspace can have. + + + + + + + The workspace is not visible in its workspace group, and clients + attempting to visualize the compositor workspace state should not + display such workspaces. + + + + + + + This event is emitted immediately after the ext_workspace_handle_v1 is + created and each time the workspace state changes, either because of a + compositor action or because of a request in this protocol. + + Missing states convey the opposite meaning, e.g. an unset active bit + means the workspace is currently inactive. + + + + + + + + + + + + + + This event advertises the capabilities supported by the compositor. If + a capability isn't supported, clients should hide or disable the UI + elements that expose this functionality. For instance, if the + compositor doesn't advertise support for removing workspaces, a button + triggering the remove request should not be displayed. + + The compositor will ignore requests it doesn't support. For instance, + a compositor which doesn't advertise support for remove will ignore + remove requests. + + Compositors must send this event once after creation of an + ext_workspace_handle_v1 . When the capabilities change, compositors + must send this event again. + + + + + + + This event is send when the workspace associated with the ext_workspace_handle_v1 + has been removed. After sending this request, the compositor will immediately consider + the object inert. Any requests will be ignored except the destroy request. + + It is guaranteed there won't be any more events referencing this + ext_workspace_handle_v1. + + The compositor must only remove a workspaces not currently belonging to any + workspace_group. + + + + + + Destroys the ext_workspace_handle_v1 object. + + This request should be made either when the client does not want to + use the workspace object any more or after the remove event to finalize + the destruction of the object. + + + + + + Request that this workspace be activated. + + There is no guarantee the workspace will be actually activated, and + behaviour may be compositor-dependent. For example, activating a + workspace may or may not deactivate all other workspaces in the same + group. + + + + + + Request that this workspace be deactivated. + + There is no guarantee the workspace will be actually deactivated. + + + + + + Requests that this workspace is assigned to the given workspace group. + + There is no guarantee the workspace will be assigned. + + + + + + + Request that this workspace be removed. + + There is no guarantee the workspace will be actually removed. + + + + diff -r 733566def44f -r 50bab568625d protocols/wlr-layer-shell-unstable-v1.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/protocols/wlr-layer-shell-unstable-v1.xml Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,390 @@ + + + + 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. + + + + + diff -r 733566def44f -r 50bab568625d protocols/wlr-output-power-management-unstable-v1.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/protocols/wlr-output-power-management-unstable-v1.xml Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,128 @@ + + + + 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 a 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. + + + + diff -r 733566def44f -r 50bab568625d smsg/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/smsg/README.md Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,55 @@ +**smsg** - send ipc messages to swindle (forked from [this repository](https://codeberg.org/notchoc/dwlmsg)) + +``` +usage: smsg [-OTLPF] + smsg [-o ] -s [-t ] [-l ] [-c ] + smsg [-o ] (-g | -w) [-FOotlcvmf] +``` + +``` +options: +-g get +-s set +-w watch +-O get all outputs +-T get number of tags +-L get all available layouts +-P get compositor pid +-F get focused output +-o select output +-t get/set selected tags (set with [+-^.], overwrite with ! prefix) +-l get/set current layout +-c get title and appid of focused client +-v get visibility of statusbar +-m get fullscreen status +-f get floating status +``` + +``` +examples: + # act like dwl stdout + smsg -w + # watch focused client appid and title + smsg -w -c + # get currently focused output + smsg -F + # watch for focused output changes + smsg -w -F + # get all available outputs + smsg -O + # watch available outputs + smsg -w -O + # select tag 1, deselect tag 2, toggle tag 4 on output eDP-1 + smsg -o eDP-1 -s -t +-.^ + # toggle tag 3, overwriting current tagset (yes, zero-indexed) + smsg -t !2^ + # select tag 8 on current output + smsg -s -t 7 + # deselect tag 8 on current output + smsg -s -t 7- + # switch to first layout (order given by smsg -L) + smsg -l 0 + # switch to floating layout + smsg -l '><>' +``` + diff -r 733566def44f -r 50bab568625d smsg/arg.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/smsg/arg.h Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,58 @@ +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF() (brk_ = 1, estrtonum(argv[0], 0, INT_MAX)) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define LNGARG() &argv[0][0] + +#endif diff -r 733566def44f -r 50bab568625d smsg/protocols/dwl-ipc-unstable-v2.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/smsg/protocols/dwl-ipc-unstable-v2.xml Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,181 @@ + + + + + This protocol allows clients to update and get updates from dwl. + + 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 exposed as a global in wl_registry. + + Clients can use this interface to get a dwl_ipc_output. + After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events. + The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client. + + + + + Indicates that the client will not the dwl_ipc_manager object anymore. + Objects created through this instance are not affected. + + + + + + Get a dwl_ipc_outout for the specified wl_output. + + + + + + + + This event is sent after binding. + A roundtrip after binding guarantees the client recieved all tags. + + + + + + + This event is sent after binding. + A roundtrip after binding guarantees the client recieved all layouts. + + + + + + + + Observe and control a dwl output. + + Events are double-buffered: + Clients should cache events and redraw when a dwl_ipc_output.frame event is sent. + + Request are not double-buffered: + The compositor will update immediately upon request. + + + + + + + + + + + Indicates to that the client no longer needs this dwl_ipc_output. + + + + + + Indicates the client should hide or show themselves. + If the client is visible then hide, if hidden then show. + + + + + + Indicates if the output is active. Zero is invalid, nonzero is valid. + + + + + + + Indicates that a tag has been updated. + + + + + + + + + + Indicates a new layout is selected. + + + + + + + Indicates the title has changed. + + + + + + + Indicates the appid has changed. + + + + + + + Indicates the layout has changed. Since layout symbols are dynamic. + As opposed to the zdwl_ipc_manager.layout event, this should take precendence when displaying. + You can ignore the zdwl_ipc_output.layout event. + + + + + + + Indicates that a sequence of status updates have finished and the client should redraw. + + + + + + + + + + + + The tags are updated as follows: + new_tags = (current_tags AND and_tags) XOR xor_tags + + + + + + + + + + + + + + Indicates if the selected client on this output is fullscreen. + + + + + + + Indicates if the selected client on this output is floating. + + + + + diff -r 733566def44f -r 50bab568625d smsg/smsg.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/smsg/smsg.c Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,516 @@ +#define _GNU_SOURCE +#include "arg.h" +#include +#include +#include +#include +#include +#include +#include +#include "dwl-ipc-unstable-v2-protocol.h" + +#define die(fmt, ...) do { fprintf(stderr, fmt "\n", ##__VA_ARGS__); exit(EXIT_FAILURE); } while (0) + +char *argv0; + +static enum { + NONE = 0, + SET = 1<<0, + GET = 1<<1, + WATCH = 1<<2 | GET, +} mode = NONE; + +static int Oflag; +static int Tflag; +static int Lflag; +static int Pflag; +static int Fflag; +static int oflag; +static int tflag; +static int lflag; +static int cflag; +static int vflag; +static int mflag; +static int fflag; + +static uint32_t occ, seltags, sel, urg; + +static char *output_name; +static char *focused_output; +static uint32_t tagcount; +static char *tagset; +static char *layout_name; +static size_t layoutcount, layout_idx; +static char *client_tags; + +struct output { + char *output_name; + uint32_t name; +}; +static size_t outputs_buflen = 4; +static struct output *outputs; +static size_t outputcount; + +static struct wl_display *display; +static struct wl_registry *registry; +static struct zdwl_ipc_manager_v2 *dwl_ipc_manager; + +static void +dwl_ipc_tags(void *data, struct zdwl_ipc_manager_v2 *dwl_ipc_manager, uint32_t count) +{ + tagcount = count; + if (Tflag && mode & GET) printf("%d\n", tagcount); +} + +static void +dwl_ipc_layout(void *data, struct zdwl_ipc_manager_v2 *dwl_ipc_manager, const char *name) +{ + if (lflag && mode & SET && strcmp(layout_name, name) == 0) + layout_idx = layoutcount; + if (Lflag && mode & GET) printf("%s\n", name); + layoutcount++; +} + +static const struct zdwl_ipc_manager_v2_listener dwl_ipc_listener = { + .tags = dwl_ipc_tags, + .layout = dwl_ipc_layout +}; + +static void +dwl_ipc_output_toggle_visibility(void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output) +{ + if (!vflag) return; + char *oname = data; + if (oname) printf("%s ", oname); + printf("toggle\n"); +} + +static void +dwl_ipc_output_active(void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output, + uint32_t active) +{ + if (active && (!output_name || !*output_name)) + output_name = strdup(data); + if (active) { + free(focused_output); + focused_output = data ? strdup(data) : NULL; + } + if (!oflag) return; + char *oname = data; + if (oname) printf("%s ", oname); + printf("selmon %u\n", !!active); +} + +static void +dwl_ipc_output_tag(void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output, + uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) +{ + if (!tflag) return; + if (state != ZDWL_IPC_OUTPUT_V2_TAG_STATE_NONE) seltags |= 1< 0) occ |= 1< '9') + die("bad layout %s", layout_name); + layout_idx = *c-'0' + layout_idx*10; + } + } + if (layout_idx >= layoutcount) + die("bad layout %s", layout_name); + zdwl_ipc_output_v2_set_layout(dwl_ipc_output, layout_idx); + } + if (tflag) { + uint32_t mask = seltags; + char *t = tagset; + uint32_t i = 0; + int toggle = 1; + + if (*t == '!') { + toggle = 0; + t++; + } + + for (; *t && *t >= '0' && *t <= '9'; t++) + i = *t-'0' + i*10; + + if (toggle && !*t) mask = 1< tagcount) die("bad tagset %s", tagset); + + zdwl_ipc_output_v2_set_tags(dwl_ipc_output, mask, toggle); + } + if (cflag) { + uint32_t and = ~0, xor = 0; + char *t = client_tags; + uint32_t i = 0; + + for (; *t && *t >= '0' && *t <= '9'; t++) + i = *t-'0' + i*10; + + if (!*t) { + and = 0; + xor = 1< tagcount) die("bad client tagset %s", client_tags); + + zdwl_ipc_output_v2_set_client_tags(dwl_ipc_output, and, xor); + } + wl_display_flush(display); + exit(0); + } else { + if (Fflag && mode == WATCH && focused_output) + printf("focusmon %s\n", focused_output); + if (tflag) { + if (data) printf("%s ", (char *)data); + printf("tags %u %u %u %u\n", occ, seltags, sel, urg); + occ = seltags = sel = urg = 0; + } + } + fflush(stdout); +} + +static const struct zdwl_ipc_output_v2_listener dwl_ipc_output_listener = { + .toggle_visibility = dwl_ipc_output_toggle_visibility, + .active = dwl_ipc_output_active, + .tag = dwl_ipc_output_tag, + .layout = dwl_ipc_output_layout, + .title = dwl_ipc_output_title, + .appid = dwl_ipc_output_appid, + .layout_symbol = dwl_ipc_output_layout_symbol, + .fullscreen = dwl_ipc_output_fullscreen, + .floating = dwl_ipc_output_floating, + .frame = dwl_ipc_output_frame, +}; + +static void +wl_output_name(void *data, struct wl_output *output, const char *name) +{ + if (outputs) { + struct output *o = (struct output *)data; + o->output_name = strdup(name); + printf("+ "); + } + if (Oflag) printf("%s\n", name); + if (output_name && *output_name && strcmp(output_name, name) != 0) { + wl_output_release(output); + return; + } + if (dwl_ipc_manager) { + struct zdwl_ipc_output_v2 *dwl_ipc_output = + zdwl_ipc_manager_v2_get_output(dwl_ipc_manager, output); + zdwl_ipc_output_v2_add_listener(dwl_ipc_output, &dwl_ipc_output_listener, + output_name && *output_name ? NULL : strdup(name)); + } +} + +static void +wl_output_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t w, int32_t h, int32_t subpixel, const char *make, const char *model, int32_t transform) { +} + +static void +wl_output_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) +{ +} + +static void +wl_output_done(void *data, struct wl_output *wl_output) +{ +} + +static void +wl_output_scale(void *data, struct wl_output *wl_output, int32_t factor) +{ +} + +static void +wl_output_description(void *data, struct wl_output *output, const char *description) +{ +} + +static const struct wl_output_listener output_listener = { + .geometry = wl_output_geometry, + .mode = wl_output_mode, + .done = wl_output_done, + .scale = wl_output_scale, + .name = wl_output_name, + .description = wl_output_description, +}; + +static void +global_add(void *data, struct wl_registry *wl_registry, + uint32_t name, const char *interface, uint32_t version) +{ + if (strcmp(interface, wl_output_interface.name) == 0) { + struct wl_output *o = wl_registry_bind( + wl_registry, name, &wl_output_interface, + WL_OUTPUT_NAME_SINCE_VERSION); + wl_output_add_listener(o, &output_listener, outputs ? &outputs[outputcount] : NULL); + if (!outputs) return; + if (outputcount >= outputs_buflen) { + outputs_buflen *= 2; + outputs = reallocarray(outputs, outputs_buflen, sizeof(struct output)); + } + outputs[outputcount].name = name; + outputcount++; + } else if (strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) { + dwl_ipc_manager = wl_registry_bind(wl_registry, name, + &zdwl_ipc_manager_v2_interface, version < 2 ? version : 2); + zdwl_ipc_manager_v2_add_listener(dwl_ipc_manager, &dwl_ipc_listener, NULL); + } +} + +static void +global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) +{ + if (!outputs) return; + for (size_t i = 0; i < outputcount; i++) { + if (outputs[i].name == name) { + printf("- %s\n", outputs[i].output_name); + free(outputs[i].output_name); + outputs[i] = outputs[--outputcount]; + } + } +} + +static const struct wl_registry_listener registry_listener = { + .global = global_add, + .global_remove = global_remove, +}; + +static void +usage(int code) +{ + fprintf(code ? stderr : stdout, + "usage:" + "\t%1$s [-OTLPF]\n" + "\t%1$s [-o ] -s [-t ] [-l ] [-c ]\n" + "\t%1$s [-o ] (-g | -w) [-FOotlcvmf]\n", + argv0); + exit(code); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + case '?': case 'h': + usage(0); break; + case 's': + if (mode != NONE) usage(1); + mode = SET; + break; + case 'g': + if (mode != NONE) usage(1); + mode = GET; + break; + case 'w': + if (mode != NONE) usage(1); + mode = WATCH; + break; + case 'o': + if (mode & SET) + output_name = EARGF(usage(1)); + else if (mode & GET || !(output_name = ARGF())) + oflag = 1; + break; + case 't': + tflag = 1; + if (mode & GET) break; + mode = SET; + tagset = EARGF(usage(1)); + break; + case 'l': + lflag = 1; + if (mode & GET) break; + mode = SET; + layout_name = EARGF(usage(1)); + break; + case 'c': + cflag = 1; + if (mode & GET) break; + mode = SET; + client_tags = EARGF(usage(1)); + break; + case 'O': + Oflag = 1; + if (mode & SET) usage(1); + if (mode & WATCH) + outputs = malloc(outputs_buflen * sizeof(struct output)); + else + mode = GET; + break; + case 'T': + Tflag = 1; + if (mode && mode != GET) usage(1); + mode = GET; + break; + case 'L': + Lflag = 1; + if (mode && mode != GET) usage(1); + mode = GET; + break; + case 'P': + Pflag = 1; + if (mode && mode != GET) usage(1); + mode = GET; + break; + case 'F': + Fflag = 1; + if (mode && mode != GET) usage(1); + mode = GET; + break; + case 'v': + vflag = 1; + if (mode & SET) usage(1); + mode |= GET; + break; + case 'm': + mflag = 1; + if (mode & SET) usage(1); + mode |= GET; + break; + case 'f': + fflag = 1; + if (mode & SET) usage(1); + mode |= GET; + break; + default: + fprintf(stderr, "%s: bad option: -%c\n", argv0, ARGC()); + usage(1); + } ARGEND + if (argc > 0) { + fprintf(stderr, "%s: bad args:", argv0); + for (; *argv; argv++) + fprintf(stderr, " %s", argv[0]); + fprintf(stderr, "\n"); + usage(1); + } + if (mode == NONE) usage(1); + if (mode & GET && !(oflag || tflag || lflag || Oflag || Tflag || Lflag || Pflag || Fflag || cflag || vflag || mflag || fflag)) + oflag = tflag = lflag = cflag = vflag = mflag = fflag = Fflag = 1; + + display = wl_display_connect(NULL); + if (!display) die("bad display"); + + if (Pflag) { + int fd = wl_display_get_fd(display); + struct ucred ucred; + socklen_t len = sizeof(struct ucred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) + die("bad getsockopt"); + printf("%d\n", ucred.pid); + } + + registry = wl_display_get_registry(display); + if (!registry) die("bad registry"); + + wl_registry_add_listener(registry, ®istry_listener, NULL); + + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (!dwl_ipc_manager && (mode & SET || tflag || lflag || Tflag || Lflag || cflag || vflag || mflag || fflag)) + die("bad dwl-ipc protocol"); + + wl_display_roundtrip(display); + + if (Fflag && !(mode == WATCH) && focused_output) + printf("focusmon %s\n", focused_output); + + if (mode == WATCH) + while (wl_display_dispatch(display) != -1); + + return 0; +} diff -r 733566def44f -r 50bab568625d src/swindle.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/swindle.c Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,4071 @@ +/* + * See LICENSE file and licenses/ directory for copyright and license details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef XWAYLAND +#include +#include +#include +#endif + +#include "util.h" +#include "parser/parser.h" + +/* macros */ +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) +#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define END(A) ((A) + LENGTH(A)) +#define TAGMASK ((1u << TAGCOUNT) - 1) +#define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) +#define LISTEN_STATIC(E, H) do { struct wl_listener *_l = ecalloc(1, sizeof(*_l)); _l->notify = (H); wl_signal_add((E), _l); } while (0) + +/* enums */ +enum { CurNormal, CurPressed, CurMove, CurResize, CurTileDrag }; /* cursor */ +enum { XDGShell, LayerShell, X11 }; /* client types */ +enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + +typedef union { + int i; + uint32_t ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct { + /* Must keep this field first */ + unsigned int type; /* XDGShell or X11* */ + + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *inner_border[4]; /* inner top, bottom, left, right */ + struct wlr_scene_rect *outer_border[4]; /* outer top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; + struct wlr_box geom; /* layout-relative, includes border */ + struct wlr_box prev; /* layout-relative, includes border */ + struct wlr_box bounds; /* only width and height are used */ + union { + struct wlr_xdg_surface *xdg; + struct wlr_xwayland_surface *xwayland; + } surface; + struct wlr_xdg_toplevel_decoration_v1 *decoration; + struct wl_listener commit; + struct wl_listener map; + struct wl_listener maximize; + struct wl_listener unmap; + struct wl_listener destroy; + struct wl_listener set_title; + struct wl_listener fullscreen; + struct wl_listener set_decoration_mode; + struct wl_listener destroy_decoration; +#ifdef XWAYLAND + struct wl_listener activate; + struct wl_listener associate; + struct wl_listener dissociate; + struct wl_listener configure; + struct wl_listener set_hints; +#endif + unsigned int bw; /* total border width = ibw + obw */ + unsigned int ibw; /* inner border width */ + unsigned int obw; /* outer border width */ + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ +} Client; + +typedef struct { + uint32_t mod; + xkb_keysym_t keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + struct wlr_keyboard_group *wlr_group; + + int nsyms; + const xkb_keysym_t *keysyms; /* invalid if nsyms == 0 */ + uint32_t mods; /* invalid if nsyms == 0 */ + struct wl_event_source *key_repeat_source; + + struct wl_listener modifiers; + struct wl_listener key; + struct wl_listener destroy; +} KeyboardGroup; + +typedef struct { + /* Must keep this field first */ + unsigned int type; /* LayerShell */ + + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_tree *popups; + struct wlr_scene_layer_surface_v1 *scene_layer; + struct wl_list link; + int mapped; + struct wlr_layer_surface_v1 *layer_surface; + + struct wl_listener destroy; + struct wl_listener unmap; + struct wl_listener surface_commit; +} LayerSurface; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +#ifndef TAGCOUNT +#define TAGCOUNT 9 +#endif + +/* dwindle tree */ +typedef struct DwindleNode DwindleNode; +struct DwindleNode { + DwindleNode *children[2]; + DwindleNode *parent; + Client *client; + struct wlr_box box; + float split_ratio; + int split_top; + int is_node; +}; + +/* which physical edge of its old box a leaf's geometry moved toward/from */ +typedef enum { EDGE_NONE, EDGE_LEFT, EDGE_RIGHT, EDGE_TOP, EDGE_BOTTOM } DwindleEdge; + +/* Filled in by dwindle_insert()/dwindle_remove() when a non-NULL hint + * pointer is passed in*/ + +/*Left untouched (caller should zero it first) if + * the operation didn't actually split/collapse anything, i.e. first + * insert into an empty tree. */ + +typedef struct { + DwindleEdge edge; + struct wlr_box old_box; +} DwindleHint; + +struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; + struct wl_listener request_state; + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; + int gaps; + unsigned int seltags; + struct wlr_ext_workspace_group_handle_v1 *ext_group; + unsigned int sellt; + uint32_t tagset[2]; + float mfact; + int gamma_lut_changed; + int nmaster; + char ltsymbol[16]; + int asleep; + DwindleNode *dwindle_root[TAGCOUNT]; /* one tree per tag */ + Client *dwindle_focus[TAGCOUNT]; /* insertion anchor, one per tag */ +}; + +typedef struct { + const char *name; + float mfact; + int nmaster; + float scale; + const Layout *lt; + enum wl_output_transform rr; + int x, y; +} MonitorRule; + +typedef struct { + struct wlr_pointer_constraint_v1 *constraint; + struct wl_listener destroy; +} PointerConstraint; + +typedef struct { + const char *id; + const char *title; + uint32_t tags; + int isfloating; + int monitor; +} Rule; + +typedef struct { + struct wlr_scene_tree *scene; + + struct wlr_session_lock_v1 *lock; + struct wl_listener new_surface; + struct wl_listener unlock; + struct wl_listener destroy; +} SessionLock; + +/* function declarations */ +static void applybounds(Client *c, struct wlr_box *bbox); +static void applyrules(Client *c); +static void arrange(Monitor *m); +static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); +static void arrangelayers(Monitor *m); +static void axisnotify(struct wl_listener *listener, void *data); +static void buttonpress(struct wl_listener *listener, void *data); +static void chvt(const Arg *arg); +static void checkidleinhibitor(struct wlr_surface *exclude); +static void cleanup(void); +static void cleanupmon(struct wl_listener *listener, void *data); +static void cleanuplisteners(void); +static void closemon(Monitor *m); +static void dwindle_free_tree(DwindleNode *n); +static void dwindle_remove_client(Client *c); +static void dwindle_insert(DwindleNode **root, Client *new_c, Client *focused, DwindleHint *hint); +static int dwindle_insert_at_edge(DwindleNode **root, Client *new_c, Client *target, DwindleEdge edge); +static void dwindle_remove(DwindleNode **root, Client *c, DwindleHint *hint); +static DwindleEdge edge_at_point(struct wlr_box box, double x, double y); +static struct wlr_box preselect_indicator_box(struct wlr_box target, DwindleEdge edge); +static Client *tiled_client_at(double x, double y, Client *exclude); +static inline int current_tag_idx(Monitor *m); +static void commitlayersurfacenotify(struct wl_listener *listener, void *data); +static void commitnotify(struct wl_listener *listener, void *data); +static void commitpopup(struct wl_listener *listener, void *data); +static void createdecoration(struct wl_listener *listener, void *data); +static void createidleinhibitor(struct wl_listener *listener, void *data); +static void createkeyboard(struct wlr_keyboard *keyboard); +static KeyboardGroup *createkeyboardgroup(void); +static void createlayersurface(struct wl_listener *listener, void *data); +static void createlocksurface(struct wl_listener *listener, void *data); +static void createmon(struct wl_listener *listener, void *data); +static void createnotify(struct wl_listener *listener, void *data); +static void createpointer(struct wlr_pointer *pointer); +static void createpointerconstraint(struct wl_listener *listener, void *data); +static void createpopup(struct wl_listener *listener, void *data); +static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); +static void cursorframe(struct wl_listener *listener, void *data); +static void cursorwarptohint(void); +static void destroydecoration(struct wl_listener *listener, void *data); +static void destroydragicon(struct wl_listener *listener, void *data); +static void destroyidleinhibitor(struct wl_listener *listener, void *data); +static void destroylayersurfacenotify(struct wl_listener *listener, void *data); +static void destroylock(SessionLock *lock, int unlocked); +static void destroylocksurface(struct wl_listener *listener, void *data); +static void destroynotify(struct wl_listener *listener, void *data); +static void destroypointerconstraint(struct wl_listener *listener, void *data); +static void destroysessionlock(struct wl_listener *listener, void *data); +static void destroykeyboardgroup(struct wl_listener *listener, void *data); +static Monitor *dirtomon(enum wlr_direction dir); +static void focusclient(Client *c, int lift); +static void focusmon(const Arg *arg); +static void focusdir(const Arg *arg); +static void swapdir(const Arg *arg); +static void swaptiled(Client *a, Client *b); +static void dwindle_move_to_edge(Client *a, Client *b, DwindleEdge edge); +static Client *focustop(Monitor *m); +static void fullscreennotify(struct wl_listener *listener, void *data); +static void gpureset(struct wl_listener *listener, void *data); +static void handlesig(int signo); +static void inputdevice(struct wl_listener *listener, void *data); +static int keybinding(uint32_t mods, xkb_keysym_t sym); +static void keypress(struct wl_listener *listener, void *data); +static void keypressmod(struct wl_listener *listener, void *data); +static int keyrepeat(void *data); +static void killclient(const Arg *arg); +static void locksession(struct wl_listener *listener, void *data); +static void mapnotify(struct wl_listener *listener, void *data); +static void maximizenotify(struct wl_listener *listener, void *data); +static void motionabsolute(struct wl_listener *listener, void *data); +static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, + double sy, double sx_unaccel, double sy_unaccel); +static void motionrelative(struct wl_listener *listener, void *data); +static void moveresize(const Arg *arg); +static void outputmgrapply(struct wl_listener *listener, void *data); +static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); +static void outputmgrtest(struct wl_listener *listener, void *data); +static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +static void printstatus(void); +static void powermgrsetmode(struct wl_listener *listener, void *data); +static void quit(const Arg *arg); +static void rendermon(struct wl_listener *listener, void *data); +static void requestdecorationmode(struct wl_listener *listener, void *data); +static void requeststartdrag(struct wl_listener *listener, void *data); +static void requestmonstate(struct wl_listener *listener, void *data); +static void resize(Client *c, struct wlr_box geo, int interact); +static void run(char *startup_cmd); +static void setcursor(struct wl_listener *listener, void *data); +static void setcursorshape(struct wl_listener *listener, void *data); +static void setfloating(Client *c, int floating); +static void setfullscreen(Client *c, int fullscreen); +static void setlayout(const Arg *arg); +static void setmon(Client *c, Monitor *m, uint32_t newtags); +static void setpsel(struct wl_listener *listener, void *data); +static void setsel(struct wl_listener *listener, void *data); +static void setup(void); +static void spawn(const Arg *arg); +static void startdrag(struct wl_listener *listener, void *data); +static void tag(const Arg *arg); +static void tagmon(const Arg *arg); +static void dwindle(Monitor *m); +static void togglefloating(const Arg *arg); +static void togglefullscreen(const Arg *arg); +static void togglegaps(const Arg *arg); +static void toggletag(const Arg *arg); +static void toggleview(const Arg *arg); +static void unlocksession(struct wl_listener *listener, void *data); +static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); +static void unmapnotify(struct wl_listener *listener, void *data); +static void updatemons(struct wl_listener *listener, void *data); +static void updatetitle(struct wl_listener *listener, void *data); +static void urgent(struct wl_listener *listener, void *data); +static void view(const Arg *arg); +static void virtualkeyboard(struct wl_listener *listener, void *data); +static void virtualpointer(struct wl_listener *listener, void *data); +static Monitor *xytomon(double x, double y); +static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + +/* variables */ +static pid_t child_pid = -1; +static int locked; +static void *exclusive_focus; +static struct wl_display *dpy; +static struct wl_event_loop *event_loop; +static struct wlr_backend *backend; +static struct wlr_scene *scene; +static struct wlr_scene_tree *layers[NUM_LAYERS]; +static struct wlr_scene_tree *drag_icon; +/* Map from ZWLR_LAYER_SHELL_* constants to Lyr* enum */ +static const int layermap[] = { LyrBg, LyrBottom, LyrTop, LyrOverlay }; +static struct wlr_renderer *drw; +static struct wlr_allocator *alloc; +static struct wlr_compositor *compositor; +static struct wlr_session *session; + +static struct wlr_xdg_shell *xdg_shell; +static struct wlr_xdg_activation_v1 *activation; +static struct wlr_xdg_decoration_manager_v1 *xdg_decoration_mgr; +static struct wl_list clients; /* tiling order */ +static struct wl_list fstack; /* focus order */ +static struct wlr_idle_notifier_v1 *idle_notifier; +static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; +static struct wlr_layer_shell_v1 *layer_shell; +static struct wlr_output_manager_v1 *output_mgr; +static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; +static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; +static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; +static struct wlr_output_power_manager_v1 *power_mgr; + +static struct wlr_pointer_constraints_v1 *pointer_constraints; +static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; +static struct wlr_pointer_constraint_v1 *active_constraint; + +static struct wlr_cursor *cursor; +static struct wlr_xcursor_manager *cursor_mgr; + +static struct wlr_scene_rect *root_bg; +static struct wlr_session_lock_manager_v1 *session_lock_mgr; +static struct wlr_scene_rect *locked_bg; +static struct wlr_session_lock_v1 *cur_lock; + +static struct wlr_seat *seat; +static KeyboardGroup *kb_group; +static unsigned int cursor_mode; +static Client *grabc; +static int grabcx, grabcy; /* client-relative */ + +/* Tile-drag preselect recomputes every motion event while cursor_mode == + CurTileDrag edge == EDGE_NONE means "dead centre of target, do a plain + swap on release"." */ +static Client *preselect_target; +static DwindleEdge preselect_edge; +static struct wlr_scene_rect *preselect_indicator; + +/* Fixed size grabc is resized to the moment a tile-drag starts, so it + visually detaches and follows the cursor like a floating window rather + than staying pinned at full tile size the whole gesture. Deliberately + not "preserve original size" */ +#define TILEDRAG_DETACH_W 960 +#define TILEDRAG_DETACH_H 540 +static struct wlr_box tiledrag_orig_geom; + +static struct wlr_output_layout *output_layout; +static struct wlr_box sgeom; +static struct wl_list mons; +static Monitor *selmon; + +/* global event handlers */ +static struct wl_listener cursor_axis = {.notify = axisnotify}; +static struct wl_listener cursor_button = {.notify = buttonpress}; +static struct wl_listener cursor_frame = {.notify = cursorframe}; +static struct wl_listener cursor_motion = {.notify = motionrelative}; +static struct wl_listener cursor_motion_absolute = {.notify = motionabsolute}; +static struct wl_listener gpu_reset = {.notify = gpureset}; +static struct wl_listener layout_change = {.notify = updatemons}; +static struct wl_listener new_idle_inhibitor = {.notify = createidleinhibitor}; +static struct wl_listener new_input_device = {.notify = inputdevice}; +static struct wl_listener new_virtual_keyboard = {.notify = virtualkeyboard}; +static struct wl_listener new_virtual_pointer = {.notify = virtualpointer}; +static struct wl_listener new_pointer_constraint = {.notify = createpointerconstraint}; +static struct wl_listener new_output = {.notify = createmon}; +static struct wl_listener new_xdg_toplevel = {.notify = createnotify}; +static struct wl_listener new_xdg_popup = {.notify = createpopup}; +static struct wl_listener new_xdg_decoration = {.notify = createdecoration}; +static struct wl_listener new_layer_surface = {.notify = createlayersurface}; +static struct wl_listener output_mgr_apply = {.notify = outputmgrapply}; +static struct wl_listener output_mgr_test = {.notify = outputmgrtest}; +static struct wl_listener output_power_mgr_set_mode = {.notify = powermgrsetmode}; +static struct wl_listener request_activate = {.notify = urgent}; +static struct wl_listener request_cursor = {.notify = setcursor}; +static struct wl_listener request_set_psel = {.notify = setpsel}; +static struct wl_listener request_set_sel = {.notify = setsel}; +static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; +static struct wl_listener request_start_drag = {.notify = requeststartdrag}; +static struct wl_listener start_drag = {.notify = startdrag}; +static struct wl_listener new_session_lock = {.notify = locksession}; + +#ifdef XWAYLAND +static void activatex11(struct wl_listener *listener, void *data); +static void associatex11(struct wl_listener *listener, void *data); +static void configurex11(struct wl_listener *listener, void *data); +static void createnotifyx11(struct wl_listener *listener, void *data); +static void dissociatex11(struct wl_listener *listener, void *data); +static void sethints(struct wl_listener *listener, void *data); +static void xwaylandready(struct wl_listener *listener, void *data); +static struct wl_listener new_xwayland_surface = {.notify = createnotifyx11}; +static struct wl_listener xwayland_ready = {.notify = xwaylandready}; +static struct wlr_xwayland *xwayland; +#endif + +/* Runtime config */ + +static Config cfg; +static struct wl_event_source *cfg_watch_src; +static const char *custom_cfg_path; + +#include "config.h" + +/* attempt to encapsulate suck into one file */ +#include "client.h" +#include "dwl-ipc.h" +#include "ext-workspace.h" + +/* function implementations */ +void +applybounds(Client *c, struct wlr_box *bbox) +{ + /* set minimum possible */ + c->geom.width = MAX(1 + 2 * (int)c->bw, c->geom.width); + c->geom.height = MAX(1 + 2 * (int)c->bw, c->geom.height); + + if (c->geom.x >= bbox->x + bbox->width) + c->geom.x = bbox->x + bbox->width - c->geom.width; + if (c->geom.y >= bbox->y + bbox->height) + c->geom.y = bbox->y + bbox->height - c->geom.height; + if (c->geom.x + c->geom.width <= bbox->x) + c->geom.x = bbox->x; + if (c->geom.y + c->geom.height <= bbox->y) + c->geom.y = bbox->y; +} + +void +applyrules(Client *c) +{ + /* rule matching */ + const char *appid, *title; + uint32_t newtags = 0; + int i, ri; + Monitor *mon = selmon, *m; + + appid = client_get_appid(c); + title = client_get_title(c); + + for (ri = 0; ri < cfg.nrules; ri++) { + const CfgRule *r = &cfg.rules[ri]; + if ((!r->title[0] || strstr(title, r->title)) + && (!r->app_id[0] || strstr(appid, r->app_id))) { + c->isfloating = r->floating; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { + if (r->monitor == i++) + mon = m; + } + } + } + + c->isfloating |= client_is_float_type(c); + setmon(c, mon, newtags); +} + +void +arrange(Monitor *m) +{ + Client *c; + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { + wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); + client_set_suspended(c, !VISIBLEON(c, m)); + } + } + + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, + (c = focustop(m)) && c->isfullscreen); + + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + + /* We move all clients (except fullscreen and unmanaged) to LyrTile while + * in floating layout to avoid "real" floating clients be always on top */ + wl_list_for_each(c, &clients, link) { + if (c->mon != m || c->scene->node.parent == layers[LyrFS]) + continue; + + wlr_scene_node_reparent(&c->scene->node, + (!m->lt[m->sellt]->arrange && c->isfloating) + ? layers[LyrTile] + : (m->lt[m->sellt]->arrange && c->isfloating) + ? layers[LyrFloat] + : c->scene->node.parent); + } + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); +} + +void +arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive) +{ + LayerSurface *l; + struct wlr_box full_area = m->m; + + wl_list_for_each(l, list, link) { + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + + if (!layer_surface->initialized) + continue; + + if (exclusive != (layer_surface->current.exclusive_zone > 0)) + continue; + + wlr_scene_layer_surface_v1_configure(l->scene_layer, &full_area, usable_area); + wlr_scene_node_set_position(&l->popups->node, l->scene->node.x, l->scene->node.y); + } +} + +void +arrangelayers(Monitor *m) +{ + int i; + struct wlr_box usable_area = m->m; + LayerSurface *l; + uint32_t layers_above_shell[] = { + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, + }; + if (!m->wlr_output->enabled) + return; + + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); + + if (!wlr_box_equal(&usable_area, &m->w)) { + m->w = usable_area; + arrange(m); + } + + /* Arrange non-exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 0); + + /* Find topmost keyboard interactive layer, if such a layer exists */ + for (i = 0; i < (int)LENGTH(layers_above_shell); i++) { + wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { + if (locked || !l->layer_surface->current.keyboard_interactive || !l->mapped) + continue; + /* Deactivate the focused client. */ + focusclient(NULL, 0); + exclusive_focus = l; + client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat)); + return; + } + } +} + +void +axisnotify(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an axis event, + * for example when you move the scroll wheel. */ + struct wlr_pointer_axis_event *event = data; + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + wlr_seat_pointer_notify_axis(seat, + event->time_msec, event->orientation, event->delta, + event->delta_discrete, event->source, event->relative_direction); +} + +static void dispatch_action(const char *action, const char (*args)[CFG_MAX_STRLEN], int nargs); + +void +buttonpress(struct wl_listener *listener, void *data) +{ + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + uint32_t mods; + Client *c; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; + selmon = xytomon(cursor->x, cursor->y); + if (locked) + break; + + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (int i = 0; i < cfg.nbuttons; i++) { + const CfgButton *b = &cfg.buttons[i]; + if (CLEANMASK(mods) == CLEANMASK(b->mods) && + event->button == b->button) { + dispatch_action(b->action, b->args, b->nargs); + return; + } + } + break; + case WL_POINTER_BUTTON_STATE_RELEASED: + /* If you released any buttons, we exit interactive move/resize mode. */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + if (cursor_mode == CurTileDrag) { + if (preselect_target && preselect_edge != EDGE_NONE) { + dwindle_move_to_edge(grabc, preselect_target, preselect_edge); + } else { + Client *target = tiled_client_at(cursor->x, cursor->y, grabc); + if (target) + swaptiled(grabc, target); + else + resize(grabc, tiledrag_orig_geom, 0); + } + wlr_scene_node_reparent(&grabc->scene->node, layers[LyrTile]); + preselect_target = NULL; + preselect_edge = EDGE_NONE; + wlr_scene_node_set_enabled(&preselect_indicator->node, false); + cursor_mode = CurNormal; + grabc = NULL; + return; + } + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); + grabc = NULL; + return; + } + cursor_mode = CurNormal; + break; + } + /* If the event wasn't handled by the compositor, notify the client with + * pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button(seat, + event->time_msec, event->button, event->state); +} + +void +chvt(const Arg *arg) +{ + if (!session) { + fprintf(stderr, "swindle: chvt: no session (not running on DRM)\n"); + return; + } + wlr_session_change_vt(session, arg->ui); +} + +void +checkidleinhibitor(struct wlr_surface *exclude) +{ + int inhibited = 0, unused_lx, unused_ly; + struct wlr_idle_inhibitor_v1 *inhibitor; + wl_list_for_each(inhibitor, &idle_inhibit_mgr->inhibitors, link) { + struct wlr_surface *surface = wlr_surface_get_root_surface(inhibitor->surface); + struct wlr_scene_tree *tree = surface->data; + if (exclude != surface && (cfg.bypass_surface_visibility || (!tree + || wlr_scene_node_coords(&tree->node, &unused_lx, &unused_ly)))) { + inhibited = 1; + break; + } + } + + wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); +} + +void +cleanup(void) +{ + config_watch_stop(cfg_watch_src); + cfg_watch_src = NULL; + cleanuplisteners(); +#ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +#endif + wl_display_destroy_clients(dpy); + if (child_pid > 0) { + kill(-child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); + } + wlr_xcursor_manager_destroy(cursor_mgr); + + destroykeyboardgroup(&kb_group->destroy, NULL); + + /* If it's not destroyed manually, it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed on the wlroots side */ + wlr_backend_destroy(backend); + + wl_display_destroy(dpy); + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); +} + +void +cleanupmon(struct wl_listener *listener, void *data) +{ + Monitor *m = wl_container_of(listener, m, destroy); + LayerSurface *l, *tmp; + size_t i; + + /* m->layers[i] are intentionally not unlinked */ + for (i = 0; i < LENGTH(m->layers); i++) { + wl_list_for_each_safe(l, tmp, &m->layers[i], link) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); + wl_list_remove(&m->request_state.link); + if (m->lock_surface) + destroylocksurface(&m->destroy_lock_surface, NULL); + m->wlr_output->data = NULL; + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + + closemon(m); + ext_workspace_cleanupmon(m); + for (int i = 0; i < TAGCOUNT; i++) { + dwindle_free_tree(m->dwindle_root[i]); + m->dwindle_root[i] = NULL; + } + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); +} + +void +cleanuplisteners(void) +{ + wl_list_remove(&cursor_axis.link); + wl_list_remove(&cursor_button.link); + wl_list_remove(&cursor_frame.link); + wl_list_remove(&cursor_motion.link); + wl_list_remove(&cursor_motion_absolute.link); + wl_list_remove(&gpu_reset.link); + wl_list_remove(&new_idle_inhibitor.link); + wl_list_remove(&layout_change.link); + wl_list_remove(&new_input_device.link); + wl_list_remove(&new_virtual_keyboard.link); + wl_list_remove(&new_virtual_pointer.link); + wl_list_remove(&new_pointer_constraint.link); + wl_list_remove(&new_output.link); + wl_list_remove(&new_xdg_toplevel.link); + wl_list_remove(&new_xdg_decoration.link); + wl_list_remove(&new_xdg_popup.link); + wl_list_remove(&new_layer_surface.link); + wl_list_remove(&output_mgr_apply.link); + wl_list_remove(&output_mgr_test.link); + wl_list_remove(&output_power_mgr_set_mode.link); + wl_list_remove(&request_activate.link); + wl_list_remove(&request_cursor.link); + wl_list_remove(&request_set_psel.link); + wl_list_remove(&request_set_sel.link); + wl_list_remove(&request_set_cursor_shape.link); + wl_list_remove(&request_start_drag.link); + wl_list_remove(&start_drag.link); + wl_list_remove(&new_session_lock.link); +#ifdef XWAYLAND + wl_list_remove(&new_xwayland_surface.link); + wl_list_remove(&xwayland_ready.link); +#endif +} + +void +closemon(Monitor *m) +{ + /* update selmon if needed and + * move closed monitor's clients to the focused one */ + Client *c; + int i = 0, nmons = wl_list_length(&mons); + if (!nmons) { + selmon = NULL; + } else if (m == selmon) { + do /* don't switch to disabled mons */ + selmon = wl_container_of(mons.next, selmon, link); + while (!selmon->wlr_output->enabled && i++ < nmons); + + if (!selmon->wlr_output->enabled) + selmon = NULL; + } + + wl_list_for_each(c, &clients, link) { + if (c->isfloating && c->geom.x > m->m.width) + resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, + .width = c->geom.width, .height = c->geom.height}, 0); + if (c->mon == m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); + printstatus(); +} + +void +commitlayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, surface_commit); + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->current.layer]]; + struct wlr_layer_surface_v1_state old_state; + + if (l->layer_surface->initial_commit) { + client_set_scale(layer_surface->surface, l->mon->wlr_output->scale); + + /* Temporarily set the layer's current state to pending + * so that we can easily arrange it */ + old_state = l->layer_surface->current; + l->layer_surface->current = l->layer_surface->pending; + arrangelayers(l->mon); + l->layer_surface->current = old_state; + return; + } + + if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped) + return; + l->mapped = layer_surface->surface->mapped; + + if (scene_layer != l->scene->node.parent) { + wlr_scene_node_reparent(&l->scene->node, scene_layer); + wl_list_remove(&l->link); + wl_list_insert(&l->mon->layers[layer_surface->current.layer], &l->link); + wlr_scene_node_reparent(&l->popups->node, (layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer)); + } + + arrangelayers(l->mon); +} + +void +commitnotify(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, commit); + + if (c->surface.xdg->initial_commit) { + /* + * Get the monitor this client will be rendered on + * Note that if the user set a rule in which the client is placed on + * a different monitor based on its title, this will likely select + * a wrong monitor. + */ + applyrules(c); + if (c->mon) { + client_set_scale(client_surface(c), c->mon->wlr_output->scale); + } + setmon(c, NULL, 0); /* Make sure to reapply rules in mapnotify() */ + + wlr_xdg_toplevel_set_wm_capabilities(c->surface.xdg->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); + if (c->decoration) + requestdecorationmode(&c->set_decoration_mode, c->decoration); + wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, 0, 0); + return; + } + + resize(c, c->geom, (c->isfloating && !c->isfullscreen)); + + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) + c->resize = 0; +} + +void +commitpopup(struct wl_listener *listener, void *data) +{ + struct wlr_surface *surface = data; + struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); + LayerSurface *l = NULL; + Client *c = NULL; + struct wlr_box box; + int type = -1; + + if (!popup->base->initial_commit) + return; + + type = toplevel_from_wlr_surface(popup->base->surface, &c, &l); + if (!popup->parent || type < 0) + return; + popup->base->surface->data = wlr_scene_xdg_surface_create( + popup->parent->data, popup->base); + if ((l && !l->mon) || (c && !c->mon)) { + wlr_xdg_popup_destroy(popup); + return; + } + box = type == LayerShell ? l->mon->m : c->mon->w; + box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x); + box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y); + wlr_xdg_popup_unconstrain_from_box(popup, &box); + wl_list_remove(&listener->link); + free(listener); +} + +void +createdecoration(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_toplevel_decoration_v1 *deco = data; + Client *c = deco->toplevel->base->data; + c->decoration = deco; + + LISTEN(&deco->events.request_mode, &c->set_decoration_mode, requestdecorationmode); + LISTEN(&deco->events.destroy, &c->destroy_decoration, destroydecoration); + + requestdecorationmode(&c->set_decoration_mode, deco); +} + +void +createidleinhibitor(struct wl_listener *listener, void *data) +{ + struct wlr_idle_inhibitor_v1 *idle_inhibitor = data; + LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor); + + checkidleinhibitor(NULL); +} + +void +createkeyboard(struct wlr_keyboard *keyboard) +{ + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); +} + +KeyboardGroup * +createkeyboardgroup(void) +{ + KeyboardGroup *group = ecalloc(1, sizeof(*group)); + struct xkb_context *context; + struct xkb_keymap *keymap; + + group->wlr_group = wlr_keyboard_group_create(); + group->wlr_group->data = group; + + /* Prepare an XKB keymap and assign it to the keyboard group. */ + context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!(keymap = xkb_keymap_new_from_names(context, &(struct xkb_rule_names){.options = NULL}, + XKB_KEYMAP_COMPILE_NO_FLAGS))) + die("failed to compile keymap"); + + wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, cfg.input.repeat_rate, cfg.input.repeat_delay); + + /* Set up listeners for keyboard events */ + LISTEN(&group->wlr_group->keyboard.events.key, &group->key, keypress); + LISTEN(&group->wlr_group->keyboard.events.modifiers, &group->modifiers, keypressmod); + + group->key_repeat_source = wl_event_loop_add_timer(event_loop, keyrepeat, group); + + /* A seat can only have one keyboard, but this is a limitation of the + * Wayland protocol - not wlroots. We assign all connected keyboards to the + * same wlr_keyboard_group, which provides a single wlr_keyboard interface for + * all of them. Set this combined wlr_keyboard as the seat keyboard. + */ + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + return group; +} + +void +createlayersurface(struct wl_listener *listener, void *data) +{ + struct wlr_layer_surface_v1 *layer_surface = data; + LayerSurface *l; + struct wlr_surface *surface = layer_surface->surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->pending.layer]]; + + if (!layer_surface->output + && !(layer_surface->output = selmon ? selmon->wlr_output : NULL)) { + wlr_layer_surface_v1_destroy(layer_surface); + return; + } + + l = layer_surface->data = ecalloc(1, sizeof(*l)); + l->type = LayerShell; + LISTEN(&surface->events.commit, &l->surface_commit, commitlayersurfacenotify); + LISTEN(&surface->events.unmap, &l->unmap, unmaplayersurfacenotify); + LISTEN(&layer_surface->events.destroy, &l->destroy, destroylayersurfacenotify); + + l->layer_surface = layer_surface; + l->mon = layer_surface->output->data; + l->scene_layer = wlr_scene_layer_surface_v1_create(scene_layer, layer_surface); + l->scene = l->scene_layer->tree; + l->popups = surface->data = wlr_scene_tree_create(layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer); + l->scene->node.data = l->popups->node.data = l; + + wl_list_insert(&l->mon->layers[layer_surface->pending.layer],&l->link); + wlr_surface_send_enter(surface, layer_surface->output); +} + +void +createlocksurface(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, new_surface); + struct wlr_session_lock_surface_v1 *lock_surface = data; + Monitor *m = lock_surface->output->data; + struct wlr_scene_tree *scene_tree = lock_surface->surface->data + = wlr_scene_subsurface_tree_create(lock->scene, lock_surface->surface); + m->lock_surface = lock_surface; + + wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); + wlr_session_lock_surface_v1_configure(lock_surface, m->m.width, m->m.height); + + LISTEN(&lock_surface->events.destroy, &m->destroy_lock_surface, destroylocksurface); + + if (m == selmon) + client_notify_enter(lock_surface->surface, wlr_seat_get_keyboard(seat)); +} + +void +createmon(struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct wlr_output *wlr_output = data; + size_t i; + struct wlr_output_state state; + Monitor *m; + + if (!wlr_output_init_render(wlr_output, alloc, drw)) + return; + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->tagset[0] = m->tagset[1] = 1; + m->gaps = (cfg.appearance.gaps > 0) ? 1 : 0; + { + int ri; + for (ri = 0; ri < cfg.nmonitors; ri++) { + const CfgMonitorRule *r = &cfg.monitors[ri]; + if (!r->name[0] || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; + m->m.y = r->y; + m->mfact = r->mfact; + m->nmaster = r->nmaster; + /* Map layout name to layouts[] entry (it's not + * very useful I know) */ + m->lt[0] = &layouts[0]; /* default: floating */ + for (int li = 0; li < (int)LENGTH(layouts); li++) { + if (layouts[li].symbol && !strcmp(r->layout, layouts[li].symbol)) { + m->lt[0] = &layouts[li]; + break; + } + /* also match by arrange function name string */ + if (!strcmp(r->layout, "dwindle") && layouts[li].arrange == dwindle) { + m->lt[0] = &layouts[li]; + break; + } + } + m->lt[1] = &layouts[LENGTH(layouts) > 1 && m->lt[0] != &layouts[1]]; + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->transform); + break; + } + } + } + + /* The mode is a tuple of (width, height, refresh rate), and each + * monitor supports only a specific set of modes. We just pick the + * monitor's preferred mode; a more sophisticated compositor would let + * the user configure it. */ + wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); + + /* Set up event listeners */ + LISTEN(&wlr_output->events.frame, &m->frame, rendermon); + LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); + LISTEN(&wlr_output->events.request_state, &m->request_state, requestmonstate); + + wlr_output_state_set_enabled(&state, 1); + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + + wl_list_insert(&mons, &m->link); + ext_workspace_createmon(m); + printstatus(); + + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make + * sure that other screen content not part of the same surface tree (made + * up of subsurfaces, popups or similarly coupled surfaces) are not + * visible below the fullscreened surface. + * + */ + + /* updatemons() will resize and set correct position */ + m->fullscreen_bg = wlr_scene_rect_create(layers[LyrFS], 0, 0, cfg.appearance.fullscreen_bg); + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, 0); + + /* Adds this to the output layout in the order it was configured. + * + * The output layout utility automatically adds a wl_output global to the + * display, which Wayland clients can see to find out information about the + * output (such as DPI, scale factor, manufacturer, etc). + */ + m->scene_output = wlr_scene_output_create(scene, wlr_output); + if (m->m.x == -1 && m->m.y == -1) + wlr_output_layout_add_auto(output_layout, wlr_output); + else + wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); +} + +void +createnotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client creates a new toplevel (application window). */ + struct wlr_xdg_toplevel *toplevel = data; + Client *c = NULL; + + /* Allocate a Client for this surface */ + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->ibw = cfg.appearance.inner_border_px; + c->obw = cfg.appearance.outer_border_px; + c->bw = c->ibw + c->obw; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); + LISTEN(&toplevel->base->surface->events.unmap, &c->unmap, unmapnotify); + LISTEN(&toplevel->events.destroy, &c->destroy, destroynotify); + LISTEN(&toplevel->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&toplevel->events.request_maximize, &c->maximize, maximizenotify); + LISTEN(&toplevel->events.set_title, &c->set_title, updatetitle); +} + +void +createpointer(struct wlr_pointer *pointer) +{ + struct libinput_device *device; + if (wlr_input_device_is_libinput(&pointer->base) + && (device = wlr_libinput_get_device_handle(&pointer->base))) { + + if (libinput_device_config_tap_get_finger_count(device)) { + libinput_device_config_tap_set_enabled(device, cfg.input.tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, cfg.input.tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, cfg.input.drag_lock); + libinput_device_config_tap_set_button_map(device, cfg.input.tap_button_map); + } + + if (libinput_device_config_scroll_has_natural_scroll(device)) + libinput_device_config_scroll_set_natural_scroll_enabled(device, cfg.input.natural_scrolling); + + if (libinput_device_config_dwt_is_available(device)) + libinput_device_config_dwt_set_enabled(device, cfg.input.disable_while_typing); + + if (libinput_device_config_left_handed_is_available(device)) + libinput_device_config_left_handed_set(device, cfg.input.left_handed); + + if (libinput_device_config_middle_emulation_is_available(device)) + libinput_device_config_middle_emulation_set_enabled(device, cfg.input.middle_button_emulation); + + if (libinput_device_config_scroll_get_methods(device) != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) + libinput_device_config_scroll_set_method(device, cfg.input.scroll_method); + + if (libinput_device_config_click_get_methods(device) != LIBINPUT_CONFIG_CLICK_METHOD_NONE) + libinput_device_config_click_set_method(device, cfg.input.click_method); + + if (libinput_device_config_send_events_get_modes(device)) + libinput_device_config_send_events_set_mode(device, cfg.input.send_events_mode); + + if (libinput_device_config_accel_is_available(device)) { + libinput_device_config_accel_set_profile(device, cfg.input.accel_profile); + libinput_device_config_accel_set_speed(device, cfg.input.accel_speed); + } + } + + wlr_cursor_attach_input_device(cursor, &pointer->base); +} + +void +createpointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = ecalloc(1, sizeof(*pointer_constraint)); + pointer_constraint->constraint = data; + LISTEN(&pointer_constraint->constraint->events.destroy, + &pointer_constraint->destroy, destroypointerconstraint); +} + +void +createpopup(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client (either xdg-shell or layer-shell) + * creates a new popup. */ + struct wlr_xdg_popup *popup = data; + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); +} + +void +cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) +{ + if (active_constraint == constraint) + return; + + if (active_constraint) + wlr_pointer_constraint_v1_send_deactivated(active_constraint); + + active_constraint = constraint; + wlr_pointer_constraint_v1_send_activated(constraint); +} + +void +cursorframe(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits a frame + * event. Frame events are sent after regular pointer events to group + * multiple events together. For instance, two axis events may happen at the + * same time, in which case a frame event won't be sent in between. */ + /* Notify the client with pointer focus of the frame event. */ + wlr_seat_pointer_notify_frame(seat); +} + +void +cursorwarptohint(void) +{ + Client *c = NULL; + double sx = active_constraint->current.cursor_hint.x; + double sy = active_constraint->current.cursor_hint.y; + + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->current.cursor_hint.enabled) { + wlr_cursor_warp(cursor, NULL, sx + c->geom.x + c->bw, sy + c->geom.y + c->bw); + wlr_seat_pointer_warp(active_constraint->seat, sx, sy); + } +} + +void +destroydecoration(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, destroy_decoration); + + wl_list_remove(&c->destroy_decoration.link); + wl_list_remove(&c->set_decoration_mode.link); +} + +void +destroydragicon(struct wl_listener *listener, void *data) +{ + /* Focus enter isn't sent during drag, so refocus the focused node. */ + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); + wl_list_remove(&listener->link); + free(listener); +} + +void +destroyidleinhibitor(struct wl_listener *listener, void *data) +{ + /* `data` is the wlr_surface of the idle inhibitor being destroyed, + * at this point the idle inhibitor is still in the list of the manager */ + checkidleinhibitor(wlr_surface_get_root_surface(data)); + wl_list_remove(&listener->link); + free(listener); +} + +void +destroylayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, destroy); + + wl_list_remove(&l->link); + wl_list_remove(&l->destroy.link); + wl_list_remove(&l->unmap.link); + wl_list_remove(&l->surface_commit.link); + wlr_scene_node_destroy(&l->scene->node); + wlr_scene_node_destroy(&l->popups->node); + free(l); +} + +void +destroylock(SessionLock *lock, int unlock) +{ + wlr_seat_keyboard_notify_clear_focus(seat); + if ((locked = !unlock)) + goto destroy; + + wlr_scene_node_set_enabled(&locked_bg->node, 0); + + focusclient(focustop(selmon), 0); + motionnotify(0, NULL, 0, 0, 0, 0); + +destroy: + wl_list_remove(&lock->new_surface.link); + wl_list_remove(&lock->unlock.link); + wl_list_remove(&lock->destroy.link); + + wlr_scene_node_destroy(&lock->scene->node); + cur_lock = NULL; + free(lock); +} + +void +destroylocksurface(struct wl_listener *listener, void *data) +{ + Monitor *m = wl_container_of(listener, m, destroy_lock_surface); + struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; + + m->lock_surface = NULL; + wl_list_remove(&m->destroy_lock_surface.link); + + if (lock_surface->surface != seat->keyboard_state.focused_surface) + return; + + if (locked && cur_lock && !wl_list_empty(&cur_lock->surfaces)) { + surface = wl_container_of(cur_lock->surfaces.next, surface, link); + client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); + } else if (!locked) { + focusclient(focustop(selmon), 1); + } else { + wlr_seat_keyboard_clear_focus(seat); + } +} + +void +destroynotify(struct wl_listener *listener, void *data) +{ + /* Called when the xdg_toplevel is destroyed. */ + Client *c = wl_container_of(listener, c, destroy); + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); +#ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); + wl_list_remove(&c->associate.link); + wl_list_remove(&c->configure.link); + wl_list_remove(&c->dissociate.link); + wl_list_remove(&c->set_hints.link); + } else +#endif + { + wl_list_remove(&c->commit.link); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); + wl_list_remove(&c->maximize.link); + } + free(c); +} + +void +destroypointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = wl_container_of(listener, pointer_constraint, destroy); + + if (active_constraint == pointer_constraint->constraint) { + cursorwarptohint(); + active_constraint = NULL; + } + + wl_list_remove(&pointer_constraint->destroy.link); + free(pointer_constraint); +} + +void +destroysessionlock(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, destroy); + destroylock(lock, 0); +} + +void +destroykeyboardgroup(struct wl_listener *listener, void *data) +{ + KeyboardGroup *group = wl_container_of(listener, group, destroy); + wl_event_source_remove(group->key_repeat_source); + wl_list_remove(&group->key.link); + wl_list_remove(&group->modifiers.link); + wl_list_remove(&group->destroy.link); + wlr_keyboard_group_destroy(group->wlr_group); + free(group); +} + +Monitor * +dirtomon(enum wlr_direction dir) +{ + struct wlr_output *next; + if (!wlr_output_layout_get(output_layout, selmon->wlr_output)) + return selmon; + if ((next = wlr_output_layout_adjacent_output(output_layout, + dir, selmon->wlr_output, selmon->m.x, selmon->m.y))) + return next->data; + if ((next = wlr_output_layout_farthest_output(output_layout, + dir ^ (WLR_DIRECTION_LEFT|WLR_DIRECTION_RIGHT), + selmon->wlr_output, selmon->m.x, selmon->m.y))) + return next->data; + return selmon; +} + +void +focusclient(Client *c, int lift) +{ + struct wlr_surface *old = seat->keyboard_state.focused_surface; + int unused_lx, unused_ly, old_client_type; + Client *old_c = NULL; + LayerSurface *old_l = NULL; + + if (locked) + return; + + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); + + if (c && client_surface(c) == old) + return; + + if ((old_client_type = toplevel_from_wlr_surface(old, &old_c, &old_l)) == XDGShell) { + struct wlr_xdg_popup *popup, *tmp; + wl_list_for_each_safe(popup, tmp, &old_c->surface.xdg->popups, link) + wlr_xdg_popup_destroy(popup); + } + + /* Put the new client atop the focus stack and select its monitor */ + if (c && !client_is_unmanaged(c)) { + wl_list_remove(&c->flink); + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + c->isurgent = 0; + + /* Track focused client for dwindle insertion anchor */ + if (c->mon && !c->isfloating && !c->isfullscreen) + c->mon->dwindle_focus[current_tag_idx(c->mon)] = c; + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) + client_set_border_color(c, cfg.appearance.inner_focus_color, cfg.appearance.outer_focus_color); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, + * but only update its position in fstack to render its border with the focus colors + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) + && old_l->layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { + return; + } else if (old_c && old_c == exclusive_focus && client_wants_focus(old_c)) { + return; + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, cfg.appearance.inner_border_color, cfg.appearance.outer_border_color); + + client_activate_surface(old, 0); + } + } + printstatus(); + + if (!c) { + /* With no client, all we have left is to clear focus */ + wlr_seat_keyboard_notify_clear_focus(seat); + return; + } + + /* Change cursor surface */ + motionnotify(0, NULL, 0, 0, 0, 0); + + /* Have a client, so focus its top-level wlr_surface */ + client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); + + /* Activate the new client */ + client_activate_surface(client_surface(c), 1); +} + +void +focusmon(const Arg *arg) +{ + int i = 0, nmons = wl_list_length(&mons); + if (nmons) { + do /* don't switch to disabled mons */ + selmon = dirtomon(arg->i); + while (!selmon->wlr_output->enabled && i++ < nmons); + } + focusclient(focustop(selmon), 1); +} + +/* Return the centre point of a client's geometry. */ +static void +client_center(Client *c, int *cx, int *cy) +{ + *cx = c->geom.x + c->geom.width / 2; + *cy = c->geom.y + c->geom.height / 2; +} + +/* focusdir is basically spatial focus in a cardinal direction. + * arg->i: WLR_DIRECTION_LEFT, RIGHT, UP, DOWN. + * It finds the nearest tiled visible client whose centre lies in the + * requested half-plane relative to the focused client's centre */ +void +focusdir(const Arg *arg) +{ + Client *c, *best = NULL; + Client *sel = focustop(selmon); + int sx, sy, cx, cy; + double bestdist = 1e18, dist; + + if (!sel || (sel->isfullscreen && !client_has_children(sel))) + return; + + client_center(sel, &sx, &sy); + + wl_list_for_each(c, &clients, link) { + if (c == sel || !VISIBLEON(c, selmon) || c->isfloating) + continue; + + client_center(c, &cx, &cy); + + /* the candidate must be in the requested direction. */ + switch (arg->i) { + case WLR_DIRECTION_LEFT: if (cx >= sx) continue; break; + case WLR_DIRECTION_RIGHT: if (cx <= sx) continue; break; + case WLR_DIRECTION_UP: if (cy >= sy) continue; break; + case WLR_DIRECTION_DOWN: if (cy <= sy) continue; break; + default: return; + } + + dist = (double)(cx - sx) * (cx - sx) + (double)(cy - sy) * (cy - sy); + if (dist < bestdist) { + bestdist = dist; + best = c; + } + } + + if (best) + focusclient(best, 1); +} + +/* We probably should change the name of this: it sounds like it + * will focus the topmost client of this mon, when actually will + * only return that client */ +Client * +focustop(Monitor *m) +{ + Client *c; + wl_list_for_each(c, &fstack, flink) { + if (VISIBLEON(c, m)) + return c; + } + return NULL; +} + +void +fullscreennotify(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, fullscreen); + setfullscreen(c, client_wants_fullscreen(c)); +} + +void +gpureset(struct wl_listener *listener, void *data) +{ + struct wlr_renderer *old_drw = drw; + struct wlr_allocator *old_alloc = alloc; + struct Monitor *m; + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't recreate renderer"); + + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't recreate allocator"); + + wl_list_remove(&gpu_reset.link); + wl_signal_add(&drw->events.lost, &gpu_reset); + + wlr_compositor_set_renderer(compositor, drw); + + wl_list_for_each(m, &mons, link) { + wlr_output_init_render(m->wlr_output, alloc, drw); + } + + wlr_allocator_destroy(old_alloc); + wlr_renderer_destroy(old_drw); +} + +void +handlesig(int signo) +{ + if (signo == SIGCHLD) + while (waitpid(-1, NULL, WNOHANG) > 0); + else if (signo == SIGINT || signo == SIGTERM) + quit(NULL); +} + + +void +inputdevice(struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new input device becomes + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + createkeyboard(wlr_keyboard_from_input_device(device)); + break; + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; + default: + /* TODO handle other input device types */ + break; + } + + /* We need to let the wlr_seat know what our capabilities are, which is + * communiciated to the client. In dwl we always have a cursor, even if + * there are no pointer devices, so we always include that capability. */ + /* TODO do we actually require a cursor? */ + caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&kb_group->wlr_group->devices)) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + wlr_seat_set_capabilities(seat, caps); +} + +/* dispatch_action maps a CfgKeybind/CfgButton action string to the + * corresponding compositor function and calls it. args[0] is used where + * needed (spawn argv, tag mask, direction, etc). */ +static void +dispatch_action(const char *action, const char (*args)[CFG_MAX_STRLEN], int nargs) +{ + Arg arg = {0}; + const char *a0 = nargs > 0 ? args[0] : NULL; + + if (!strcmp(action, "spawn")) { + /* Build a NULL-terminated argv on the stack. + * CFG_MAX_ARGS is 16, which is plenty. */ + const char *argv[CFG_MAX_ARGS + 1]; + int i; + for (i = 0; i < nargs && i < CFG_MAX_ARGS; i++) + argv[i] = args[i]; + argv[i] = NULL; + arg.v = argv; + spawn(&arg); + return; + } + if (!strcmp(action, "killclient")) { killclient(NULL); return; } + if (!strcmp(action, "togglefloating")) { togglefloating(NULL); return; } + if (!strcmp(action, "togglefullscreen")){ togglefullscreen(NULL); return; } + if (!strcmp(action, "togglegaps")) { togglegaps(NULL); return; } + if (!strcmp(action, "quit")) { quit(NULL); return; } + + if (!strcmp(action, "view")) { + if (a0 && strcmp(a0, "all") == 0) arg.ui = ~0u; + else if (a0) arg.ui = (uint32_t)atoi(a0); + view(&arg); + return; + } + if (!strcmp(action, "toggleview")) { + if (a0) arg.ui = (uint32_t)atoi(a0); + toggleview(&arg); + return; + } + if (!strcmp(action, "tag")) { + if (a0) arg.ui = (uint32_t)atoi(a0); + tag(&arg); + return; + } + if (!strcmp(action, "toggletag")) { + if (a0) arg.ui = (uint32_t)atoi(a0); + toggletag(&arg); + return; + } + + if (!strcmp(action, "focusdir")) { + if (a0 && !strcmp(a0, "left")) arg.i = WLR_DIRECTION_LEFT; + else if (a0 && !strcmp(a0, "right")) arg.i = WLR_DIRECTION_RIGHT; + else if (a0 && !strcmp(a0, "up")) arg.i = WLR_DIRECTION_UP; + else if (a0 && !strcmp(a0, "down")) arg.i = WLR_DIRECTION_DOWN; + focusdir(&arg); + return; + } + if (!strcmp(action, "swapdir")) { + if (a0 && !strcmp(a0, "left")) arg.i = WLR_DIRECTION_LEFT; + else if (a0 && !strcmp(a0, "right")) arg.i = WLR_DIRECTION_RIGHT; + else if (a0 && !strcmp(a0, "up")) arg.i = WLR_DIRECTION_UP; + else if (a0 && !strcmp(a0, "down")) arg.i = WLR_DIRECTION_DOWN; + swapdir(&arg); + return; + } + if (!strcmp(action, "focusmon")) { + if (a0 && !strcmp(a0, "left")) arg.i = WLR_DIRECTION_LEFT; + else if (a0 && !strcmp(a0, "right")) arg.i = WLR_DIRECTION_RIGHT; + focusmon(&arg); + return; + } + if (!strcmp(action, "setlayout")) { + /* arg is index into layouts[]; "0" = floating, "1" = dwindle and so + * on and so forth */ + + if (a0) arg.ui = (uint32_t)atoi(a0); + setlayout(&arg); + return; + } + if (!strcmp(action, "tagmon")) { + if (a0 && !strcmp(a0, "left")) arg.i = WLR_DIRECTION_LEFT; + else if (a0 && !strcmp(a0, "right")) arg.i = WLR_DIRECTION_RIGHT; + tagmon(&arg); + return; + } + if (!strcmp(action, "moveresize")) { + if (a0 && !strcmp(a0, "move")) arg.ui = CurMove; + else if (a0 && !strcmp(a0, "resize")) arg.ui = CurResize; + moveresize(&arg); + return; + } + if (!strcmp(action, "chvt")) { + if (a0) arg.ui = (uint32_t)atoi(a0); + chvt(&arg); + return; + } + + fprintf(stderr, "swindle: unknown action '%s'\n", action); +} + +int +keybinding(uint32_t mods, xkb_keysym_t sym) +{ + for (int i = 0; i < cfg.nkeybinds; i++) { + const CfgKeybind *k = &cfg.keybinds[i]; + if (CLEANMASK(mods) == CLEANMASK(k->mods) + && xkb_keysym_to_lower(sym) == xkb_keysym_to_lower(k->key)) { + dispatch_action(k->action, k->args, k->nargs); + return 1; + } + } + return 0; +} + +void +keypress(struct wl_listener *listener, void *data) +{ + int i; + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; + /* Get a list of keysyms based on the keymap for this keyboard */ + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms( + group->wlr_group->keyboard.xkb_state, keycode, &syms); + + /* + * Also get the base-level (unshifted) keysyms for this key + * This is necessary for binds like { mods={"logo","shift"}, key="1" }: + * when Shift is held, xkb_state_key_get_syms returns "exclam" (0x21), + * not "1" (0x31), so the config match would silently fail + * Querying level 0 of the active layout gives it the unshifted sym + * regardless of modifier state, which matches what the user wrote in + * the config + */ + const xkb_keysym_t *base_syms; + int base_nsyms = xkb_keymap_key_get_syms_by_level( + xkb_state_get_keymap(group->wlr_group->keyboard.xkb_state), + keycode, + xkb_state_key_get_layout( + group->wlr_group->keyboard.xkb_state, keycode), + 0, /* level 0 = unshifted */ + &base_syms); + + int handled = 0; + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + if (session && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) { + if (syms[i] >= XKB_KEY_XF86Switch_VT_1 && + syms[i] <= XKB_KEY_XF86Switch_VT_12) { + wlr_session_change_vt(session, + syms[i] - XKB_KEY_XF86Switch_VT_1 + 1); + return; + } + } + for (i = 0; i < base_nsyms; i++) { + if (base_syms[i] >= XKB_KEY_XF86Switch_VT_1 && + base_syms[i] <= XKB_KEY_XF86Switch_VT_12) { + wlr_session_change_vt(session, + base_syms[i] - XKB_KEY_XF86Switch_VT_1 + 1); + return; + } + } + } + + /* Try state-aware syms first, then base syms (catches shifted digit binds). */ + if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) + handled = keybinding(mods, syms[i]) || handled; + if (!handled) { + for (i = 0; i < base_nsyms; i++) + handled = keybinding(mods, base_syms[i]) || handled; + } + } + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; + group->keysyms = base_syms; + group->nsyms = base_nsyms; + wl_event_source_timer_update(group->key_repeat_source, + group->wlr_group->keyboard.repeat_info.delay); + } else { + group->nsyms = 0; + wl_event_source_timer_update(group->key_repeat_source, 0); + } + + if (handled) + return; + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Pass unhandled keycodes along to the client. */ + wlr_seat_keyboard_notify_key(seat, event->time_msec, + event->keycode, event->state); +} + +void +keypressmod(struct wl_listener *listener, void *data) +{ + /* This event is raised when a modifier key, such as shift or alt, is + * pressed. We simply communicate this to the client. */ + KeyboardGroup *group = wl_container_of(listener, group, modifiers); + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Send modifiers to the client. */ + wlr_seat_keyboard_notify_modifiers(seat, + &group->wlr_group->keyboard.modifiers); +} + +int +keyrepeat(void *data) +{ + KeyboardGroup *group = data; + int i; + if (!group->nsyms || group->wlr_group->keyboard.repeat_info.rate <= 0) + return 0; + + wl_event_source_timer_update(group->key_repeat_source, + 1000 / group->wlr_group->keyboard.repeat_info.rate); + + for (i = 0; i < group->nsyms; i++) + keybinding(group->mods, group->keysyms[i]); + + return 0; +} + +void +killclient(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + client_send_close(sel); +} + +void +locksession(struct wl_listener *listener, void *data) +{ + struct wlr_session_lock_v1 *session_lock = data; + SessionLock *lock; + wlr_scene_node_set_enabled(&locked_bg->node, 1); + if (cur_lock) { + wlr_session_lock_v1_destroy(session_lock); + return; + } + lock = session_lock->data = ecalloc(1, sizeof(*lock)); + focusclient(NULL, 0); + + lock->scene = wlr_scene_tree_create(layers[LyrBlock]); + cur_lock = lock->lock = session_lock; + locked = 1; + + LISTEN(&session_lock->events.new_surface, &lock->new_surface, createlocksurface); + LISTEN(&session_lock->events.destroy, &lock->destroy, destroysessionlock); + LISTEN(&session_lock->events.unlock, &lock->unlock, unlocksession); + + wlr_session_lock_v1_send_locked(session_lock); +} + +void +mapnotify(struct wl_listener *listener, void *data) +{ + /* Called when the surface is mapped, or ready to display on-screen. */ + Client *p = NULL; + Client *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); + /* Enabled later by a call to arrange() */ + wlr_scene_node_set_enabled(&c->scene->node, client_is_unmanaged(c)); + c->scene_surface = c->type == XDGShell + ? wlr_scene_xdg_surface_create(c->scene, c->surface.xdg) + : wlr_scene_subsurface_tree_create(c->scene, client_surface(c)); + c->scene->node.data = c->scene_surface->node.data = c; + + client_get_geometry(c, &c->geom); + + /* Handle unmanaged clients first so we can return prior create borders */ + if (client_is_unmanaged(c)) { + /* Unmanaged clients always are floating */ + wlr_scene_node_reparent(&c->scene->node, layers[LyrFloat]); + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + client_set_size(c, c->geom.width, c->geom.height); + if (client_wants_focus(c)) { + focusclient(c, 1); + exclusive_focus = c; + } + goto unset_fullscreen; + } + + for (i = 0; i < 4; i++) { + c->inner_border[i] = wlr_scene_rect_create(c->scene, 0, 0, + c->isurgent ? cfg.appearance.inner_urgent_color : cfg.appearance.inner_border_color); + c->inner_border[i]->node.data = c; + + c->outer_border[i] = wlr_scene_rect_create(c->scene, 0, 0, + c->isurgent ? cfg.appearance.outer_urgent_color : cfg.appearance.outer_border_color); + c->outer_border[i]->node.data = c; + } + + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; + c->geom.height += 2 * c->bw; + + /* Insert this client into client lists. */ + wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* Set initial monitor, tags, floating status, and focus: + * we always consider floating, clients that have parent and thus + * we set the same tags and monitor as its parent. + * If there is no parent, apply rules */ + if ((p = client_get_parent(c))) { + c->isfloating = 1; + setmon(c, p->mon, p->tags); + } else { + applyrules(c); + } + printstatus(); + +unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); + wl_list_for_each(w, &clients, link) { + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) + setfullscreen(w, 0); + } +} + +void +maximizenotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client would like to maximize itself, + * typically because the user clicked on the maximize button on + * client-side decorations. swindle doesn't support maximization, but + * to conform to xdg-shell protocol we still must send a configure. + * Since xdg-shell protocol v5 we should ignore request of unsupported + * capabilities, just schedule a empty configure when the client uses <5 + * protocol version + * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ + Client *c = wl_container_of(listener, c, maximize); + if (c->surface.xdg->initialized + && wl_resource_get_version(c->surface.xdg->toplevel->resource) + < XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION) + wlr_xdg_surface_schedule_configure(c->surface.xdg); +} + + +void +motionabsolute(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an _absolute_ + * motion event, from 0..1 on each axis. This happens, for example, when + * wlroots is running under a Wayland window rather than KMS+DRM, and you + * move the mouse over the window. You could enter the window from any edge, + * so we have to warp the mouse there. Also, some hardware emits these events. */ + struct wlr_pointer_motion_absolute_event *event = data; + double lx, ly, dx, dy; + + if (!event->time_msec) /* this is 0 with virtual pointers */ + wlr_cursor_warp_absolute(cursor, &event->pointer->base, event->x, event->y); + + wlr_cursor_absolute_to_layout_coords(cursor, &event->pointer->base, event->x, event->y, &lx, &ly); + dx = lx - cursor->x; + dy = ly - cursor->y; + motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy); +} + +void +motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) +{ + double sx = 0, sy = 0, sx_confined, sy_confined; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct wlr_surface *surface = NULL; + struct wlr_pointer_constraint_v1 *constraint; + + /* Find the client under the pointer and send the event along. */ + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); + + if (cursor_mode == CurPressed && !seat->drag + && surface != seat->pointer_state.focused_surface + && toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, &l) >= 0) { + c = w; + surface = seat->pointer_state.focused_surface; + sx = cursor->x - (l ? l->scene->node.x : w->geom.x); + sy = cursor->y - (l ? l->scene->node.y : w->geom.y); + } + + /* time is 0 in internal calls meant to restore pointer focus. */ + if (time) { + wlr_relative_pointer_manager_v1_send_relative_motion( + relative_pointer_mgr, seat, (uint64_t)time * 1000, + dx, dy, dx_unaccel, dy_unaccel); + + wl_list_for_each(constraint, &pointer_constraints->constraints, link) + cursorconstrain(constraint); + + if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->surface == seat->pointer_state.focused_surface) { + sx = cursor->x - c->geom.x - c->bw; + sy = cursor->y - c->geom.y - c->bw; + if (wlr_region_confine(&active_constraint->region, sx, sy, + sx + dx, sy + dy, &sx_confined, &sy_confined)) { + dx = sx_confined - sx; + dy = sy_confined - sy; + } + + if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) + return; + } + } + + wlr_cursor_move(cursor, device, dx, dy); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + if (cfg.sloppyfocus) + selmon = xytomon(cursor->x, cursor->y); + } + + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + + /* If we are currently grabbing the mouse, handle and return */ + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; + } else if (cursor_mode == CurResize) { + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); + return; + } else if (cursor_mode == CurTileDrag) { + resize(grabc, (struct wlr_box){ + .x = (int)round(cursor->x) - grabcx, + .y = (int)round(cursor->y) - grabcy, + .width = TILEDRAG_DETACH_W, + .height = TILEDRAG_DETACH_H, + }, 1); + + /* tiled_client_at, not xytonode: grabc is on LyrFloat now and + * covers whatever's under the cursor, so a scene hit-test would + * just find grabc itself and stop. */ + Client *target = tiled_client_at(cursor->x, cursor->y, grabc); + + if (target) { + preselect_target = target; + preselect_edge = edge_at_point(target->geom, cursor->x, cursor->y); + } else { + preselect_target = NULL; + preselect_edge = EDGE_NONE; + } + + if (preselect_target && preselect_edge != EDGE_NONE) { + struct wlr_box ind = preselect_indicator_box(preselect_target->geom, preselect_edge); + wlr_scene_node_set_position(&preselect_indicator->node, ind.x, ind.y); + wlr_scene_rect_set_size(preselect_indicator, ind.width, ind.height); + wlr_scene_node_set_enabled(&preselect_indicator->node, true); + } else { + wlr_scene_node_set_enabled(&preselect_indicator->node, false); + } + return; + } + + /* If there's no client surface under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * off of a client or over its border. */ + if (!surface && !seat->drag) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + pointerfocus(c, surface, sx, sy, time); +} + +void +motionrelative(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits a _relative_ + * pointer motion event (i.e. a delta) */ + struct wlr_pointer_motion_event *event = data; + /* The cursor doesn't move unless we tell it to. The cursor automatically + * handles constraining the motion to the output layout, as well as any + * special configuration applied for the specific input device which + * generated the event. You can pass NULL for the device if you want to move + * the cursor around without any input. */ + motionnotify(event->time_msec, &event->pointer->base, event->delta_x, event->delta_y, + event->unaccel_dx, event->unaccel_dy); +} + +void +moveresize(const Arg *arg) +{ + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + + if (arg->ui == CurMove && !grabc->isfloating + && grabc->mon->lt[grabc->mon->sellt]->arrange) { + cursor_mode = CurTileDrag; + tiledrag_orig_geom = grabc->geom; + grabcx = TILEDRAG_DETACH_W / 2; + grabcy = TILEDRAG_DETACH_H / 2; + wlr_scene_node_reparent(&grabc->scene->node, layers[LyrFloat]); + resize(grabc, (struct wlr_box){ + .x = (int)round(cursor->x) - grabcx, + .y = (int)round(cursor->y) - grabcy, + .width = TILEDRAG_DETACH_W, + .height = TILEDRAG_DETACH_H, + }, 1); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "grabbing"); + return; + } + + /* Float the window and tell motionnotify to grab it */ + setfloating(grabc, 1); + switch (cursor_mode = arg->ui) { + case CurMove: + grabcx = (int)round(cursor->x) - grabc->geom.x; + grabcy = (int)round(cursor->y) - grabc->geom.y; + wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll"); + break; + case CurResize: + /* Doesn't work for X11 output - the next absolute motion event + * returns the cursor to where it started */ + wlr_cursor_warp_closest(cursor, NULL, + grabc->geom.x + grabc->geom.width, + grabc->geom.y + grabc->geom.height); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); + break; + } +} + +void +outputmgrapply(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 0); +} + +void +outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) +{ + /* + * Called when a client such as wlr-randr requests a change in output + * configuration. This is only one way that the layout can be changed, + * so any Monitor information should be updated by updatemons() after an + * output_layout.change event, not here. + */ + struct wlr_output_configuration_head_v1 *config_head; + int ok = 1; + + wl_list_for_each(config_head, &config->heads, link) { + struct wlr_output *wlr_output = config_head->state.output; + Monitor *m = wlr_output->data; + struct wlr_output_state state; + + /* Ensure displays previously disabled by wlr-output-power-management-v1 + * are properly handled*/ + m->asleep = 0; + + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, config_head->state.enabled); + if (!config_head->state.enabled) + goto apply_or_test; + + if (config_head->state.mode) + wlr_output_state_set_mode(&state, config_head->state.mode); + else + wlr_output_state_set_custom_mode(&state, + config_head->state.custom_mode.width, + config_head->state.custom_mode.height, + config_head->state.custom_mode.refresh); + + wlr_output_state_set_transform(&state, config_head->state.transform); + wlr_output_state_set_scale(&state, config_head->state.scale); + wlr_output_state_set_adaptive_sync_enabled(&state, + config_head->state.adaptive_sync_enabled); + +apply_or_test: + ok &= test ? wlr_output_test_state(wlr_output, &state) + : wlr_output_commit_state(wlr_output, &state); + + /* Don't move monitors if position wouldn't change. This avoids + * wlroots marking the output as manually configured. + * wlr_output_layout_add does not like disabled outputs */ + if (!test && wlr_output->enabled && (m->m.x != config_head->state.x || m->m.y != config_head->state.y)) + wlr_output_layout_add(output_layout, wlr_output, + config_head->state.x, config_head->state.y); + + wlr_output_state_finish(&state); + } + + if (ok) + wlr_output_configuration_v1_send_succeeded(config); + else + wlr_output_configuration_v1_send_failed(config); + wlr_output_configuration_v1_destroy(config); + + updatemons(NULL, NULL); +} + +void +outputmgrtest(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 1); +} + +void +pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + uint32_t time) +{ + struct timespec now; + + if (surface != seat->pointer_state.focused_surface && + cfg.sloppyfocus && time && c && !client_is_unmanaged(c)) + focusclient(c, 0); + + /* If surface is NULL, clear pointer focus */ + if (!surface) { + wlr_seat_pointer_notify_clear_focus(seat); + return; + } + + if (!time) { + clock_gettime(CLOCK_MONOTONIC, &now); + time = now.tv_sec * 1000 + now.tv_nsec / 1000000; + } + + /* Let the client know that the mouse cursor has entered one + * of its surfaces, and make keyboard focus follow if desired. + * wlroots makes this a no-op if surface is already focused */ + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(seat, time, sx, sy); +} + +void +printstatus(void) +{ + Monitor *m = NULL; + Client *c; + uint32_t occ, urg, sel; + + wl_list_for_each(m, &mons, link) { + occ = urg = 0; + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + if ((c = focustop(m))) { + printf("%s title %s\n", m->wlr_output->name, client_get_title(c)); + printf("%s appid %s\n", m->wlr_output->name, client_get_appid(c)); + printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); + printf("%s floating %d\n", m->wlr_output->name, c->isfloating); + sel = c->tags; + } else { + printf("%s title \n", m->wlr_output->name); + printf("%s appid \n", m->wlr_output->name); + printf("%s fullscreen \n", m->wlr_output->name); + printf("%s floating \n", m->wlr_output->name); + sel = 0; + } + + printf("%s selmon %u\n", m->wlr_output->name, m == selmon); + printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", + m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); + printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); + ext_workspace_printstatus(m); + } + fflush(stdout); + ipc_printstatus(); +} + +void +powermgrsetmode(struct wl_listener *listener, void *data) +{ + struct wlr_output_power_v1_set_mode_event *event = data; + struct wlr_output_state state = {0}; + Monitor *m = event->output->data; + + if (!m) + return; + + m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the output */ + wlr_output_state_set_enabled(&state, event->mode); + wlr_output_commit_state(m->wlr_output, &state); + + m->asleep = !event->mode; + updatemons(NULL, NULL); +} + +void +quit(const Arg *arg) +{ + wl_display_terminate(dpy); +} + +void +rendermon(struct wl_listener *listener, void *data) +{ + /* This function is called every time an output is ready to display a frame, + * generally at the output's refresh rate (e.g. 60Hz). */ + Monitor *m = wl_container_of(listener, m, frame); + Client *c; + struct wlr_output_state pending = {0}; + struct timespec now; + + /* Render if no XDG clients have an outstanding resize and are visible on + * this monitor. */ + wl_list_for_each(c, &clients, link) { + if (c->resize && !c->isfloating && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) + goto skip; + } + + wlr_scene_output_commit(m->scene_output, NULL); + +skip: + /* Let clients know a frame has been rendered */ + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(m->scene_output, &now); + wlr_output_state_finish(&pending); +} + +void +requestdecorationmode(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_decoration_mode); + if (c->surface.xdg->initialized) + wlr_xdg_toplevel_decoration_v1_set_mode(c->decoration, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); +} + +void +requeststartdrag(struct wl_listener *listener, void *data) +{ + struct wlr_seat_request_start_drag_event *event = data; + + if (wlr_seat_validate_pointer_grab_serial(seat, event->origin, + event->serial)) + wlr_seat_start_pointer_drag(seat, event->drag, event->serial); + else + wlr_data_source_destroy(event->drag->source); +} + +void +requestmonstate(struct wl_listener *listener, void *data) +{ + struct wlr_output_event_request_state *event = data; + wlr_output_commit_state(event->output, event->state); + updatemons(NULL, NULL); +} + +void +resize(Client *c, struct wlr_box geo, int interact) +{ + struct wlr_box *bbox; + struct wlr_box clip; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; + applybounds(c, bbox); + + /* Update scene-graph, including borders. + * Outer ring sits flush with the client's outer edge (0,0 .. w,h). + * Inner ring is inset by obw from the outer ring. + * The surface itself is inset by the full bw = ibw + obw. */ + { + unsigned int ib = c->ibw, ob = c->obw; + int w = c->geom.width, h = c->geom.height; + + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + + wlr_scene_rect_set_size(c->inner_border[0], MAX(1, w - 2 * (int)ob), (int)ib); + wlr_scene_node_set_position(&c->inner_border[0]->node, ob, ob); + wlr_scene_rect_set_size(c->inner_border[1], MAX(1, w - 2 * (int)ob), (int)ib); + wlr_scene_node_set_position(&c->inner_border[1]->node, ob, MAX(0, h - (int)ob - (int)ib)); + wlr_scene_rect_set_size(c->inner_border[2], (int)ib, MAX(1, h - 2 * (int)ob)); + wlr_scene_node_set_position(&c->inner_border[2]->node, ob, ob); + wlr_scene_rect_set_size(c->inner_border[3], (int)ib, MAX(1, h - 2 * (int)ob)); + wlr_scene_node_set_position(&c->inner_border[3]->node, MAX(0, w - (int)ob - (int)ib), ob); + + wlr_scene_rect_set_size(c->outer_border[0], MAX(1, w), (int)ob); + wlr_scene_node_set_position(&c->outer_border[0]->node, 0, 0); + wlr_scene_rect_set_size(c->outer_border[1], MAX(1, w), (int)ob); + wlr_scene_node_set_position(&c->outer_border[1]->node, 0, MAX(0, h - (int)ob)); + wlr_scene_rect_set_size(c->outer_border[2], (int)ob, MAX(1, h - 2 * (int)ob)); + wlr_scene_node_set_position(&c->outer_border[2]->node, 0, ob); + wlr_scene_rect_set_size(c->outer_border[3], (int)ob, MAX(1, h - 2 * (int)ob)); + wlr_scene_node_set_position(&c->outer_border[3]->node, MAX(0, w - (int)ob), ob); + } + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, + c->geom.height - 2 * c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); +} + +void +run(char *startup_cmd) +{ + /* Add a Unix socket to the Wayland display. */ + const char *socket = wl_display_add_socket_auto(dpy); + if (!socket) + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); + + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ + if (!wlr_backend_start(backend)) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ + if (startup_cmd) { + int piperw[2]; + if (pipe(piperw) < 0) + die("startup: pipe:"); + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { + setsid(); + dup2(piperw[0], STDIN_FILENO); + close(piperw[0]); + close(piperw[1]); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } + dup2(piperw[1], STDOUT_FILENO); + close(piperw[1]); + close(piperw[0]); + } + + /* Mark stdout as non-blocking as to avoid the startup script + * causing swindle to freeze when a user neither closes stdin + * nor consumes standard input in their startup script */ + + if (fd_set_nonblock(STDOUT_FILENO) < 0) + close(STDOUT_FILENO); + + printstatus(); + + /* Launch autostart commands now that the socket is live and the backend + * is running. Commands run sequentially in a detached child process so + * the compositor never blocks. */ + config_autostart_run(&cfg); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ + selmon = xytomon(cursor->x, cursor->y); + + wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + /* Run the Wayland event loop. This does not return until you exit the + * compositor. Starting the backend rigged up all of the necessary event + * loop configuration to listen to libinput events, DRM events, generate + * frame events at the refresh rate, and so on. */ + wl_display_run(dpy); +} + +void +setcursor(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client provides a cursor image */ + struct wlr_seat_pointer_request_set_cursor_event *event = data; + /* If we're "grabbing" the cursor, don't use the client's image, we will + * restore it after "grabbing" sending a leave event, followed by a enter + * event, which will result in the client requesting set the cursor surface */ + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* This can be sent by any client, so we check to make sure this one + * actually has pointer focus first. If so, we can tell the cursor to + * use the provided surface as the cursor image. It will set the + * hardware cursor on the output that it's currently on and continue to + * do so as the cursor moves between outputs. */ + if (event->seat_client == seat->pointer_state.focused_client) + wlr_cursor_set_surface(cursor, event->surface, + event->hotspot_x, event->hotspot_y); +} + +void +setcursorshape(struct wl_listener *listener, void *data) +{ + struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* This can be sent by any client, so we check to make sure this one + * actually has pointer focus first. If so, we can tell the cursor to + * use the provided cursor shape. */ + if (event->seat_client == seat->pointer_state.focused_client) + wlr_cursor_set_xcursor(cursor, cursor_mgr, + wlr_cursor_shape_v1_name(event->shape)); +} + +void +setfloating(Client *c, int floating) +{ + Client *p = client_get_parent(c); + c->isfloating = floating; + /* If in floating layout do not change the client's layer */ + if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange) + return; + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + printstatus(); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + c->isfullscreen = fullscreen; + if (!c->mon || !client_surface(c)->mapped) + return; + c->ibw = fullscreen ? 0 : cfg.appearance.inner_border_px; + c->obw = fullscreen ? 0 : cfg.appearance.outer_border_px; + c->bw = c->ibw + c->obw; + client_set_fullscreen(c, fullscreen); + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen + ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); + + if (fullscreen) { + c->prev = c->geom; + resize(c, c->mon->m, 0); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); + } + arrange(c->mon); + printstatus(); +} + +void +setlayout(const Arg *arg) +{ + if (!selmon) + return; + /* arg->ui is an index into layouts[]. If out of range, just toggle. */ + if (arg && arg->ui < LENGTH(layouts)) { + if (selmon->lt[selmon->sellt] != &layouts[arg->ui]) + selmon->sellt ^= 1; + selmon->lt[selmon->sellt] = &layouts[arg->ui]; + } else { + selmon->sellt ^= 1; + } + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + printstatus(); +} + +/* arg > 1.0 will set mfact absolutely */ + +void +setmon(Client *c, Monitor *m, uint32_t newtags) +{ + Monitor *oldmon = c->mon; + + if (oldmon == m) + return; + c->mon = m; + c->prev = c->geom; + + /* Scene graph sends surface leave/enter events on move and resize */ + if (oldmon) + arrange(oldmon); + if (m) { + /* Make sure window actually overlaps with the monitor */ + resize(c, c->geom, 0); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } + focusclient(focustop(selmon), 1); +} + +void +setpsel(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in dwl we always honor them + */ + struct wlr_seat_request_set_primary_selection_event *event = data; + wlr_seat_set_primary_selection(seat, event->source, event->serial); +} + +void +setsel(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in dwl we always honor them + */ + struct wlr_seat_request_set_selection_event *event = data; + wlr_seat_set_selection(seat, event->source, event->serial); +} + +/* Config hot reload callback is fired by the inotify watcher */ + +static void +on_config_reload(const Config *newcfg, void *ud) +{ + Monitor *m; + (void)ud; + cfg = *newcfg; + wl_list_for_each(m, &mons, link) + arrange(m); + printstatus(); +} + +void +setup(void) +{ + int drm_fd, i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + + /* Load config before anything else so all settings are ready */ + { + char cfgpath[1024]; + const char *path; + int debug_override = (cfg.log_level == WLR_DEBUG); /* set by -d flag in main() */ + if (custom_cfg_path) + path = custom_cfg_path; + else { + config_get_path(cfgpath, sizeof(cfgpath)); + path = cfgpath; + } + if (config_load(path, &cfg) < 0) + die("failed to load config from '%s'", path); + if (debug_override) + cfg.log_level = WLR_DEBUG; + } + + wlr_log_init(cfg.log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, managing Wayland globals, and so on and so forth */ + dpy = wl_display_create(); + event_loop = wl_display_get_event_loop(dpy); + ipc_init(); + workspaces_init(); + + /* Start the inotify config watcher */ + + { + char cfgpath[1024]; + const char *path = custom_cfg_path; + if (!path) { + config_get_path(cfgpath, sizeof(cfgpath)); + path = cfgpath; + } + cfg_watch_src = config_watch_start(event_loop, path, + on_config_reload, NULL); + } + + /* The backend is a wlroots feature which abstracts the underlying input and + * output hardware. The autocreate option will choose the most suitable + * backend based on the current environment, such as opening an X11 window + * if an X11 server is running. */ + if (!(backend = wlr_backend_autocreate(event_loop, &session))) + die("couldn't create backend"); + + /* Initialize the scene graph used to lay out windows */ + scene = wlr_scene_create(); + root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, cfg.appearance.root_color); + for (i = 0; i < NUM_LAYERS; i++) + layers[i] = wlr_scene_tree_create(&scene->tree); + drag_icon = wlr_scene_tree_create(&scene->tree); + wlr_scene_node_place_below(&drag_icon->node, &layers[LyrBlock]->node); + + { + static const float preselect_color[4] = {1.0f, 1.0f, 1.0f, 0.25f}; + preselect_indicator = wlr_scene_rect_create(layers[LyrOverlay], 0, 0, preselect_color); + wlr_scene_node_set_enabled(&preselect_indicator->node, false); + } + + /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user + * can also specify a renderer using the WLR_RENDERER env var. + * The renderer is responsible for defining the various pixel formats it + * supports for shared memory, this configures that for clients. */ + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't create renderer"); + wl_signal_add(&drw->events.lost, &gpu_reset); + + /* Create shm, drm and linux_dmabuf interfaces by ourselves. + * The simplest way is to call: + * wlr_renderer_init_wl_display(drw); + * but we need to create the linux_dmabuf interface manually to integrate it + * with wlr_scene. */ + wlr_renderer_init_wl_shm(drw, dpy); + + if (wlr_renderer_get_texture_formats(drw, WLR_BUFFER_CAP_DMABUF)) { + wlr_drm_create(dpy, drw); + wlr_scene_set_linux_dmabuf_v1(scene, + wlr_linux_dmabuf_v1_create_with_renderer(dpy, 5, drw)); + } + + if ((drm_fd = wlr_renderer_get_drm_fd(drw)) >= 0 && drw->features.timeline + && backend->features.timeline) + wlr_linux_drm_syncobj_manager_v1_create(dpy, 1, drm_fd); + + /* Autocreates an allocator for us. + * The allocator is the bridge between the renderer and the backend. It + * handles the buffer creation, allowing wlroots to render onto the + * screen */ + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't create allocator"); + + /* This creates some hands-off wlroots interfaces. The compositor is + * necessary for clients to allocate surfaces and the data device manager + * handles the clipboard. Each of these wlroots interfaces has room for you + * to dig your fingers in and play with their behavior if you want. Note that + * the clients cannot set the selection directly without compositor approval, + * see the setsel() function. */ + compositor = wlr_compositor_create(dpy, 6, drw); + wlr_subcompositor_create(dpy); + wlr_data_device_manager_create(dpy); + wlr_export_dmabuf_manager_v1_create(dpy); + wlr_screencopy_manager_v1_create(dpy); + wlr_data_control_manager_v1_create(dpy); + wlr_ext_data_control_manager_v1_create(dpy, 1); + wlr_primary_selection_v1_device_manager_create(dpy); + wlr_viewporter_create(dpy); + wlr_single_pixel_buffer_manager_v1_create(dpy); + wlr_fractional_scale_manager_v1_create(dpy, 1); + wlr_presentation_create(dpy, backend, 2); + wlr_alpha_modifier_v1_create(dpy); + + /* Initializes the interface used to implement urgency hints */ + activation = wlr_xdg_activation_v1_create(dpy); + wl_signal_add(&activation->events.request_activate, &request_activate); + + wlr_scene_set_gamma_control_manager_v1(scene, wlr_gamma_control_manager_v1_create(dpy)); + + power_mgr = wlr_output_power_manager_v1_create(dpy); + wl_signal_add(&power_mgr->events.set_mode, &output_power_mgr_set_mode); + + /* Creates an output layout, which is a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + output_layout = wlr_output_layout_create(dpy); + wl_signal_add(&output_layout->events.change, &layout_change); + + wlr_xdg_output_manager_v1_create(dpy, output_layout); + + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + wl_list_init(&mons); + wl_signal_add(&backend->events.new_output, &new_output); + + /* Set up our client lists, the xdg-shell and the layer-shell. The xdg-shell is a + * Wayland protocol which is used for application windows. For more + * detail on shells, refer to the article: + * + * https://drewdevault.com/2018/07/29/Wayland-shells.html + */ + wl_list_init(&clients); + wl_list_init(&fstack); + + xdg_shell = wlr_xdg_shell_create(dpy, 6); + wl_signal_add(&xdg_shell->events.new_toplevel, &new_xdg_toplevel); + wl_signal_add(&xdg_shell->events.new_popup, &new_xdg_popup); + + layer_shell = wlr_layer_shell_v1_create(dpy, 3); + wl_signal_add(&layer_shell->events.new_surface, &new_layer_surface); + + idle_notifier = wlr_idle_notifier_v1_create(dpy); + + idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); + wl_signal_add(&idle_inhibit_mgr->events.new_inhibitor, &new_idle_inhibitor); + + session_lock_mgr = wlr_session_lock_manager_v1_create(dpy); + wl_signal_add(&session_lock_mgr->events.new_lock, &new_session_lock); + locked_bg = wlr_scene_rect_create(layers[LyrBlock], sgeom.width, sgeom.height, + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); + wlr_scene_node_set_enabled(&locked_bg->node, 0); + + /* Use decoration protocols to negotiate server-side decorations */ + wlr_server_decoration_manager_set_default_mode( + wlr_server_decoration_manager_create(dpy), + WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); + xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy); + wl_signal_add(&xdg_decoration_mgr->events.new_toplevel_decoration, &new_xdg_decoration); + + pointer_constraints = wlr_pointer_constraints_v1_create(dpy); + wl_signal_add(&pointer_constraints->events.new_constraint, &new_pointer_constraint); + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. + */ + cursor = wlr_cursor_create(); + wlr_cursor_attach_output_layout(cursor, output_layout); + + /* Creates an xcursor manager, another wlroots utility which loads up + * Xcursor themes to source cursor images from and makes sure that cursor + * images are available at all scale factors on the screen (necessary for + * HiDPI support). Scaled cursors will be loaded with each output. */ + cursor_mgr = wlr_xcursor_manager_create(NULL, 24); + setenv("XCURSOR_SIZE", "24", 1); + + /* + * wlr_cursor *only* displays an image on screen. It does not move around + * when the pointer moves. However, we can attach input devices to it, and + * it will generate aggregate events for all of them. In these events, we + * can choose how we want to process them, forwarding them to clients and + * moving the cursor around. More detail on this process is described in + * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html + * + * And more comments are sprinkled throughout the notify functions above. + */ + wl_signal_add(&cursor->events.motion, &cursor_motion); + wl_signal_add(&cursor->events.motion_absolute, &cursor_motion_absolute); + wl_signal_add(&cursor->events.button, &cursor_button); + wl_signal_add(&cursor->events.axis, &cursor_axis); + wl_signal_add(&cursor->events.frame, &cursor_frame); + + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); + + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ + wl_signal_add(&backend->events.new_input, &new_input_device); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); + wl_signal_add(&virtual_keyboard_mgr->events.new_virtual_keyboard, + &new_virtual_keyboard); + virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); + wl_signal_add(&virtual_pointer_mgr->events.new_virtual_pointer, + &new_virtual_pointer); + + seat = wlr_seat_create(dpy, "seat0"); + wl_signal_add(&seat->events.request_set_cursor, &request_cursor); + wl_signal_add(&seat->events.request_set_selection, &request_set_sel); + wl_signal_add(&seat->events.request_set_primary_selection, &request_set_psel); + wl_signal_add(&seat->events.request_start_drag, &request_start_drag); + wl_signal_add(&seat->events.start_drag, &start_drag); + + kb_group = createkeyboardgroup(); + wl_list_init(&kb_group->destroy.link); + + output_mgr = wlr_output_manager_v1_create(dpy); + wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); + wl_signal_add(&output_mgr->events.test, &output_mgr_test); + + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ + unsetenv("DISPLAY"); +#ifdef XWAYLAND + /* + * Initialise the XWayland X server. + * It will be started when the first X client is started. + */ + if ((xwayland = wlr_xwayland_create(dpy, compositor, 1))) { + wl_signal_add(&xwayland->events.ready, &xwayland_ready); + wl_signal_add(&xwayland->events.new_surface, &new_xwayland_surface); + + setenv("DISPLAY", xwayland->display_name, 1); + } else { + fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); + } +#endif +} + +void +spawn(const Arg *arg) +{ + if (fork() == 0) { + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + die("dwl: execvp %s failed:", ((char **)arg->v)[0]); + } +} + +void +startdrag(struct wl_listener *listener, void *data) +{ + struct wlr_drag *drag = data; + if (!drag->icon) + return; + + drag->icon->data = &wlr_scene_drag_icon_create(drag_icon, drag->icon)->node; + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); +} + +void +tag(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (!sel || (arg->ui & TAGMASK) == 0) + return; + + /* Remove from the source tag's dwindle tree */ + dwindle_remove_client(sel); + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +} + +void +tagmon(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setmon(sel, dirtomon(arg->i), 0); +} + + +/* dwindle helpers */ + +/* + * Returns the 0-based index of the lowest set tag bit for monitor m. + * For single-tag views this is the exact tag. For multi-tag views it + * picks the lowest bit */ + +static inline int +current_tag_idx(Monitor *m) +{ + uint32_t tags = m->tagset[m->seltags] & TAGMASK; + if (!tags) + return 0; + int idx = 0; + while (!(tags & 1u)) { tags >>= 1; idx++; } + return idx < TAGCOUNT ? idx : 0; +} + +static DwindleNode * +dwindle_new_node(void) +{ + DwindleNode *n = ecalloc(1, sizeof(*n)); + n->split_ratio = 1.0f; + return n; +} + +void +dwindle_free_tree(DwindleNode *n) +{ + if (!n) + return; + dwindle_free_tree(n->children[0]); + dwindle_free_tree(n->children[1]); + free(n); +} + +static DwindleNode * +dwindle_find_leaf(DwindleNode *n, Client *c) +{ + if (!n) + return NULL; + if (!n->is_node) + return n->client == c ? n : NULL; + DwindleNode *r = dwindle_find_leaf(n->children[0], c); + return r ? r : dwindle_find_leaf(n->children[1], c); +} + +static DwindleNode * +dwindle_first_leaf(DwindleNode *n) +{ + if (!n) + return NULL; + while (n->is_node) + n = n->children[0]; + return n; +} + +/* + * Recursively distribute geometry downward from node n + * gap is inner gap pixels between the two halves + */ + +static void +dwindle_recalc(DwindleNode *n, int gap) +{ + if (!n) + return; + + if (!n->is_node) { + if (n->client && !n->client->isfullscreen) + resize(n->client, n->box, 0); + return; + } + + /* Wider than tall -> split left/right; taller -> split top/bottom. */ + n->split_top = (n->box.height > n->box.width); + + if (!n->split_top) { + int w1 = MAX(1, (int)(n->box.width / 2.0f * n->split_ratio) - gap / 2); + n->children[0]->box = (struct wlr_box){ + n->box.x, n->box.y, w1, n->box.height + }; + n->children[1]->box = (struct wlr_box){ + n->box.x + w1 + gap, n->box.y, + MAX(1, n->box.width - w1 - gap), n->box.height + }; + } else { + int h1 = MAX(1, (int)(n->box.height / 2.0f * n->split_ratio) - gap / 2); + n->children[0]->box = (struct wlr_box){ + n->box.x, n->box.y, n->box.width, h1 + }; + n->children[1]->box = (struct wlr_box){ + n->box.x, n->box.y + h1 + gap, + n->box.width, MAX(1, n->box.height - h1 - gap) + }; + } + + dwindle_recalc(n->children[0], gap); + dwindle_recalc(n->children[1], gap); +} + +static int +dwindle_insert_at_edge(DwindleNode **root, Client *new_c, Client *target, DwindleEdge edge) +{ + DwindleNode *opening_on = dwindle_find_leaf(*root, target); + if (!opening_on) + return 0; + + DwindleNode *new_leaf = dwindle_new_node(); + new_leaf->client = new_c; + new_leaf->is_node = 0; + + DwindleNode *new_parent = dwindle_new_node(); + new_parent->is_node = 1; + new_parent->box = opening_on->box; + new_parent->parent = opening_on->parent; + + int new_c_first = (edge == EDGE_LEFT || edge == EDGE_TOP); + new_parent->split_top = (edge == EDGE_TOP || edge == EDGE_BOTTOM); + new_parent->children[new_c_first ? 0 : 1] = new_leaf; + new_parent->children[new_c_first ? 1 : 0] = opening_on; + + opening_on->parent = new_parent; + new_leaf->parent = new_parent; + + if (new_parent->parent) { + if (new_parent->parent->children[0] == opening_on) + new_parent->parent->children[0] = new_parent; + else + new_parent->parent->children[1] = new_parent; + } else { + *root = new_parent; + } + return 1; +} + +/* + * Insert new_c into the tree, bisecting the focused client's node + * It falls back to the first leaf if focused is NULL or not in the tree + */ + +static void +dwindle_insert(DwindleNode **root, Client *new_c, Client *focused, DwindleHint *hint) +{ + DwindleNode *new_leaf = dwindle_new_node(); + new_leaf->client = new_c; + new_leaf->is_node = 0; + + if (!*root) { + *root = new_leaf; + return; + } + + DwindleNode *opening_on = focused + ? dwindle_find_leaf(*root, focused) : NULL; + if (!opening_on) + opening_on = dwindle_first_leaf(*root); + + if (hint) { + int split_top = (opening_on->box.height > opening_on->box.width); + hint->old_box = opening_on->box; + hint->edge = split_top ? EDGE_BOTTOM : EDGE_RIGHT; + } + + DwindleNode *new_parent = dwindle_new_node(); + new_parent->is_node = 1; + new_parent->box = opening_on->box; + new_parent->parent = opening_on->parent; + new_parent->split_top = (opening_on->box.height > opening_on->box.width); + new_parent->children[0] = opening_on; + new_parent->children[1] = new_leaf; + + opening_on->parent = new_parent; + new_leaf->parent = new_parent; + + if (new_parent->parent) { + if (new_parent->parent->children[0] == opening_on) + new_parent->parent->children[0] = new_parent; + else + new_parent->parent->children[1] = new_parent; + } else { + *root = new_parent; + } +} + +/* + * Remove client c from the tree, promoting its sibling upward. + */ + +static void +dwindle_remove(DwindleNode **root, Client *c, DwindleHint *hint) +{ + DwindleNode *leaf = dwindle_find_leaf(*root, c); + if (!leaf) + return; + + DwindleNode *parent = leaf->parent; + if (!parent) { + free(leaf); + *root = NULL; + return; + } + + int leaf_is_first = (parent->children[0] == leaf); + DwindleNode *sibling = leaf_is_first + ? parent->children[1] : parent->children[0]; + DwindleNode *grandparent = parent->parent; + + if (hint) { + hint->old_box = sibling->box; + hint->edge = parent->split_top + ? (leaf_is_first ? EDGE_TOP : EDGE_BOTTOM) + : (leaf_is_first ? EDGE_LEFT : EDGE_RIGHT); + } + + sibling->parent = grandparent; + if (grandparent) { + if (grandparent->children[0] == parent) + grandparent->children[0] = sibling; + else + grandparent->children[1] = sibling; + } else { + *root = sibling; + } + + free(leaf); + free(parent); +} + +/* Remove c from every monitor's per-tag tree and call from unmapnotify(). */ + +static void +dwindle_remove_client(Client *c) +{ + Monitor *m; + wl_list_for_each(m, &mons, link) + for (int i = 0; i < TAGCOUNT; i++) + dwindle_remove(&m->dwindle_root[i], c, NULL); +} + +#define PRESELECT_MARGIN 0.2 + +static DwindleEdge +edge_at_point(struct wlr_box box, double x, double y) +{ + double rel_x = (x - box.x) / MAX(1, box.width); + double rel_y = (y - box.y) / MAX(1, box.height); + + /* Distance from each edge, in the same normalized 0..1 space. */ + double d_left = rel_x; + double d_right = 1.0 - rel_x; + double d_top = rel_y; + double d_bottom = 1.0 - rel_y; + + double closest = MIN(MIN(d_left, d_right), MIN(d_top, d_bottom)); + if (closest > PRESELECT_MARGIN) + return EDGE_NONE; + + if (closest == d_left) return EDGE_LEFT; + if (closest == d_right) return EDGE_RIGHT; + if (closest == d_top) return EDGE_TOP; + return EDGE_BOTTOM; +} + +static struct wlr_box +preselect_indicator_box(struct wlr_box target, DwindleEdge edge) +{ + struct wlr_box r = target; + switch (edge) { + case EDGE_LEFT: r.width /= 2; break; + case EDGE_RIGHT: r.width /= 2; r.x += target.width - r.width; break; + case EDGE_TOP: r.height /= 2; break; + case EDGE_BOTTOM: r.height /= 2; r.y += target.height - r.height; break; + default: break; + } + return r; +} + +/* layout entry point */ + +void +dwindle(Monitor *m) +{ + Client *c; + int n = 0, e; + Client *dragging = (cursor_mode == CurTileDrag) ? grabc : NULL; + + wl_list_for_each(c, &clients, link) + if (c != dragging && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) + n++; + + if (n == 0) + return; + + e = (m->gaps && !(cfg.appearance.smart_gaps && n == 1)) ? (int)cfg.appearance.gaps : 0; + + int ti = current_tag_idx(m); + DwindleNode **root = &m->dwindle_root[ti]; + + /* prune leaves whose clients are no longer tiled here */ + { + DwindleNode *stack[512]; + Client *stale[512]; + int sp = 0, sc = 0; + + if (*root) + stack[sp++] = *root; + + while (sp > 0) { + DwindleNode *nd = stack[--sp]; + if (!nd->is_node) { + int found = 0; + if (nd->client == dragging) { + /* Treat the dragged client as "still present" so it + * isn't pruned out from under itself mid-gesture. */ + found = 1; + } else { + wl_list_for_each(c, &clients, link) { + if (c == nd->client + && VISIBLEON(c, m) + && !c->isfloating) { + found = 1; + break; + } + } + } + if (!found && sc < 512) + stale[sc++] = nd->client; + } else { + if (nd->children[1]) stack[sp++] = nd->children[1]; + if (nd->children[0]) stack[sp++] = nd->children[0]; + } + } + + for (int i = 0; i < sc; i++) + dwindle_remove(root, stale[i], NULL); /* TODO: wire to animation (soon tm) */ + } + + /* insert any newly visible client, splitting the focused node. + * Use m->dwindle_focus[ti] so it actually splits what the user + * was looking at when they spawned the window, rather than whatever + * focustop() happens to return */ + + Client *focused = m->dwindle_focus[ti]; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen || c == dragging) + continue; + if (!dwindle_find_leaf(*root, c)) { + dwindle_insert(root, c, focused, NULL); /* TODO: wire to animation once that lands */ + focused = c; + } + } + + /* assign root box and recurse */ + if (*root) { + (*root)->box = (struct wlr_box){ + m->w.x + e, + m->w.y + e, + MAX(1, m->w.width - 2 * e), + MAX(1, m->w.height - 2 * e), + }; + dwindle_recalc(*root, e); + } +} + +void +togglefloating(const Arg *arg) +{ + Client *sel = focustop(selmon); + /* return if fullscreen */ + if (sel && !sel->isfullscreen) + setfloating(sel, !sel->isfloating); +} + +void +togglefullscreen(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setfullscreen(sel, !sel->isfullscreen); +} + +void +togglegaps(const Arg *arg) +{ + selmon->gaps = !selmon->gaps; + arrange(selmon); +} + +/* swapdir is essentially focusdir but swap */ + +void +swapdir(const Arg *arg) +{ + Client *c, *other = NULL; + Client *sel = focustop(selmon); + int sx, sy, cx, cy; + double bestdist = 1e18, dist; + + if (!sel || sel->isfloating || sel->isfullscreen) + return; + + client_center(sel, &sx, &sy); + + wl_list_for_each(c, &clients, link) { + if (c == sel || !VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen) + continue; + + client_center(c, &cx, &cy); + + switch (arg->i) { + case WLR_DIRECTION_LEFT: if (cx >= sx) continue; break; + case WLR_DIRECTION_RIGHT: if (cx <= sx) continue; break; + case WLR_DIRECTION_UP: if (cy >= sy) continue; break; + case WLR_DIRECTION_DOWN: if (cy <= sy) continue; break; + default: return; + } + + dist = (double)(cx - sx) * (cx - sx) + (double)(cy - sy) * (cy - sy); + if (dist < bestdist) { + bestdist = dist; + other = c; + } + } + + if (!other) + return; + + if (selmon->lt[selmon->sellt]->arrange == dwindle) { + int ti = current_tag_idx(selmon); + DwindleNode **root = &selmon->dwindle_root[ti]; + if (*root) { + DwindleNode *leaf_sel = dwindle_find_leaf(*root, sel); + DwindleNode *leaf_other = dwindle_find_leaf(*root, other); + if (leaf_sel && leaf_other) { + leaf_sel->client = other; + leaf_other->client = sel; + int e = (selmon->gaps && cfg.appearance.gaps) ? (int)cfg.appearance.gaps : 0; + (*root)->box = (struct wlr_box){ + selmon->w.x + e, + selmon->w.y + e, + MAX(1, selmon->w.width - 2 * e), + MAX(1, selmon->w.height - 2 * e), + }; + dwindle_recalc(*root, e); + } + } + } else { + struct wlr_box tmp = sel->geom; + resize(sel, other->geom, 0); + resize(other, tmp, 0); + } + + focusclient(sel, 1); + printstatus(); +} + +void +swaptiled(Client *a, Client *b) +{ + Monitor *m; + if (!a || !b || a == b) + return; + + wl_list_for_each(m, &mons, link) { + for (int i = 0; i < TAGCOUNT; i++) { + DwindleNode **root = &m->dwindle_root[i]; + if (!*root) + continue; + DwindleNode *la = dwindle_find_leaf(*root, a); + DwindleNode *lb = dwindle_find_leaf(*root, b); + if (!la || !lb) + continue; + la->client = b; + lb->client = a; + int e = (m->gaps && cfg.appearance.gaps) ? (int)cfg.appearance.gaps : 0; + (*root)->box = (struct wlr_box){ + m->w.x + e, + m->w.y + e, + MAX(1, m->w.width - 2 * e), + MAX(1, m->w.height - 2 * e), + }; + dwindle_recalc(*root, e); + } + } + + focusclient(a, 1); + printstatus(); +} + +void +dwindle_move_to_edge(Client *a, Client *b, DwindleEdge edge) +{ + if (!a || !b || a == b || edge == EDGE_NONE) + return; + + DwindleNode **root = &b->mon->dwindle_root[current_tag_idx(b->mon)]; + if (!dwindle_find_leaf(*root, a) || !dwindle_find_leaf(*root, b)) + return; + + dwindle_remove(root, a, NULL); + if (!dwindle_insert_at_edge(root, a, b, edge)) + return; /* shouldn't happen (b was just confirmed present above) */ + + int e = (b->mon->gaps && cfg.appearance.gaps) ? (int)cfg.appearance.gaps : 0; + (*root)->box = (struct wlr_box){ + b->mon->w.x + e, + b->mon->w.y + e, + MAX(1, b->mon->w.width - 2 * e), + MAX(1, b->mon->w.height - 2 * e), + }; + dwindle_recalc(*root, e); + + focusclient(a, 1); + printstatus(); +} + +void +toggletag(const Arg *arg) +{ + uint32_t newtags; + Client *sel = focustop(selmon); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + + dwindle_remove_client(sel); + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +} + +void +toggleview(const Arg *arg) +{ + uint32_t newtagset; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; + + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +} + +void +unlocksession(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, unlock); + destroylock(lock, 1); +} + +void +unmaplayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, unmap); + + l->mapped = 0; + wlr_scene_node_set_enabled(&l->scene->node, 0); + if (l == exclusive_focus) + exclusive_focus = NULL; + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) + arrangelayers(l->mon); + if (l->layer_surface->surface == seat->keyboard_state.focused_surface) + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +unmapnotify(struct wl_listener *listener, void *data) +{ + /* Called when the surface is unmapped, and should no longer be shown. */ + Client *c = wl_container_of(listener, c, unmap); + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; + } + + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) { + exclusive_focus = NULL; + focusclient(focustop(selmon), 1); + } + } else { + wl_list_remove(&c->link); + dwindle_remove_client(c); + setmon(c, NULL, 0); + wl_list_remove(&c->flink); + } + + wlr_scene_node_destroy(&c->scene->node); + printstatus(); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +updatemons(struct wl_listener *listener, void *data) +{ + /* + * Called whenever the output layout changes: adding or removing a + * monitor, changing an output's mode or position, etc. This is where + * the change officially happens and we update geometry, window + * positions, focus, and the stored configuration in wlroots' + * output-manager implementation. + */ + struct wlr_output_configuration_v1 *config + = wlr_output_configuration_v1_create(); + Client *c; + struct wlr_output_configuration_head_v1 *config_head; + Monitor *m; + + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled || m->asleep) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head->state.enabled = 0; + /* Remove this output from the layout to avoid cursor enter inside it */ + wlr_output_layout_remove(output_layout, m->wlr_output); + closemon(m); + m->m = m->w = (struct wlr_box){0}; + } + /* Insert outputs that need to */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled + && !wlr_output_layout_get(output_layout, m->wlr_output)) + wlr_output_layout_add_auto(output_layout, m->wlr_output); + } + + /* Now that we update the output layout we can get its box */ + wlr_output_layout_get_box(output_layout, NULL, &sgeom); + + wlr_scene_node_set_position(&root_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(root_bg, sgeom.width, sgeom.height); + + /* Make sure the clients are hidden when dwl is locked */ + wlr_scene_node_set_position(&locked_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(locked_bg, sgeom.width, sgeom.height); + + wl_list_for_each(m, &mons, link) { + if (!m->wlr_output->enabled) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + + /* Get the effective monitor geometry to use for surfaces */ + wlr_output_layout_get_box(output_layout, m->wlr_output, &m->m); + m->w = m->m; + wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); + + wlr_scene_node_set_position(&m->fullscreen_bg->node, m->m.x, m->m.y); + wlr_scene_rect_set_size(m->fullscreen_bg, m->m.width, m->m.height); + + if (m->lock_surface) { + struct wlr_scene_tree *scene_tree = m->lock_surface->surface->data; + wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); + wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, m->m.height); + } + + /* Calculate the effective monitor geometry to use for clients */ + arrangelayers(m); + /* Don't move clients to the left output when plugging monitors */ + arrange(m); + /* make sure fullscreen clients have the right size */ + if ((c = focustop(m)) && c->isfullscreen) + resize(c, m->m, 0); + + /* Try to re-set the gamma LUT when updating monitors, + * it's only really needed when enabling a disabled output, but meh. */ + m->gamma_lut_changed = 1; + + config_head->state.x = m->m.x; + config_head->state.y = m->m.y; + + if (!selmon) { + selmon = m; + } + } + + if (selmon && selmon->wlr_output->enabled) { + wl_list_for_each(c, &clients, link) { + if (!c->mon && client_surface(c)->mapped) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); + if (selmon->lock_surface) { + client_notify_enter(selmon->lock_surface->surface, + wlr_seat_get_keyboard(seat)); + client_activate_surface(selmon->lock_surface->surface, 1); + } + } + + wlr_cursor_move(cursor, NULL, 0, 0); + + wlr_output_manager_v1_set_configuration(output_mgr, config); +} + +void +updatetitle(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + printstatus(); +} + +void +urgent(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_activation_v1_request_activate_event *event = data; + Client *c = NULL; + toplevel_from_wlr_surface(event->surface, &c, NULL); + if (!c || c == focustop(selmon)) + return; + + c->isurgent = 1; + printstatus(); + + if (client_surface(c)->mapped) + client_set_border_color(c, cfg.appearance.inner_urgent_color, cfg.appearance.outer_urgent_color); +} + +void +view(const Arg *arg) +{ + if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +} + +void +virtualkeyboard(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_keyboard_v1 *kb = data; + /* virtual keyboards shouldn't share keyboard group */ + KeyboardGroup *group = createkeyboardgroup(); + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(&kb->keyboard, group->wlr_group->keyboard.keymap); + LISTEN(&kb->keyboard.base.events.destroy, &group->destroy, destroykeyboardgroup); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(group->wlr_group, &kb->keyboard); +} + +void +virtualpointer(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_pointer_v1_new_pointer_event *event = data; + struct wlr_input_device *device = &event->new_pointer->pointer.base; + + wlr_cursor_attach_input_device(cursor, device); + if (event->suggested_output) + wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); +} + +Monitor * +xytomon(double x, double y) +{ + struct wlr_output *o = wlr_output_layout_output_at(output_layout, x, y); + return o ? o->data : NULL; +} + +static Client * +tiled_client_at(double x, double y, Client *exclude) +{ + Client *c; + wl_list_for_each(c, &clients, link) { + if (c == exclude || c->isfloating || c->isfullscreen) + continue; + if (!c->mon || !VISIBLEON(c, c->mon)) + continue; + if (c->mon->lt[c->mon->sellt]->arrange != dwindle) + continue; + if (x >= c->geom.x && x < c->geom.x + c->geom.width + && y >= c->geom.y && y < c->geom.y + c->geom.height) + return c; + } + return NULL; +} + +void +xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny) +{ + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; + + for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) { + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + + if (node->type == WLR_SCENE_NODE_BUFFER) + surface = wlr_scene_surface_try_from_buffer( + wlr_scene_buffer_from_node(node))->surface; + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; + if (c && c->type == LayerShell) { + c = NULL; + l = pnode->data; + } + } + + if (psurface) *psurface = surface; + if (pc) *pc = c; + if (pl) *pl = l; +} + + +#ifdef XWAYLAND +void +activatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, activate); + + /* Only "managed" windows can be activated */ + if (!client_is_unmanaged(c)) + wlr_xwayland_surface_activate(c->surface.xwayland, 1); +} + +void +associatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, associate); + + LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); + LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); +} + +void +configurex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, configure); + struct wlr_xwayland_surface_configure_event *event = data; + if (!client_surface(c) || !client_surface(c)->mapped) { + wlr_xwayland_surface_configure(c->surface.xwayland, + event->x, event->y, event->width, event->height); + return; + } + if (client_is_unmanaged(c)) { + wlr_scene_node_set_position(&c->scene->node, event->x, event->y); + wlr_xwayland_surface_configure(c->surface.xwayland, + event->x, event->y, event->width, event->height); + return; + } + if ((c->isfloating && c != grabc) || !c->mon->lt[c->mon->sellt]->arrange) { + resize(c, (struct wlr_box){.x = event->x - c->bw, + .y = event->y - c->bw, .width = event->width + c->bw * 2, + .height = event->height + c->bw * 2}, 0); + } else { + arrange(c->mon); + } +} + +void +createnotifyx11(struct wl_listener *listener, void *data) +{ + struct wlr_xwayland_surface *xsurface = data; + Client *c; + + /* Allocate a Client for this surface */ + c = xsurface->data = ecalloc(1, sizeof(*c)); + c->surface.xwayland = xsurface; + c->type = X11; + if (client_is_unmanaged(c)) { + c->ibw = c->obw = c->bw = 0; + } else { + c->ibw = cfg.appearance.inner_border_px; + c->obw = cfg.appearance.outer_border_px; + c->bw = c->ibw + c->obw; + } + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); + LISTEN(&xsurface->events.destroy, &c->destroy, destroynotify); + LISTEN(&xsurface->events.dissociate, &c->dissociate, dissociatex11); + LISTEN(&xsurface->events.request_activate, &c->activate, activatex11); + LISTEN(&xsurface->events.request_configure, &c->configure, configurex11); + LISTEN(&xsurface->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&xsurface->events.set_hints, &c->set_hints, sethints); + LISTEN(&xsurface->events.set_title, &c->set_title, updatetitle); +} + +void +dissociatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, dissociate); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); +} + +void +sethints(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_hints); + struct wlr_surface *surface = client_surface(c); + if (c == focustop(selmon) || !c->surface.xwayland->hints) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); + printstatus(); + + if (c->isurgent && surface && surface->mapped) + client_set_border_color(c, cfg.appearance.inner_urgent_color, cfg.appearance.outer_urgent_color); +} + +void +xwaylandready(struct wl_listener *listener, void *data) +{ + struct wlr_xcursor *xcursor; + struct wlr_xcursor_image *image; + + /* assign the one and only seat */ + wlr_xwayland_set_seat(xwayland, seat); + + /* Set the default XWayland cursor to match the rest of swindle. */ + if ((xcursor = wlr_xcursor_manager_get_xcursor(cursor_mgr, "default", 1))) { + image = xcursor->images[0]; + wlr_xwayland_set_cursor(xwayland, + wlr_xcursor_image_get_buffer(image), + image->hotspot_x, image->hotspot_y); + } +} +#endif + +int +main(int argc, char *argv[]) +{ + char *startup_cmd = NULL; + int c; + + while ((c = getopt(argc, argv, "c:s:hdv")) != -1) { + switch (c) { + case 'c': + { + struct stat st; + if (stat(optarg, &st) < 0) { + if (errno == ENOENT) + die("error: config file '%s' does not exist", optarg); + die("error: config file '%s': %s", optarg, strerror(errno)); + } + if (S_ISDIR(st.st_mode)) + die("error: config file '%s' is a directory, expected a regular file", optarg); + if (access(optarg, R_OK) < 0) + die("error: config file '%s' is not readable (permission denied)", optarg); + custom_cfg_path = optarg; + break; + } + case 's': + startup_cmd = optarg; + break; + case 'd': + cfg.log_level = WLR_DEBUG; /* override before setup() */ + break; + case 'v': + die("swindle " VERSION); + default: + goto usage; + } + } + if (optind < argc) + goto usage; + + /* Wayland requires XDG_RUNTIME_DIR for creating its communications socket */ + if (!getenv("XDG_RUNTIME_DIR")) + die("XDG_RUNTIME_DIR must be set"); + setup(); + run(startup_cmd); + cleanup(); + return EXIT_SUCCESS; + +usage: + die("Usage: %s [-v] [-d] [-s startup command] [-c config file]", argv[0]); +} + diff -r 733566def44f -r 50bab568625d src/util.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/util.c Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,51 @@ +/* See LICENSE.dwm file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "util.h" + +void +die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} + +int +fd_set_nonblock(int fd) { + int flags = fcntl(fd, F_GETFL); + if (flags < 0) { + perror("fcntl(F_GETFL):"); + return -1; + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + perror("fcntl(F_SETFL):"); + return -1; + } + + return 0; +} diff -r 733566def44f -r 50bab568625d src/util.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/util.h Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,5 @@ +/* See LICENSE.dwm file for copyright and license details. */ + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); +int fd_set_nonblock(int fd); diff -r 733566def44f -r 50bab568625d src/wlr_ext_workspace_v1.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/wlr_ext_workspace_v1.c Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,975 @@ +#include "wlr_ext_workspace_v1.h" +#include "ext-workspace-v1-protocol.h" +#include +#include +#include +#include + +#define EXT_WORKSPACE_V1_VERSION 1 + +struct wlr_ext_workspace_v1_group_output { + struct wlr_output *output; + struct wlr_ext_workspace_group_handle_v1 *group; + struct wl_listener output_bind; + struct wl_listener output_destroy; + struct wl_list link; +}; + +// These structs wrap wl_resource of each interface to access the request queue +// (wlr_ext_workspace_manager_v1_resource.requests) assigned per manager +// resource + +struct wlr_ext_workspace_manager_v1_resource { + struct wl_resource *resource; + struct wlr_ext_workspace_manager_v1 *manager; + struct wl_list requests; // wlr_ext_workspace_v1_request.link + struct wl_list workspace_resources; // wlr_ext_workspace_v1_resource.link + struct wl_list group_resources; // wlr_ext_workspace_group_v1_resource.link + struct wl_list link; // wlr_ext_workspace_manager_v1.resources +}; + +struct wlr_ext_workspace_group_v1_resource { + struct wl_resource *resource; + struct wlr_ext_workspace_group_handle_v1 *group; + struct wlr_ext_workspace_manager_v1_resource *manager; + struct wl_list link; // wlr_ext_workspace_group_v1.resources + struct wl_list + manager_resource_link; // wlr_ext_workspace_manager_v1_resource.group_resources +}; + +struct wlr_ext_workspace_v1_resource { + struct wl_resource *resource; + struct wlr_ext_workspace_handle_v1 *workspace; + struct wlr_ext_workspace_manager_v1_resource *manager; + struct wl_list link; // wlr_ext_workspace_v1.resources + struct wl_list + manager_resource_link; // wlr_ext_workspace_manager_v1_resource.workspace_resources +}; + +static const struct ext_workspace_group_handle_v1_interface group_impl; + +static struct wlr_ext_workspace_group_v1_resource * +group_resource_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of( + resource, &ext_workspace_group_handle_v1_interface, &group_impl)); + return wl_resource_get_user_data(resource); +} + +static const struct ext_workspace_handle_v1_interface workspace_impl; + +static struct wlr_ext_workspace_v1_resource * +workspace_resource_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &ext_workspace_handle_v1_interface, + &workspace_impl)); + return wl_resource_get_user_data(resource); +} + +static const struct ext_workspace_manager_v1_interface manager_impl; + +static struct wlr_ext_workspace_manager_v1_resource * +manager_resource_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of( + resource, &ext_workspace_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void workspace_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void workspace_handle_activate(struct wl_client *client, + struct wl_resource *workspace_resource) { + struct wlr_ext_workspace_v1_resource *workspace_res = + workspace_resource_from_resource(workspace_resource); + if (!workspace_res) { + return; + } + + struct wlr_ext_workspace_v1_request *req = calloc(1, sizeof(*req)); + if (!req) { + wl_resource_post_no_memory(workspace_resource); + return; + } + req->type = WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE; + req->activate.workspace = workspace_res->workspace; + wl_list_insert(workspace_res->manager->requests.prev, &req->link); +} + +static void +workspace_handle_deactivate(struct wl_client *client, + struct wl_resource *workspace_resource) { + struct wlr_ext_workspace_v1_resource *workspace_res = + workspace_resource_from_resource(workspace_resource); + if (!workspace_res) { + return; + } + + struct wlr_ext_workspace_v1_request *req = calloc(1, sizeof(*req)); + if (!req) { + wl_resource_post_no_memory(workspace_resource); + return; + } + req->type = WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE; + req->deactivate.workspace = workspace_res->workspace; + wl_list_insert(workspace_res->manager->requests.prev, &req->link); +} + +static void workspace_handle_assign(struct wl_client *client, + struct wl_resource *workspace_resource, + struct wl_resource *group_resource) { + struct wlr_ext_workspace_v1_resource *workspace_res = + workspace_resource_from_resource(workspace_resource); + struct wlr_ext_workspace_group_v1_resource *group_res = + group_resource_from_resource(group_resource); + if (!workspace_res || !group_res) { + return; + } + + struct wlr_ext_workspace_v1_request *req = calloc(1, sizeof(*req)); + if (!req) { + wl_resource_post_no_memory(workspace_resource); + return; + } + req->type = WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN; + req->assign.group = group_res->group; + req->assign.workspace = workspace_res->workspace; + wl_list_insert(workspace_res->manager->requests.prev, &req->link); +} + +static void workspace_handle_remove(struct wl_client *client, + struct wl_resource *workspace_resource) { + struct wlr_ext_workspace_v1_resource *workspace_res = + workspace_resource_from_resource(workspace_resource); + if (!workspace_res) { + return; + } + + struct wlr_ext_workspace_v1_request *req = calloc(1, sizeof(*req)); + if (!req) { + wl_resource_post_no_memory(workspace_resource); + return; + } + req->type = WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE; + req->remove.workspace = workspace_res->workspace; + wl_list_insert(workspace_res->manager->requests.prev, &req->link); +} + +static const struct ext_workspace_handle_v1_interface workspace_impl = { + .destroy = workspace_handle_destroy, + .activate = workspace_handle_activate, + .deactivate = workspace_handle_deactivate, + .assign = workspace_handle_assign, + .remove = workspace_handle_remove, +}; + +static void group_handle_create_workspace(struct wl_client *client, + struct wl_resource *group_resource, + const char *name) { + struct wlr_ext_workspace_group_v1_resource *group_res = + group_resource_from_resource(group_resource); + if (!group_res) { + return; + } + + struct wlr_ext_workspace_v1_request *req = calloc(1, sizeof(*req)); + if (!req) { + wl_resource_post_no_memory(group_resource); + return; + } + req->create_workspace.name = strdup(name); + if (!req->create_workspace.name) { + free(req); + wl_resource_post_no_memory(group_resource); + return; + } + req->type = WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE; + req->create_workspace.group = group_res->group; + wl_list_insert(group_res->manager->requests.prev, &req->link); +} + +static void group_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct ext_workspace_group_handle_v1_interface group_impl = { + .create_workspace = group_handle_create_workspace, + .destroy = group_handle_destroy, +}; + +static void destroy_workspace_resource( + struct wlr_ext_workspace_v1_resource *workspace_res) { + wl_list_remove(&workspace_res->link); + wl_list_remove(&workspace_res->manager_resource_link); + wl_resource_set_user_data(workspace_res->resource, NULL); + free(workspace_res); +} + +static void workspace_resource_destroy(struct wl_resource *resource) { + struct wlr_ext_workspace_v1_resource *workspace_res = + workspace_resource_from_resource(resource); + if (workspace_res) { + destroy_workspace_resource(workspace_res); + } +} + +static struct wlr_ext_workspace_v1_resource *create_workspace_resource( + struct wlr_ext_workspace_handle_v1 *workspace, + struct wlr_ext_workspace_manager_v1_resource *manager_res) { + struct wlr_ext_workspace_v1_resource *workspace_res = + calloc(1, sizeof(*workspace_res)); + if (!workspace_res) { + return NULL; + } + + struct wl_client *client = wl_resource_get_client(manager_res->resource); + workspace_res->resource = + wl_resource_create(client, &ext_workspace_handle_v1_interface, + wl_resource_get_version(manager_res->resource), 0); + if (!workspace_res->resource) { + free(workspace_res); + return NULL; + } + wl_resource_set_implementation(workspace_res->resource, &workspace_impl, + workspace_res, workspace_resource_destroy); + + workspace_res->workspace = workspace; + workspace_res->manager = manager_res; + wl_list_insert(&workspace->resources, &workspace_res->link); + wl_list_insert(&manager_res->workspace_resources, + &workspace_res->manager_resource_link); + + return workspace_res; +} + +static void +destroy_group_resource(struct wlr_ext_workspace_group_v1_resource *group_res) { + wl_list_remove(&group_res->link); + wl_list_remove(&group_res->manager_resource_link); + wl_resource_set_user_data(group_res->resource, NULL); + free(group_res); +} + +static void group_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_ext_workspace_group_v1_resource *group_res = + group_resource_from_resource(resource); + if (group_res) { + destroy_group_resource(group_res); + } +} + +static struct wlr_ext_workspace_group_v1_resource *create_group_resource( + struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_ext_workspace_manager_v1_resource *manager_res) { + struct wlr_ext_workspace_group_v1_resource *group_res = + calloc(1, sizeof(*group_res)); + if (!group_res) { + return NULL; + } + + struct wl_client *client = wl_resource_get_client(manager_res->resource); + uint32_t version = wl_resource_get_version(manager_res->resource); + group_res->resource = wl_resource_create( + client, &ext_workspace_group_handle_v1_interface, version, 0); + if (group_res->resource == NULL) { + free(group_res); + return NULL; + } + wl_resource_set_implementation(group_res->resource, &group_impl, group_res, + group_handle_resource_destroy); + + group_res->group = group; + group_res->manager = manager_res; + wl_list_insert(&group->resources, &group_res->link); + wl_list_insert(&manager_res->group_resources, + &group_res->manager_resource_link); + + return group_res; +} + +static void +destroy_requests(struct wlr_ext_workspace_manager_v1_resource *manager_res) { + struct wlr_ext_workspace_v1_request *req, *tmp; + wl_list_for_each_safe(req, tmp, &manager_res->requests, link) { + if (req->type == WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE) { + free(req->create_workspace.name); + } + wl_list_remove(&req->link); + free(req); + } +} + +static void +clear_requests_by(struct wlr_ext_workspace_manager_v1_resource *manager_res, + struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_ext_workspace_handle_v1 *workspace) { + struct wlr_ext_workspace_v1_request *req, *tmp; + wl_list_for_each_safe(req, tmp, &manager_res->requests, link) { + switch (req->type) { + case WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE: + if (group && req->create_workspace.group == group) { + req->create_workspace.group = NULL; + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE: + if (workspace && req->activate.workspace == workspace) { + req->activate.workspace = NULL; + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE: + if (workspace && req->deactivate.workspace == workspace) { + req->deactivate.workspace = NULL; + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN: + if (workspace && req->assign.workspace == workspace) { + req->assign.workspace = NULL; + } + if (group && req->assign.group == group) { + req->assign.group = NULL; + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE: + if (workspace && req->remove.workspace == workspace) { + req->remove.workspace = NULL; + } + break; + } + } +} + +static void manager_handle_commit(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_ext_workspace_manager_v1_resource *manager_res = + manager_resource_from_resource(resource); + if (!manager_res) { + return; + } + + struct wlr_ext_workspace_v1_commit_event commit_event = { + .requests = &manager_res->requests, + }; + wl_signal_emit_mutable(&manager_res->manager->events.commit, &commit_event); + destroy_requests(manager_res); +} + +static void handle_idle(void *data) { + struct wlr_ext_workspace_manager_v1 *manager = data; + + struct wlr_ext_workspace_manager_v1_resource *manager_res; + wl_list_for_each(manager_res, &manager->resources, link) { + ext_workspace_manager_v1_send_done(manager_res->resource); + } + manager->idle_source = NULL; +} + +static void +manager_schedule_done(struct wlr_ext_workspace_manager_v1 *manager) { + if (!manager->idle_source) { + manager->idle_source = + wl_event_loop_add_idle(manager->event_loop, handle_idle, manager); + } +} + +static void +workspace_send_details(struct wlr_ext_workspace_v1_resource *workspace_res) { + struct wlr_ext_workspace_handle_v1 *workspace = workspace_res->workspace; + struct wl_resource *resource = workspace_res->resource; + + ext_workspace_handle_v1_send_capabilities(resource, workspace->caps); + if (workspace->coordinates.size > 0) { + ext_workspace_handle_v1_send_coordinates(resource, + &workspace->coordinates); + } + if (workspace->name) { + ext_workspace_handle_v1_send_name(resource, workspace->name); + } + if (workspace->id) { + ext_workspace_handle_v1_send_id(resource, workspace->id); + } + ext_workspace_handle_v1_send_state(resource, workspace->state); + manager_schedule_done(workspace->manager); +} + +static void manager_handle_stop(struct wl_client *client, + struct wl_resource *resource) { + ext_workspace_manager_v1_send_finished(resource); + wl_resource_destroy(resource); +} + +static const struct ext_workspace_manager_v1_interface manager_impl = { + .commit = manager_handle_commit, + .stop = manager_handle_stop, +}; + +static void destroy_manager_resource( + struct wlr_ext_workspace_manager_v1_resource *manager_res) { + destroy_requests(manager_res); + + struct wlr_ext_workspace_v1_resource *workspace_res, *tmp2; + wl_list_for_each_safe(workspace_res, tmp2, + &manager_res->workspace_resources, + manager_resource_link) { + destroy_workspace_resource(workspace_res); + } + struct wlr_ext_workspace_group_v1_resource *group_res, *tmp3; + wl_list_for_each_safe(group_res, tmp3, &manager_res->group_resources, + manager_resource_link) { + destroy_group_resource(group_res); + } + + wl_list_remove(&manager_res->link); + wl_resource_set_user_data(manager_res->resource, NULL); + free(manager_res); +} + +static void manager_resource_destroy(struct wl_resource *resource) { + struct wlr_ext_workspace_manager_v1_resource *manager_res = + manager_resource_from_resource(resource); + if (manager_res) { + destroy_manager_resource(manager_res); + } +} + +static void +group_send_details(struct wlr_ext_workspace_group_v1_resource *group_res) { + struct wlr_ext_workspace_group_handle_v1 *group = group_res->group; + struct wl_resource *resource = group_res->resource; + struct wl_client *client = wl_resource_get_client(resource); + + ext_workspace_group_handle_v1_send_capabilities(resource, group->caps); + + struct wlr_ext_workspace_v1_group_output *group_output; + wl_list_for_each(group_output, &group->outputs, link) { + struct wl_resource *output_resource; + wl_resource_for_each(output_resource, + &group_output->output->resources) { + if (wl_resource_get_client(output_resource) == client) { + ext_workspace_group_handle_v1_send_output_enter( + resource, output_resource); + } + } + } + + manager_schedule_done(group->manager); +} + +static void manager_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_ext_workspace_manager_v1 *manager = data; + + struct wlr_ext_workspace_manager_v1_resource *manager_res = + calloc(1, sizeof(*manager_res)); + if (!manager_res) { + wl_client_post_no_memory(client); + return; + } + + manager_res->manager = manager; + wl_list_init(&manager_res->requests); + wl_list_init(&manager_res->workspace_resources); + wl_list_init(&manager_res->group_resources); + + manager_res->resource = wl_resource_create( + client, &ext_workspace_manager_v1_interface, version, id); + if (!manager_res->resource) { + free(manager_res); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(manager_res->resource, &manager_impl, + manager_res, manager_resource_destroy); + wl_list_insert(&manager->resources, &manager_res->link); + + struct wlr_ext_workspace_group_handle_v1 *group; + wl_list_for_each(group, &manager->groups, link) { + struct wlr_ext_workspace_group_v1_resource *group_res = + create_group_resource(group, manager_res); + if (!group_res) { + wl_resource_post_no_memory(manager_res->resource); + continue; + } + ext_workspace_manager_v1_send_workspace_group(manager_res->resource, + group_res->resource); + group_send_details(group_res); + } + + struct wlr_ext_workspace_handle_v1 *workspace; + wl_list_for_each(workspace, &manager->workspaces, link) { + struct wlr_ext_workspace_v1_resource *workspace_res = + create_workspace_resource(workspace, manager_res); + if (!workspace_res) { + wl_resource_post_no_memory(manager_res->resource); + continue; + } + ext_workspace_manager_v1_send_workspace(manager_res->resource, + workspace_res->resource); + workspace_send_details(workspace_res); + + if (!workspace->group) { + continue; + } + struct wlr_ext_workspace_group_v1_resource *group_res; + wl_list_for_each(group_res, &workspace->group->resources, link) { + if (group_res->manager == manager_res) { + ext_workspace_group_handle_v1_send_workspace_enter( + group_res->resource, workspace_res->resource); + } + } + } + + ext_workspace_manager_v1_send_done(manager_res->resource); +} + +static void manager_handle_display_destroy(struct wl_listener *listener, + void *data) { + struct wlr_ext_workspace_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.commit.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + struct wlr_ext_workspace_group_handle_v1 *group, *tmp; + wl_list_for_each_safe(group, tmp, &manager->groups, link) { + wlr_ext_workspace_group_handle_v1_destroy(group); + } + + struct wlr_ext_workspace_handle_v1 *workspace, *tmp2; + wl_list_for_each_safe(workspace, tmp2, &manager->workspaces, link) { + wlr_ext_workspace_handle_v1_destroy(workspace); + } + + struct wlr_ext_workspace_manager_v1_resource *manager_res, *tmp3; + wl_list_for_each_safe(manager_res, tmp3, &manager->resources, link) { + destroy_manager_resource(manager_res); + } + + if (manager->idle_source) { + wl_event_source_remove(manager->idle_source); + } + + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_ext_workspace_manager_v1 * +wlr_ext_workspace_manager_v1_create(struct wl_display *display, + uint32_t version) { + assert(version <= EXT_WORKSPACE_V1_VERSION); + + struct wlr_ext_workspace_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + return NULL; + } + + manager->global = + wl_global_create(display, &ext_workspace_manager_v1_interface, version, + manager, manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + manager->event_loop = wl_display_get_event_loop(display); + + manager->display_destroy.notify = manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + wl_list_init(&manager->groups); + wl_list_init(&manager->workspaces); + wl_list_init(&manager->resources); + wl_signal_init(&manager->events.commit); + wl_signal_init(&manager->events.destroy); + + return manager; +} + +struct wlr_ext_workspace_group_handle_v1 * +wlr_ext_workspace_group_handle_v1_create( + struct wlr_ext_workspace_manager_v1 *manager, uint32_t caps) { + struct wlr_ext_workspace_group_handle_v1 *group = calloc(1, sizeof(*group)); + if (!group) { + return NULL; + } + + group->manager = manager; + group->caps = caps; + + wl_list_init(&group->outputs); + wl_list_init(&group->resources); + wl_signal_init(&group->events.destroy); + + wl_list_insert(manager->groups.prev, &group->link); + + struct wlr_ext_workspace_manager_v1_resource *manager_res; + wl_list_for_each(manager_res, &manager->resources, link) { + struct wlr_ext_workspace_group_v1_resource *group_res = + create_group_resource(group, manager_res); + if (!group_res) { + continue; + } + ext_workspace_manager_v1_send_workspace_group(manager_res->resource, + group_res->resource); + group_send_details(group_res); + } + + manager_schedule_done(manager); + + return group; +} + +static void +workspace_send_group(struct wlr_ext_workspace_handle_v1 *workspace, + struct wlr_ext_workspace_group_handle_v1 *group, + bool enter) { + + struct wlr_ext_workspace_v1_resource *workspace_res; + wl_list_for_each(workspace_res, &workspace->resources, link) { + struct wlr_ext_workspace_group_v1_resource *group_res; + wl_list_for_each(group_res, &group->resources, link) { + if (group_res->manager != workspace_res->manager) { + continue; + } + if (enter) { + ext_workspace_group_handle_v1_send_workspace_enter( + group_res->resource, workspace_res->resource); + } else { + ext_workspace_group_handle_v1_send_workspace_leave( + group_res->resource, workspace_res->resource); + } + } + } + + manager_schedule_done(workspace->manager); +} + +static void +destroy_group_output(struct wlr_ext_workspace_v1_group_output *group_output) { + wl_list_remove(&group_output->output_bind.link); + wl_list_remove(&group_output->output_destroy.link); + wl_list_remove(&group_output->link); + free(group_output); +} + +static void group_send_output(struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_output *output, bool enter) { + + struct wlr_ext_workspace_group_v1_resource *group_res; + wl_list_for_each(group_res, &group->resources, link) { + struct wl_client *client = wl_resource_get_client(group_res->resource); + + struct wl_resource *output_resource; + wl_resource_for_each(output_resource, &output->resources) { + if (wl_resource_get_client(output_resource) != client) { + continue; + } + if (enter) { + ext_workspace_group_handle_v1_send_output_enter( + group_res->resource, output_resource); + } else { + ext_workspace_group_handle_v1_send_output_leave( + group_res->resource, output_resource); + } + } + } + + manager_schedule_done(group->manager); +} + +void wlr_ext_workspace_group_handle_v1_destroy( + struct wlr_ext_workspace_group_handle_v1 *group) { + if (!group) { + return; + } + + wl_signal_emit_mutable(&group->events.destroy, NULL); + + assert(wl_list_empty(&group->events.destroy.listener_list)); + + struct wlr_ext_workspace_handle_v1 *workspace; + wl_list_for_each(workspace, &group->manager->workspaces, link) { + if (workspace->group == group) { + workspace_send_group(workspace, group, false); + workspace->group = NULL; + } + } + + struct wlr_ext_workspace_group_v1_resource *group_res, *tmp; + wl_list_for_each_safe(group_res, tmp, &group->resources, link) { + ext_workspace_group_handle_v1_send_removed(group_res->resource); + destroy_group_resource(group_res); + } + + struct wlr_ext_workspace_manager_v1_resource *manager_res; + wl_list_for_each(manager_res, &group->manager->resources, link) { + clear_requests_by(manager_res, group, NULL); + } + + struct wlr_ext_workspace_v1_group_output *group_output, *tmp3; + wl_list_for_each_safe(group_output, tmp3, &group->outputs, link) { + group_send_output(group, group_output->output, false); + destroy_group_output(group_output); + } + + manager_schedule_done(group->manager); + + wl_list_remove(&group->link); + free(group); +} + +static void handle_output_bind(struct wl_listener *listener, void *data) { + struct wlr_ext_workspace_v1_group_output *group_output = + wl_container_of(listener, group_output, output_bind); + struct wlr_output_event_bind *event = data; + struct wl_client *client = wl_resource_get_client(event->resource); + + struct wlr_ext_workspace_group_v1_resource *group_res; + wl_list_for_each(group_res, &group_output->group->resources, link) { + if (wl_resource_get_client(group_res->resource) == client) { + ext_workspace_group_handle_v1_send_output_enter(group_res->resource, + event->resource); + } + } + + manager_schedule_done(group_output->group->manager); +} + +static void handle_output_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_workspace_v1_group_output *group_output = + wl_container_of(listener, group_output, output_destroy); + group_send_output(group_output->group, group_output->output, false); + destroy_group_output(group_output); +} + +static struct wlr_ext_workspace_v1_group_output * +get_group_output(struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_output *output) { + struct wlr_ext_workspace_v1_group_output *group_output; + wl_list_for_each(group_output, &group->outputs, link) { + if (group_output->output == output) { + return group_output; + } + } + return NULL; +} + +void wlr_ext_workspace_group_handle_v1_output_enter( + struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_output *output) { + if (get_group_output(group, output)) { + return; + } + struct wlr_ext_workspace_v1_group_output *group_output = + calloc(1, sizeof(*group_output)); + if (!group_output) { + return; + } + group_output->output = output; + group_output->group = group; + wl_list_insert(&group->outputs, &group_output->link); + + group_output->output_bind.notify = handle_output_bind; + wl_signal_add(&output->events.bind, &group_output->output_bind); + group_output->output_destroy.notify = handle_output_destroy; + wl_signal_add(&output->events.destroy, &group_output->output_destroy); + + group_send_output(group, output, true); +} + +void wlr_ext_workspace_group_handle_v1_output_leave( + struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_output *output) { + struct wlr_ext_workspace_v1_group_output *group_output = + get_group_output(group, output); + if (!group_output) { + return; + } + + group_send_output(group, output, false); + destroy_group_output(group_output); +} + +struct wlr_ext_workspace_handle_v1 * +wlr_ext_workspace_handle_v1_create(struct wlr_ext_workspace_manager_v1 *manager, + const char *id, uint32_t caps) { + struct wlr_ext_workspace_handle_v1 *workspace = + calloc(1, sizeof(*workspace)); + if (!workspace) { + return NULL; + } + + workspace->manager = manager; + workspace->caps = caps; + + if (id) { + workspace->id = strdup(id); + if (!workspace->id) { + free(workspace); + return NULL; + } + } + + wl_list_init(&workspace->resources); + wl_array_init(&workspace->coordinates); + wl_signal_init(&workspace->events.destroy); + + wl_list_insert(manager->workspaces.prev, &workspace->link); + + struct wlr_ext_workspace_manager_v1_resource *manager_res; + wl_list_for_each(manager_res, &manager->resources, link) { + struct wlr_ext_workspace_v1_resource *workspace_res = + create_workspace_resource(workspace, manager_res); + if (!workspace_res) { + continue; + } + ext_workspace_manager_v1_send_workspace(manager_res->resource, + workspace_res->resource); + workspace_send_details(workspace_res); + } + + manager_schedule_done(manager); + + return workspace; +} + +void wlr_ext_workspace_handle_v1_destroy( + struct wlr_ext_workspace_handle_v1 *workspace) { + if (!workspace) { + return; + } + + wl_signal_emit_mutable(&workspace->events.destroy, NULL); + + assert(wl_list_empty(&workspace->events.destroy.listener_list)); + + if (workspace->group) { + workspace_send_group(workspace, workspace->group, false); + } + + struct wlr_ext_workspace_v1_resource *workspace_res, *tmp; + wl_list_for_each_safe(workspace_res, tmp, &workspace->resources, link) { + ext_workspace_handle_v1_send_removed(workspace_res->resource); + destroy_workspace_resource(workspace_res); + } + + struct wlr_ext_workspace_manager_v1_resource *manager_res; + wl_list_for_each(manager_res, &workspace->manager->resources, link) { + clear_requests_by(manager_res, NULL, workspace); + } + + manager_schedule_done(workspace->manager); + + wl_list_remove(&workspace->link); + wl_array_release(&workspace->coordinates); + free(workspace->id); + free(workspace->name); + free(workspace); +} + +void wlr_ext_workspace_handle_v1_set_group( + struct wlr_ext_workspace_handle_v1 *workspace, + struct wlr_ext_workspace_group_handle_v1 *group) { + if (workspace->group == group) { + return; + } + + if (workspace->group) { + workspace_send_group(workspace, workspace->group, false); + } + workspace->group = group; + if (group) { + workspace_send_group(workspace, group, true); + } +} + +void wlr_ext_workspace_handle_v1_set_name( + struct wlr_ext_workspace_handle_v1 *workspace, const char *name) { + assert(name); + + if (workspace->name && strcmp(workspace->name, name) == 0) { + return; + } + + free(workspace->name); + workspace->name = strdup(name); + if (workspace->name == NULL) { + return; + } + + struct wlr_ext_workspace_v1_resource *workspace_res; + wl_list_for_each(workspace_res, &workspace->resources, link) { + ext_workspace_handle_v1_send_name(workspace_res->resource, + workspace->name); + } + + manager_schedule_done(workspace->manager); +} + +void wlr_ext_workspace_handle_v1_set_coordinates( + struct wlr_ext_workspace_handle_v1 *workspace, const uint32_t *coords, + size_t coords_len) { + size_t size = coords_len * sizeof(coords[0]); + if (size == workspace->coordinates.size && + (size == 0 || memcmp(workspace->coordinates.data, coords, size) == 0)) { + return; + } + + wl_array_release(&workspace->coordinates); + wl_array_init(&workspace->coordinates); + struct wl_array arr = { + .data = (void *)coords, + .size = size, + }; + wl_array_copy(&workspace->coordinates, &arr); + + struct wlr_ext_workspace_v1_resource *workspace_res; + wl_list_for_each(workspace_res, &workspace->resources, link) { + ext_workspace_handle_v1_send_coordinates(workspace_res->resource, + &workspace->coordinates); + } + + manager_schedule_done(workspace->manager); +} + +static void workspace_set_state(struct wlr_ext_workspace_handle_v1 *workspace, + enum ext_workspace_handle_v1_state state, + bool enabled) { + uint32_t old_state = workspace->state; + if (enabled) { + workspace->state |= state; + } else { + workspace->state &= ~state; + } + if (old_state == workspace->state) { + return; + } + + struct wlr_ext_workspace_v1_resource *workspace_res; + wl_list_for_each(workspace_res, &workspace->resources, link) { + ext_workspace_handle_v1_send_state(workspace_res->resource, + workspace->state); + } + + manager_schedule_done(workspace->manager); +} + +void wlr_ext_workspace_handle_v1_set_active( + struct wlr_ext_workspace_handle_v1 *workspace, bool enabled) { + workspace_set_state(workspace, EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE, + enabled); +} + +void wlr_ext_workspace_handle_v1_set_urgent( + struct wlr_ext_workspace_handle_v1 *workspace, bool enabled) { + workspace_set_state(workspace, EXT_WORKSPACE_HANDLE_V1_STATE_URGENT, + enabled); +} + +void wlr_ext_workspace_handle_v1_set_hidden( + struct wlr_ext_workspace_handle_v1 *workspace, bool enabled) { + workspace_set_state(workspace, EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN, + enabled); +} diff -r 733566def44f -r 50bab568625d src/wlr_ext_workspace_v1.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/wlr_ext_workspace_v1.h Sat Jul 11 17:56:41 2026 +0800 @@ -0,0 +1,150 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_EXT_WORKSPACE_V1_H +#define WLR_TYPES_WLR_EXT_WORKSPACE_V1_H + +#include +#include + +struct wlr_output; + +enum wlr_ext_workspace_v1_request_type { + WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE, + WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE, + WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE, + WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN, + WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE, +}; + +struct wlr_ext_workspace_v1_request { + enum wlr_ext_workspace_v1_request_type type; + struct wl_list link; // wlr_ext_workspace_manager_v1_resource.requests + union { + struct { + char *name; + struct wlr_ext_workspace_group_handle_v1 + *group; // NULL if destroyed + } create_workspace; + struct { + struct wlr_ext_workspace_handle_v1 *workspace; // NULL if destroyed + } activate; + struct { + struct wlr_ext_workspace_handle_v1 *workspace; // NULL if destroyed + } deactivate; + struct { + struct wlr_ext_workspace_handle_v1 *workspace; // NULL if destroyed + struct wlr_ext_workspace_group_handle_v1 + *group; // NULL if destroyed + } assign; + struct { + struct wlr_ext_workspace_handle_v1 *workspace; // NULL if destroyed + } remove; + }; +}; + +struct wlr_ext_workspace_v1_commit_event { + struct wl_list *requests; // wlr_ext_workspace_v1_request.link +}; + +struct wlr_ext_workspace_manager_v1 { + struct wl_global *global; + struct wl_list groups; // wlr_ext_workspace_group_handle_v1.link + struct wl_list workspaces; // wlr_ext_workspace_handle_v1.link + + struct { + struct wl_signal commit; // wlr_ext_workspace_v1_commit_event + struct wl_signal destroy; + } events; + + void *data; + + struct { + struct wl_list resources; // wlr_ext_workspace_manager_v1_resource.link + struct wl_event_source *idle_source; + struct wl_event_loop *event_loop; + struct wl_listener display_destroy; + }; +}; + +struct wlr_ext_workspace_group_handle_v1 { + struct wlr_ext_workspace_manager_v1 *manager; + uint32_t caps; // ext_workspace_group_handle_v1_group_capabilities + struct { + struct wl_signal destroy; + } events; + + struct wl_list link; // wlr_ext_workspace_manager_v1.groups + + void *data; + + struct { + struct wl_list outputs; // wlr_ext_workspace_v1_group_output.link + struct wl_list resources; // wlr_ext_workspace_manager_v1_resource.link + }; +}; + +struct wlr_ext_workspace_handle_v1 { + struct wlr_ext_workspace_manager_v1 *manager; + struct wlr_ext_workspace_group_handle_v1 *group; // May be NULL + char *id; + char *name; + struct wl_array coordinates; + uint32_t caps; // ext_workspace_handle_v1_workspace_capabilities + uint32_t state; // ext_workspace_handle_v1_state + + struct { + struct wl_signal destroy; + } events; + + struct wl_list link; // wlr_ext_workspace_manager_v1.workspaces + + void *data; + + struct { + struct wl_list resources; // wlr_ext_workspace_v1_resource.link + }; +}; + +struct wlr_ext_workspace_manager_v1 * +wlr_ext_workspace_manager_v1_create(struct wl_display *display, + uint32_t version); + +struct wlr_ext_workspace_group_handle_v1 * +wlr_ext_workspace_group_handle_v1_create( + struct wlr_ext_workspace_manager_v1 *manager, uint32_t caps); +void wlr_ext_workspace_group_handle_v1_destroy( + struct wlr_ext_workspace_group_handle_v1 *group); + +void wlr_ext_workspace_group_handle_v1_output_enter( + struct wlr_ext_workspace_group_handle_v1 *group, struct wlr_output *output); +void wlr_ext_workspace_group_handle_v1_output_leave( + struct wlr_ext_workspace_group_handle_v1 *group, struct wlr_output *output); + +struct wlr_ext_workspace_handle_v1 * +wlr_ext_workspace_handle_v1_create(struct wlr_ext_workspace_manager_v1 *manager, + const char *id, uint32_t caps); +void wlr_ext_workspace_handle_v1_destroy( + struct wlr_ext_workspace_handle_v1 *workspace); + +void wlr_ext_workspace_handle_v1_set_group( + struct wlr_ext_workspace_handle_v1 *workspace, + struct wlr_ext_workspace_group_handle_v1 *group); +void wlr_ext_workspace_handle_v1_set_name( + struct wlr_ext_workspace_handle_v1 *workspace, const char *name); +void wlr_ext_workspace_handle_v1_set_coordinates( + struct wlr_ext_workspace_handle_v1 *workspace, const uint32_t *coords, + size_t coords_len); +void wlr_ext_workspace_handle_v1_set_active( + struct wlr_ext_workspace_handle_v1 *workspace, bool enabled); +void wlr_ext_workspace_handle_v1_set_urgent( + struct wlr_ext_workspace_handle_v1 *workspace, bool enabled); +void wlr_ext_workspace_handle_v1_set_hidden( + struct wlr_ext_workspace_handle_v1 *workspace, bool enabled); + +#endif