changeset 1:50bab568625d default tip

lol
author tsukasa <qwerasdnoob@gmail.com>
date Sat, 11 Jul 2026 17:56:41 +0800
parents 733566def44f
children
files LICENSE Makefile README.md assets/log.txt config.h config.mk example/config.lua flake.lock flake.nix include/client.h include/dwl-ipc.h include/ext-workspace.h include/lua5.4/lauxlib.h include/lua5.4/lua.h include/lua5.4/luaconf.h include/lua5.4/lualib.h licenses/LICENSE licenses/LICENSE.dwl licenses/LICENSE.dwlmsg licenses/LICENSE.dwm licenses/LICENSE.mangowm licenses/LICENSE.sway licenses/LICENSE.tinywl licenses/LICENSE.wlroots parser/parser.c parser/parser.h protocols/dwl-ipc-unstable-v2.xml protocols/ext-workspace-v1.xml protocols/wlr-layer-shell-unstable-v1.xml protocols/wlr-output-power-management-unstable-v1.xml smsg/README.md smsg/arg.h smsg/protocols/dwl-ipc-unstable-v2.xml smsg/smsg.c src/swindle.c src/util.c src/util.h src/wlr_ext_workspace_v1.c src/wlr_ext_workspace_v1.h
diffstat 39 files changed, 12038 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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. <https://fsf.org/>
+ 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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 <https://www.gnu.org/licenses/>.
+
+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:
+
+    <program>  Copyright (C) <year>  <name of author>
+    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
+<https://www.gnu.org/licenses/>.
+
+  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
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
--- /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 $<
--- /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
--- /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 @@
+  ████████  ██  ██                                             
+  ██	██  ██  ██ 
+  ██	██  ██  ██
+████	██████████
--- /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},
+};
--- /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
--- /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"} },
+}
--- /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
+}
--- /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;
+        };
+      });
+}
+
--- /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;
+}
+
--- /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);
+}
--- /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);
+}
--- /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
--- /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
--- /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
--- /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
--- /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.,
+ <https://fsf.org/>
+ 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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 <https://www.gnu.org/licenses/>.
+
+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.
+
+  <signature of Moe Ghoul>, 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.
--- /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. <https://fsf.org/>
+ 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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 <https://www.gnu.org/licenses/>.
+
+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:
+
+    <program>  Copyright (C) <year>  <name of author>
+    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
+<https://www.gnu.org/licenses/>.
+
+  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
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
--- /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 <notchoc@disroot.org>
+
+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.
--- /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 <anselm@garbe.ca>
+© 2006-2009 Jukka Salmi <jukka at salmi dot ch>
+© 2006-2007 Sander van Dijk <a dot h dot vandijk at gmail dot com>
+© 2007-2011 Peter Hartlich <sgkkr at hartlich dot com>
+© 2007-2009 Szabolcs Nagy <nszabolcs at gmail dot com>
+© 2007-2009 Christof Musik <christof at sendfax dot de>
+© 2007-2009 Premysl Hruby <dfenze at gmail dot com>
+© 2007-2008 Enno Gottox Boland <gottox at s01 dot de>
+© 2008 Martin Hurton <martin dot hurton at gmail dot com>
+© 2008 Neale Pickett <neale dot woozle dot org>
+© 2009 Mate Nagy <mnagy at port70 dot net>
+© 2010-2016 Hiltjo Posthuma <hiltjo@codemadness.org>
+© 2010-2012 Connor Lane Smith <cls@lubutu.com>
+© 2011 Christoph Lohmann <20h@r-36.net>
+© 2015-2016 Quentin Rameau <quinq@fifth.space>
+© 2015-2016 Eric Pruitt <eric.pruitt@gmail.com>
+© 2016-2017 Markus Teich <markus.teich@stusta.mhn.de>
+
+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.
--- /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. <https://fsf.org/>
+ 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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 <https://www.gnu.org/licenses/>.
+
+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:
+
+    <program>  Copyright (C) <year>  <name of author>
+    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
+<https://www.gnu.org/licenses/>.
+
+  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
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
\ No newline at end of file
--- /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.
--- /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.
--- /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.
--- /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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/inotify.h>
+#include <linux/input-event-codes.h>
+#include <xkbcommon/xkbcommon.h>
+#include <wayland-server-core.h>
+
+/* Lua */
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+/* wlroots */
+#include <wlr/util/log.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <libinput.h>
+
+/* 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);
+}
--- /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 <stdint.h>
+#include <stdbool.h>
+#include <wayland-server-core.h>
+
+/* 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
+
--- /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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="dwl_ipc_unstable_v2">
+  <copyright>
+    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.
+  </copyright>
+
+  <interface name="zdwl_ipc_manager_v2" version="2">
+    <description summary="manage dwl state">
+      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.
+    </description>
+
+    <request name="release" type="destructor">
+      <description summary="release dwl_ipc_manager">
+        Indicates that the client will not use the manager object anymore.
+        Objects created through this interface remain valid.
+      </description>
+    </request>
+
+    <request name="get_output">
+      <description summary="get a dwl_ipc_output for a wl_output">
+        Get a dwl_ipc_output for the given wl_output.
+      </description>
+      <arg name="id" type="new_id" interface="zdwl_ipc_output_v2"/>
+      <arg name="output" type="object" interface="wl_output"/>
+    </request>
+
+    <event name="tags">
+      <description summary="announces number of tags">
+        This event is sent after binding and announces the number of tags.
+      </description>
+      <arg name="amount" type="uint"/>
+    </event>
+
+    <event name="layout">
+      <description summary="announces a layout">
+        This event is sent after binding and announces a layout.
+        Layouts are announced in order.
+      </description>
+      <arg name="name" type="string"/>
+    </event>
+  </interface>
+
+  <interface name="zdwl_ipc_output_v2" version="2">
+    <description summary="interact with dwl output state">
+      Observe and control a dwl output.
+    </description>
+
+    <enum name="tag_state">
+      <entry name="none" value="0" summary="no state"/>
+      <entry name="active" value="1" summary="tag is active"/>
+      <entry name="urgent" value="2" summary="tag has urgent client"/>
+    </enum>
+
+    <request name="release" type="destructor">
+      <description summary="release dwl_ipc_output">
+        Indicates that the client will not use the dwl_ipc_output object
+        anymore. Objects created through this interface remain valid.
+      </description>
+    </request>
+
+    <request name="set_tags">
+      <description summary="set the active tags of this output">
+        set the active tags of this output.
+      </description>
+      <arg name="tagmask" type="uint" summary="bitmask of the tags that should be set"/>
+      <arg name="toggle_tagset" type="uint" summary="whether to toggle the tagset"/>
+    </request>
+
+    <request name="set_client_tags">
+      <description summary="set the tags of the focused client">
+        Set the tags of the focused client.
+      </description>
+      <arg name="and_tags" type="uint"/>
+      <arg name="xor_tags" type="uint"/>
+    </request>
+
+    <request name="set_layout">
+      <description summary="set the layout of this output">
+        Set the layout of this output.
+      </description>
+      <arg name="index" type="uint"/>
+    </request>
+
+    <event name="toggle_visibility">
+      <description summary="dwl visibility has been toggled">
+        Indicates that dwl visibility has been toggled.
+      </description>
+    </event>
+
+    <event name="active">
+      <description summary="output is active">
+        Indicates the output is active (selected monitor).
+      </description>
+      <arg name="active" type="uint"/>
+    </event>
+
+    <event name="tag">
+      <description summary="tag state changed">
+        Indicates a tag state change.
+      </description>
+      <arg name="tag" type="uint" summary="index of the tag"/>
+      <arg name="state" type="uint" enum="tag_state" summary="state of the tag"/>
+      <arg name="clients" type="uint" summary="number of clients in tag"/>
+      <arg name="focused" type="uint" summary="1 if focused client is in this tag"/>
+    </event>
+
+    <event name="layout">
+      <description summary="layout changed">
+        Indicates the layout changed.
+      </description>
+      <arg name="layout" type="uint" summary="index of the layout"/>
+    </event>
+
+    <event name="title">
+      <description summary="title changed">
+        Indicates the focused client title changed.
+      </description>
+      <arg name="title" type="string"/>
+    </event>
+
+    <event name="appid">
+      <description summary="appid changed">
+        Indicates the focused client appid changed.
+      </description>
+      <arg name="appid" type="string"/>
+    </event>
+
+    <event name="layout_symbol">
+      <description summary="layout symbol changed">
+        Indicates the layout symbol changed.
+      </description>
+      <arg name="layout" type="string"/>
+    </event>
+
+    <event name="frame">
+      <description summary="frame event">
+        Sent after all state events have been sent for a given state change.
+      </description>
+    </event>
+
+    <event name="fullscreen" since="2">
+      <description summary="fullscreen state changed">
+        Indicates the fullscreen state changed.
+      </description>
+      <arg name="is_fullscreen" type="uint"/>
+    </event>
+
+    <event name="floating" since="2">
+      <description summary="floating state changed">
+        Indicates the floating state changed.
+      </description>
+      <arg name="is_floating" type="uint"/>
+    </event>
+  </interface>
+</protocol>
--- /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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="ext_workspace_v1">
+  <copyright>
+    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.
+  </copyright>
+
+  <interface name="ext_workspace_manager_v1" version="1">
+    <description summary="list and control workspaces">
+      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.
+    </description>
+
+    <event name="workspace_group">
+      <description summary="a workspace group has been created">
+        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.
+      </description>
+      <arg name="workspace_group" type="new_id" interface="ext_workspace_group_handle_v1"/>
+    </event>
+
+    <event name="workspace">
+      <description summary="workspace has been created">
+        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.
+      </description>
+      <arg name="workspace" type="new_id" interface="ext_workspace_handle_v1"/>
+    </event>
+  
+    <request name="commit">
+      <description summary="all requests about the workspaces have been sent">
+        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.
+      </description>
+    </request>
+
+    <event name="done">
+      <description summary="all information about the workspaces and workspace groups has been sent">
+        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.
+      </description>
+    </event>
+
+    <event name="finished" type="destructor">
+      <description summary="the compositor has finished with the workspace_manager">
+        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.
+      </description>
+    </event>
+
+    <request name="stop">
+      <description summary="stop sending events">
+        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.
+      </description>
+    </request>
+
+  </interface>
+
+  <interface name="ext_workspace_group_handle_v1" version="1">
+    <description summary="a workspace group assigned to a set of outputs">
+      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.
+    </description>
+
+    <enum name="group_capabilities" bitfield="true">
+      <entry name="create_workspace" value="1" summary="create_workspace request is available"/>
+    </enum>
+
+    <event name="capabilities">
+      <description summary="compositor capabilities">
+        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.
+      </description>
+      <arg name="capabilities" type="uint" summary="capabilities" enum="group_capabilities"/>
+    </event>
+
+    <event name="output_enter">
+      <description summary="output assigned to workspace group">
+        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.
+      </description>
+      <arg name="output" type="object" interface="wl_output"/>
+    </event>
+
+    <event name="output_leave">
+      <description summary="output removed from workspace group">
+        This event is emitted whenever an output is removed from the workspace
+        group.
+      </description>
+      <arg name="output" type="object" interface="wl_output"/>
+    </event>
+
+    <event name="workspace_enter">
+      <description summary="workspace added to 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.
+      </description>
+      <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/>
+    </event>
+
+    <event name="workspace_leave">
+      <description summary="workspace removed from workspace group">
+        This event is emitted whenever a workspace is removed from this group.
+      </description>
+      <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/>
+    </event>
+
+    <event name="removed">
+      <description summary="this workspace group has been removed">
+        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.
+      </description>
+    </event>
+
+    <request name="create_workspace">
+      <description summary="create a new workspace">
+        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.
+      </description>
+      <arg name="workspace" type="string"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the ext_workspace_group_handle_v1 object">
+        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.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="ext_workspace_handle_v1" version="1">
+    <description summary="a workspace handing a group of surfaces">
+      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).
+    </description>
+
+    <event name="id">
+      <description summary="workspace id">
+        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.
+      </description>
+      <arg name="id" type="string"/>
+    </event>
+
+    <event name="name">
+      <description summary="workspace name changed">
+        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.
+      </description>
+      <arg name="name" type="string"/>
+    </event>
+
+    <event name="coordinates">
+      <description summary="workspace coordinates changed">
+        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.
+      </description>
+      <arg name="coordinates" type="array"/>
+    </event>
+
+    <enum name="state" bitfield="true">
+      <description summary="types of states on the workspace">
+        The different states that a workspace can have.
+      </description>
+
+      <entry name="active" value="1" summary="the workspace is active"/>
+      <entry name="urgent" value="2" summary="the workspace requests attention"/>
+      <entry name="hidden" value="4">
+        <description summary="the workspace is not visible">
+          The workspace is not visible in its workspace group, and clients
+          attempting to visualize the compositor workspace state should not
+          display such workspaces.
+        </description>
+      </entry>
+    </enum>
+
+    <event name="state">
+      <description summary="the state of the workspace changed">
+        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.
+      </description>
+      <arg name="state" type="uint" enum="state"/>
+    </event>
+
+    <enum name="workspace_capabilities" bitfield="true">
+      <entry name="activate" value="1" summary="activate request is available"/>
+      <entry name="deactivate" value="2" summary="deactivate request is available"/>
+      <entry name="remove" value="4" summary="remove request is available"/>
+      <entry name="assign" value="8" summary="assign request is available"/>
+    </enum>
+
+    <event name="capabilities">
+      <description summary="compositor capabilities">
+        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.
+      </description>
+      <arg name="capabilities" type="uint" summary="capabilities" enum="workspace_capabilities"/>
+    </event>
+
+    <event name="removed">
+      <description summary="this workspace has been removed">
+        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.
+      </description>
+    </event>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the ext_workspace_handle_v1 object">
+        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.
+      </description>
+    </request>
+
+    <request name="activate">
+      <description summary="activate the workspace">
+        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.
+      </description>
+    </request>
+
+    <request name="deactivate">
+      <description summary="deactivate the workspace">
+        Request that this workspace be deactivated.
+
+        There is no guarantee the workspace will be actually deactivated.
+      </description>
+    </request>
+
+    <request name="assign">
+      <description summary="assign workspace to group">
+        Requests that this workspace is assigned to the given workspace group.
+
+        There is no guarantee the workspace will be assigned.
+      </description>
+      <arg name="workspace_group" type="object" interface="ext_workspace_group_handle_v1"/>
+    </request>
+
+    <request name="remove">
+      <description summary="remove the workspace">
+        Request that this workspace be removed.
+
+        There is no guarantee the workspace will be actually removed.
+      </description>
+    </request>
+  </interface>
+</protocol>
--- /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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_layer_shell_unstable_v1">
+  <copyright>
+    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.
+  </copyright>
+
+  <interface name="zwlr_layer_shell_v1" version="4">
+    <description summary="create surfaces that are layers of the desktop">
+      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.
+    </description>
+
+    <request name="get_layer_surface">
+      <description summary="create a layer_surface from a surface">
+        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.
+      </description>
+      <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
+      <arg name="surface" type="object" interface="wl_surface"/>
+      <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+      <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
+      <arg name="namespace" type="string" summary="namespace for the layer surface"/>
+    </request>
+
+    <enum name="error">
+      <entry name="role" value="0" summary="wl_surface has another role"/>
+      <entry name="invalid_layer" value="1" summary="layer value is invalid"/>
+      <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
+    </enum>
+
+    <enum name="layer">
+      <description summary="available layers for surfaces">
+        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.
+      </description>
+
+      <entry name="background" value="0"/>
+      <entry name="bottom" value="1"/>
+      <entry name="top" value="2"/>
+      <entry name="overlay" value="3"/>
+    </enum>
+
+    <!-- Version 3 additions -->
+
+    <request name="destroy" type="destructor" since="3">
+      <description summary="destroy the layer_shell object">
+        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.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="zwlr_layer_surface_v1" version="4">
+    <description summary="layer metadata interface">
+      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.
+    </description>
+
+    <request name="set_size">
+      <description summary="sets the size of the surface">
+        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.
+      </description>
+      <arg name="width" type="uint"/>
+      <arg name="height" type="uint"/>
+    </request>
+
+    <request name="set_anchor">
+      <description summary="configures the anchor point of the surface">
+        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.
+      </description>
+      <arg name="anchor" type="uint" enum="anchor"/>
+    </request>
+
+    <request name="set_exclusive_zone">
+      <description summary="configures the exclusive geometry of this surface">
+        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.
+      </description>
+      <arg name="zone" type="int"/>
+    </request>
+
+    <request name="set_margin">
+      <description summary="sets a margin from the anchor point">
+        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.
+      </description>
+      <arg name="top" type="int"/>
+      <arg name="right" type="int"/>
+      <arg name="bottom" type="int"/>
+      <arg name="left" type="int"/>
+    </request>
+
+    <enum name="keyboard_interactivity">
+      <description summary="types of keyboard interaction possible for a layer shell surface">
+        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.
+      </description>
+
+      <entry name="none" value="0">
+        <description summary="no keyboard focus is possible">
+          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.
+        </description>
+      </entry>
+      <entry name="exclusive" value="1">
+        <description summary="request exclusive keyboard focus">
+          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.
+        </description>
+      </entry>
+      <entry name="on_demand" value="2" since="4">
+        <description summary="request regular keyboard focus semantics">
+          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.
+        </description>
+      </entry>
+    </enum>
+
+    <request name="set_keyboard_interactivity">
+      <description summary="requests keyboard events">
+        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.
+      </description>
+      <arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
+    </request>
+
+    <request name="get_popup">
+      <description summary="assign this layer_surface as an xdg_popup parent">
+        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.
+      </description>
+      <arg name="popup" type="object" interface="xdg_popup"/>
+    </request>
+
+    <request name="ack_configure">
+      <description summary="ack a configure event">
+        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.
+      </description>
+      <arg name="serial" type="uint" summary="the serial from the configure event"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the layer_surface">
+        This request destroys the layer surface.
+      </description>
+    </request>
+
+    <event name="configure">
+      <description summary="suggest a surface change">
+        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.
+      </description>
+      <arg name="serial" type="uint"/>
+      <arg name="width" type="uint"/>
+      <arg name="height" type="uint"/>
+    </event>
+
+    <event name="closed">
+      <description summary="surface should be closed">
+        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.
+      </description>
+    </event>
+
+    <enum name="error">
+      <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
+      <entry name="invalid_size" value="1" summary="size is invalid"/>
+      <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
+      <entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
+    </enum>
+
+    <enum name="anchor" bitfield="true">
+      <entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
+      <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
+      <entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
+      <entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
+    </enum>
+
+    <!-- Version 2 additions -->
+
+    <request name="set_layer" since="2">
+      <description summary="change the layer of the surface">
+        Change the layer that the surface is rendered on.
+
+        Layer is double-buffered, see wl_surface.commit.
+      </description>
+      <arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
+    </request>
+  </interface>
+</protocol>
--- /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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_output_power_management_unstable_v1">
+  <copyright>
+    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.
+  </copyright>
+
+  <description summary="Control power management modes of outputs">
+    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.
+  </description>
+
+  <interface name="zwlr_output_power_manager_v1" version="1">
+    <description summary="manager to create per-output power management">
+      This interface is a manager that allows creating per-output power
+      management mode controls.
+    </description>
+
+    <request name="get_output_power">
+      <description summary="get a power management for an output">
+        Create a output power management mode control that can be used to
+        adjust the power management mode for a given output.
+      </description>
+      <arg name="id" type="new_id" interface="zwlr_output_power_v1"/>
+      <arg name="output" type="object" interface="wl_output"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the manager">
+        All objects created by the manager will still remain valid, until their
+        appropriate destroy request has been called.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="zwlr_output_power_v1" version="1">
+    <description summary="adjust power management mode for an output">
+      This object offers requests to set the power management mode of
+      an output.
+    </description>
+
+    <enum name="mode">
+      <entry name="off" value="0"
+             summary="Output is turned off."/>
+      <entry name="on" value="1"
+             summary="Output is turned on, no power saving"/>
+    </enum>
+
+    <enum name="error">
+      <entry name="invalid_mode" value="1" summary="inexistent power save mode"/>
+    </enum>
+
+    <request name="set_mode">
+      <description summary="Set an outputs power save mode">
+        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.
+      </description>
+      <arg name="mode" type="uint" enum="mode" summary="the power save mode to set"/>
+    </request>
+
+    <event name="mode">
+      <description summary="Report a power management mode change">
+        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.
+      </description>
+      <arg name="mode" type="uint" enum="mode"
+           summary="the output's new power management mode"/>
+    </event>
+
+    <event name="failed">
+      <description summary="object no longer valid">
+        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.
+      </description>
+    </event>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy this power management">
+        Destroys the output power management mode control object.
+      </description>
+    </request>
+  </interface>
+</protocol>
--- /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 <output>] -s [-t <tags>] [-l <layout>] [-c <tags>]
+	smsg [-o <output>] (-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 '><>'
+```
+
--- /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
--- /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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+This is largely ripped from somebar's ipc patchset; just with some personal modifications.
+I would probably just submit raphi's patchset but I don't think that would be polite.
+-->
+<protocol name="dwl_ipc_unstable_v2">
+  <description summary="inter-proccess-communication about dwl's state">
+      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.
+  </description>
+
+  <interface name="zdwl_ipc_manager_v2" version="2">
+    <description summary="manage dwl state">
+      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.
+    </description>
+
+    <request name="release" type="destructor">
+      <description summary="release dwl_ipc_manager">
+        Indicates that the client will not the dwl_ipc_manager object anymore.
+        Objects created through this instance are not affected.
+      </description>
+    </request>
+
+    <request name="get_output">
+      <description summary="get a dwl_ipc_outout for a wl_output">
+        Get a dwl_ipc_outout for the specified wl_output.
+      </description>
+      <arg name="id" type="new_id" interface="zdwl_ipc_output_v2"/>
+      <arg name="output" type="object" interface="wl_output"/>
+    </request>
+
+    <event name="tags">
+      <description summary="Announces tag amount">
+        This event is sent after binding.
+        A roundtrip after binding guarantees the client recieved all tags.
+      </description>
+      <arg name="amount" type="uint"/>
+    </event>
+
+    <event name="layout">
+      <description summary="Announces a layout">
+        This event is sent after binding.
+        A roundtrip after binding guarantees the client recieved all layouts.
+      </description>
+      <arg name="name" type="string"/>
+    </event>
+  </interface>
+
+  <interface name="zdwl_ipc_output_v2" version="2">
+    <description summary="control dwl output">
+      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.
+    </description>
+
+    <enum name="tag_state">
+      <entry name="none" value="0" summary="no state"/>
+      <entry name="active" value="1" summary="tag is active"/>
+      <entry name="urgent" value="2" summary="tag has at least one urgent client"/>
+    </enum>
+
+    <request name="release" type="destructor">
+      <description summary="release dwl_ipc_outout">
+        Indicates to that the client no longer needs this dwl_ipc_output.
+      </description>
+    </request>
+
+    <event name="toggle_visibility">
+      <description summary="Toggle client visibilty">
+        Indicates the client should hide or show themselves.
+        If the client is visible then hide, if hidden then show.
+      </description>
+    </event>
+
+    <event name="active">
+      <description summary="Update the selected output.">
+        Indicates if the output is active. Zero is invalid, nonzero is valid.
+      </description>
+      <arg name="active" type="uint"/>
+    </event>
+
+    <event name="tag">
+      <description summary="Update the state of a tag.">
+        Indicates that a tag has been updated.
+      </description>
+      <arg name="tag" type="uint" summary="Index of the tag"/>
+      <arg name="state" type="uint" enum="tag_state" summary="The state of the tag."/>
+      <arg name="clients" type="uint" summary="The number of clients in the tag."/>
+      <arg name="focused" type="uint" summary="If there is a focused client. Nonzero being valid, zero being invalid."/>
+    </event>
+
+    <event name="layout">
+      <description summary="Update the layout.">
+        Indicates a new layout is selected.
+      </description>
+      <arg name="layout" type="uint" summary="Index of the layout."/>
+    </event>
+
+    <event name="title">
+      <description summary="Update the title.">
+        Indicates the title has changed.
+      </description>
+      <arg name="title" type="string" summary="The new title name."/>
+    </event>
+
+    <event name="appid" since="1">
+      <description summary="Update the appid.">
+        Indicates the appid has changed.
+      </description>
+      <arg name="appid" type="string" summary="The new appid."/>
+    </event>
+
+    <event name="layout_symbol" since="1">
+      <description summary="Update the current layout symbol">
+          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.
+      </description>
+      <arg name="layout" type="string" summary="The new layout"/>
+    </event>
+
+    <event name="frame">
+      <description summary="The update sequence is done.">
+        Indicates that a sequence of status updates have finished and the client should redraw.
+      </description>
+    </event>
+
+    <request name="set_tags">
+      <description summary="Set the active tags of this output"/>
+      <arg name="tagmask" type="uint" summary="bitmask of the tags that should be set."/>
+      <arg name="toggle_tagset" type="uint" summary="toggle the selected tagset, zero for invalid, nonzero for valid."/>
+    </request>
+
+    <request name="set_client_tags">
+      <description summary="Set the tags of the focused client.">
+        The tags are updated as follows:
+        new_tags = (current_tags AND and_tags) XOR xor_tags
+      </description>
+      <arg name="and_tags" type="uint"/>
+      <arg name="xor_tags" type="uint"/>
+    </request>
+
+    <request name="set_layout">
+      <description summary="Set the layout of this output"/>
+      <arg name="index" type="uint" summary="index of a layout recieved by dwl_ipc_manager.layout"/>
+    </request>
+
+    <!-- Version 2 -->
+    <event name="fullscreen" since="2">
+      <description summary="Update fullscreen status">
+          Indicates if the selected client on this output is fullscreen.
+      </description>
+      <arg name="is_fullscreen" type="uint" summary="If the selected client is fullscreen. Nonzero is valid, zero invalid"/>
+    </event>
+
+    <event name="floating" since="2">
+      <description summary="Update the floating status">
+          Indicates if the selected client on this output is floating.
+      </description>
+      <arg name="is_floating" type="uint" summary="If the selected client is floating. Nonzero is valid, zero invalid"/>
+    </event>
+  </interface>
+</protocol>
--- /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 <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <wayland-client.h>
+#include <wayland-util.h>
+#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<<tag;
+	if (state == ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE) urg |= 1<<tag;
+	if (clients > 0) occ |= 1<<tag;
+	if (!(mode & GET)) return;
+	if (data) printf("%s ", (char *)data);
+	printf("tag %u %u %u %u\n", tag, state, clients, focused);
+}
+
+static void
+dwl_ipc_output_layout(void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output,
+	uint32_t layout)
+{
+}
+
+static void
+dwl_ipc_output_layout_symbol(void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output,
+	const char *layout)
+{
+	if (!(lflag && mode & GET)) return;
+	if (data) printf("%s ", (char *)data);
+	printf("layout %s\n", layout);
+}
+
+static void
+dwl_ipc_output_title(void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output,
+	const char *title)
+{
+	if (!(cflag && mode & GET)) return;
+	if (data) printf("%s ", (char *)data);
+	printf("title %s\n", title);
+}
+
+static void
+dwl_ipc_output_appid(void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output,
+	const char *appid)
+{
+	if (!(cflag && mode & GET)) return;
+	if (data) printf("%s ", (char *)data);
+	printf("appid %s\n", appid);
+}
+
+static void
+dwl_ipc_output_fullscreen(void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output,
+	uint32_t is_fullscreen)
+{
+	if (!mflag) return;
+	if (data) printf("%s ", (char *)data);
+	printf("fullscreen %u\n", is_fullscreen);
+}
+
+static void
+dwl_ipc_output_floating(void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output,
+	uint32_t is_floating)
+{
+	if (!fflag) return;
+	if (data) printf("%s ", (char *)data);
+	printf("floating %u\n", is_floating);
+}
+
+static void
+dwl_ipc_output_frame(void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output)
+{
+	if (mode & SET) {
+		if (data && !(output_name && strcmp(output_name, (char *)data) == 0)) return;
+		if (lflag) {
+			if (layout_idx == 0) {
+				for (char *c = layout_name; *c; c++) {
+					if (*c < '0' || *c > '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<<i;
+
+			for (; *t; t++, i++) {
+				switch (*t) {
+				case '-':
+					mask &= ~(1<<i);
+					break;
+				case '+':
+					mask |= 1<<i;
+					break;
+				case '^':
+					mask ^= 1<<i;
+					break;
+				}
+			}
+
+			if (i > 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<<i;
+			}
+
+			for (; *t; t++, i++) {
+				switch (*t) {
+				case '-':
+					and &= ~(1<<i);
+					break;
+				case '+':
+					and &= ~(1<<i);
+					xor |= 1<<i;
+					break;
+				case '^':
+					xor |= 1<<i;
+					break;
+				}
+			}
+			if (i > 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 <output>] -s [-t <tags>] [-l <layout>] [-c <tags>]\n"
+			"\t%1$s [-o <output>] (-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, &registry_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;
+}
--- /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 <errno.h>
+#include <getopt.h>
+#include <libinput.h>
+#include <linux/input-event-codes.h>
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+#include <wayland-server-core.h>
+#include <wlr/backend.h>
+#include <wlr/backend/libinput.h>
+#include <wlr/render/allocator.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_alpha_modifier_v1.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_cursor_shape_v1.h>
+#include <wlr/types/wlr_data_control_v1.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_drm.h>
+#include <wlr/types/wlr_export_dmabuf_v1.h>
+#include <wlr/types/wlr_ext_data_control_v1.h>
+#include <wlr/types/wlr_fractional_scale_v1.h>
+#include <wlr/types/wlr_gamma_control_v1.h>
+#include <wlr/types/wlr_idle_inhibit_v1.h>
+#include <wlr/types/wlr_idle_notify_v1.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/types/wlr_keyboard_group.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#include <wlr/types/wlr_linux_dmabuf_v1.h>
+#include <wlr/types/wlr_linux_drm_syncobj_v1.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_output_management_v1.h>
+#include <wlr/types/wlr_output_power_management_v1.h>
+#include <wlr/types/wlr_pointer.h>
+#include <wlr/types/wlr_pointer_constraints_v1.h>
+#include <wlr/types/wlr_presentation_time.h>
+#include <wlr/types/wlr_primary_selection.h>
+#include <wlr/types/wlr_primary_selection_v1.h>
+#include <wlr/types/wlr_relative_pointer_v1.h>
+#include <wlr/types/wlr_scene.h>
+#include <wlr/types/wlr_screencopy_v1.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/types/wlr_server_decoration.h>
+#include <wlr/types/wlr_session_lock_v1.h>
+#include <wlr/types/wlr_single_pixel_buffer_v1.h>
+#include <wlr/types/wlr_subcompositor.h>
+#include <wlr/types/wlr_viewporter.h>
+#include <wlr/types/wlr_virtual_keyboard_v1.h>
+#include <wlr/types/wlr_virtual_pointer_v1.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/types/wlr_xdg_activation_v1.h>
+#include <wlr/types/wlr_xdg_decoration_v1.h>
+#include <wlr/types/wlr_xdg_output_v1.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+#include <xkbcommon/xkbcommon.h>
+#ifdef XWAYLAND
+#include <wlr/xwayland.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_icccm.h>
+#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]);
+}
+
--- /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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#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;
+}
--- /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);
--- /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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wlr/types/wlr_output.h>
+
+#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);
+}
--- /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 <wayland-protocols/ext-workspace-v1-enum.h>
+#include <wayland-server-core.h>
+
+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