commit a0e445f5ea171c3bf26e42e3445bd4edc213d875
Author: Raymond Cole <rc@wolog.xyz>
Date: Thu, 24 Oct 2024 20:58:34 +0000
Initial commit
Diffstat:
A | .gitignore | | | 2 | ++ |
A | LICENSE | | | 674 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | Makefile | | | 31 | +++++++++++++++++++++++++++++++ |
A | README | | | 326 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | bar.c | | | 313 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | client.c | | | 595 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | config.c | | | 445 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | config.h | | | 20 | ++++++++++++++++++++ |
A | config.mk | | | 28 | ++++++++++++++++++++++++++++ |
A | drw.c | | | 453 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | drw.h | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | evt.c | | | 277 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | lyt.c | | | 505 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | main.c | | | 601 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | misc.c | | | 594 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | monitor.c | | | 295 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | swm.h | | | 206 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | util.c | | | 60 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | util.h | | | 23 | +++++++++++++++++++++++ |
19 files changed, 5511 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+swm
+*.o
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://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 <http://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
+<http://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
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,31 @@
+include config.mk
+
+SWMSRC = bar.c client.c config.c evt.c lyt.c main.c misc.c monitor.c
+SWMOBJ = ${SWMSRC:.c=.o}
+SRC = ${SWMSRC} drw.c util.c
+OBJ = ${SRC:.c=.o}
+
+all: swm
+
+.c.o:
+ ${CC} -c ${CFLAGS} $<
+
+${OBJ}: config.mk util.h
+${SWMOBJ}: config.h swm.h
+bar.o drw.o: drw.h
+
+swm: ${OBJ}
+ ${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+clean:
+ rm -f swm *.o
+
+install: all
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp -f swm ${DESTDIR}${PREFIX}/bin
+ chmod 755 ${DESTDIR}${PREFIX}/bin/swm
+
+uninstall:
+ rm -f ${DESTDIR}${PREFIX}/bin/swm
+
+.PHONY: all clean install uninstall
diff --git a/README b/README
@@ -0,0 +1,326 @@
+swm - Σ Window Manager
+======================
+swm is [dwm](https://dwm.suckless.org) made less bare-boned and more
+customizable.
+
+
+Install & Run
+-------------
+libX11, libXft, and optionally libXinerama are required. Edit config.mk
+to match your local setup, and then build and install swm with the
+following command (as root if necessary):
+
+ make install
+
+The default installation directory is /usr/local/bin.
+
+You may add the following line to your .xinitrc to start swm using startx:
+
+ exec swm
+
+But be warned that this new software might not yet be sufficiently
+crash-proof, so, for now, it's probably better to instead add something
+like this:
+
+ swm || dwm
+
+This way a reliable window manager like dwm in this example can take
+over windows upon abnormal termination of swm.
+
+swm sends warnings and errors to stderr, so having its stderr logged
+might sometimes prove helpful.
+
+
+Quick Start
+-----------
+swm grabs any key with, by default, mod4 as a modifier, which is normally
+produced by the Super key. This can be changed in config.h. For the
+rest of the section Super is assumed to be the modifier key for swm.
+
+- Super+Enter: Spawn st, the suckless terminal.
+
+- Super+p: Launch other programs with dmenu.
+
+- Super+Backspace: Close the focused window.
+
+- Super+{j,k}: Focus the next/previous window.
+
+- Super+Shift+{j,k}: Shift the focused window.
+
+- Super+*n*: switch to the *n*th workspace.
+
+- Super+Shift+*n*: Tag the focused window with tag *n*; usually has the
+ effect of sending the window to the *n*th workspace.
+
+- Super+{t,m}: Switch to the tiled (a.k.a. right stack) or monocle layout.
+
+- Super+y: Select from a list of layouts with dmenu; you can even make
+ a new layout using this menu, but more on that later.
+
+- Super+{i,d}: Increase/Decrease the number of master area windows; if
+ you don't already know the meaning, just try with a few windows in
+ the default layout.
+
+- Super+{h,l}: Change the portion of master area.
+
+- Super+f: Toggle whether the focused window is fullscreen.
+
+- Super+Shift+{q,r}: Quit/Restart swm.
+
+If you use multiple monitors and don't disable Xinerama, these are
+also useful:
+
+- Super+], Super+[: Switch to the next/previous monitor.
+
+- Super+Shift+], Super+Shift+[: Send the focused window to the
+ next/previous monitor.
+
+Using the mouse, you can press Super+Button1 to move a window and
+Super+Button3 to resize. These will make the window floating. You can
+press Super+Shift+Space to toggle the floating state of a window.
+
+More can be learnt from config.c.
+
+
+Configuration
+-------------
+config.c and config.h are provided for configuration. Few options
+are contained in config.h, and everything else, including key bindings
+and colors etc. belong to config.c, a small separate compilation unit.
+This way rebuilding swm after changing key bindings etc. is much more
+efficient compared to dwm where changing any configuration requires
+recompilation of the whole program.
+
+Unlike dwm, key bindings are defined directly inside the key event
+handler, and mouse button bindings inside the button event handler,
+instead of arrays searched by the handlers. This different approach is
+taken for greater flexibility.
+
+With keybindings defined directly in a function, it's complicated for swm
+to grab exactly the keys and buttons that are bound like dwm does, so,
+to be simple, swm grabs any key or button with the configured modifier,
+but this prevents the modifier from being shared with other graphical
+programs. This issue is discussed next.
+
+
+Tips about Modifier Keys
+------------------------
+The Alt key is probably the handiest modifier key on a typical keyboard,
+but using it with swm will prevent other graphical program from using it.
+You may do just that and live with the exclusiveness, or you may stick
+with using Super, which is generally not used by other graphical programs,
+if you don't mind the relative inconvenience; these are the two obvious
+options. But here is a recommended third way: Turn Caps Lock into Super.
+
+You can turn Caps Lock into Super with this command:
+
+ xmodmap -e 'remove Lock = Caps_Lock' \
+ -e 'keysym Caps_Lock = Super_L Caps_Lock'
+
+This will still allow toggling all-caps by pressing Shift+Caps_Lock.
+Put the command in .xinitrc to automate it.
+
+If you also would like to use Caps Lock for Escape (sweet for vi users),
+do the two usages conflict? They don't have to, with the help of
+[xcape](https://github.com/alols/xcape). The following will make Caps
+Lock, now a surrogate Super key, function as Escape when pressed and
+released on its own:
+
+ xcape -e Super_L=Escape
+
+xcape should be run after the xmodmap command.
+
+If xcape is used with xmodmap as described above, Shift+Caps_Lock will
+generate an Escape besides toggling all-caps. You can fix that little
+problem by changing the xmodmap command above to this:
+
+ xmodmap -e 'remove Lock = Caps_Lock' \
+ -e 'keysym Caps_Lock = Super_L' \
+ -e 'keysym Escape = Escape Caps_Lock'
+
+Be aware that there is a small issue with using Caps Lock as Super: Some
+keyboards have a problem with certain modifier combinations when using
+such a surrogate Super key. So you might find it necessary to resort to
+the original Super key for, e.g., Super+Shift+2. Modifier combinations
+are typically easier with the original Super key, anyway.
+
+
+Workspaces & Tags
+-----------------
+There are as many workspaces as there are tags. You can select one
+workspace at a time, which contains a separate layout and a separate
+set of tags. Every window can be assigned one or more tags just like
+in dwm, and a workspace's tag set is used to match windows by tags.
+Windows matched by the tag set of a workspace belong, non-exclusively,
+to the workspace.
+
+To compare, dwm has one layout and one tag set per (logical) monitor,
+and what feels sort of like switching between workspaces in dwm is
+actually setting the tag set to comprise only a specific tag. You can
+put multiple tags in the tag set, but the selected tags are easily
+lost during switching between tags, making selecting multiple tags a
+less usable feature. Besides, this design makes things like one layout
+"per tag" not quite well-defined. So swm slightly enhanced the design to
+address these problems. Initially, the tag set of each workspace contains
+a single tag that has the same index as the workspace, making selecting
+a workspace in swm works much like viewing a specific tag in dwm.
+
+The array of tags displayed on the status bar is dually used for tags
+and workspaces. The current workspace is indicated by a bold underline,
+and the tag set of the workspace and tags of windows are indicated in
+the same way as indicated in dwm: Tags in the current tag set are colored
+differently, and tags that are applied to one or more windows are marked,
+in their upper left corner, by a little square, which is filled if the
+tag is applied to the focused window and empty otherwise.
+
+
+Restarting
+----------
+Taking the dwm approach, you have to rebuild and restart swm for new
+configurations to take effect. By default you can press Super+Shift+r
+to restart swm. During restarting, swm executes the command that starts
+it and uses environment variables (later cleared by the new instance)
+to pass down certain runtime information, including:
+
+- The monitor each client belongs to
+- Order of clients in each monitor
+- Tags and state (floating/fullscreen/scratchpad) of each client
+- Tag set and layout in each workspace of each monitor
+- Whether gaps and bar are shown in each monitor
+- Each monitor's current workspace
+- The focused client
+
+These help provide a seamless transition and make reconfiguration like
+a breeze.
+
+
+Status Program
+--------------
+swm runs a status program configured in config.c and uses pipes to connect
+to the status program's input and output. Every time the status program
+outputs a line, the line is taken as the new status text and displayed on
+the status bar. swm talks to the status program by writing to its input.
+
+This is quite different from how dwm handles status. dwm reads the
+WM_NAME property of the root window for status text. This simplifies the
+window manager's main loop, but makes it uneasy for the window manager
+to talk to the status program, adds a little burden to the status program
+and is less efficient.
+
+swm recognizes status text as blocks separated by two or more consecutive
+spaces. When a block displayed on the status bar is clicked, swm's
+button event handler by default writes to the status program a line
+indicating which block is clicked (1 for the rightmost, 2 for the second
+rightmost, etc.), which button is pressed, and which modifiers are on.
+Details of what gets written can be found or changed in config.c.
+
+The status program can be toggled on and off with Super+Shift+s by
+default. Pressing the key twice has the effect of restarting the status
+program. By turning the status program off, swm doesn't directly kill
+it but closes the pipe to its input, and the status program is expected
+to detect this condition.
+
+The status program that swm by default is configured to use is
+[infobar](https://wolog.xyz/repos/infobar).
+
+
+Scratchpads
+-----------
+Scratchpads are windows, typically floating and centered, that can be
+handily brought into and out of view. In swm, any window can be marked
+a scratchpad. Here are the related default key bindings:
+
+- `Super+'`: Toggle a scratchpad. If visible scratchpads are present,
+ the topmost is hidden; otherwise, the topmost hidden scratchpad
+ (normally the most recently used) is brought into view. If there is
+ no scratchpad at all, a scratchpad terminal is launched.
+
+- `Super+.`: Cycle through all hidden scratchpads. If the focused window
+ is a scratchpad, it gets replaced by the next hidden scratchpad, if
+ any; otherwise this just brings a hidden scratchpad, if there is one,
+ into view.
+
+- Super+Shift+Enter: Launch the scratchpad terminal.
+
+- Super+<backtick>: Toggle whether a window is a scratchpad or not.
+ A floating window gets centered when turned into a scratchpad.
+
+The basic usage is simply to toggle a scratchpad terminal on and off
+by pressing `Super+'`, but you're offered convenience to develop more
+sophisticated usages.
+
+
+Layout Description Language
+---------------------------
+The layout description language is a tiny language invented for
+specification of layouts in swm. Most layouts that can be found in
+dwm and its patches can be described quite well in this language. Here
+are examples:
+
+- Right stack (the default tiled layout):
+
+ [*]:.55 []
+
+- Monocle:
+
+ ()
+
+- Deck:
+
+ [*]:.55 ()
+
+- Bottom stack:
+
+ [{*}:.55 {}]
+
+- Fibonacci spiral:
+
+ .62 [.62 -{.62 [.62 ()]}]
+
+- Fibonacci dwindle:
+
+ .62 [.62 {.62 [.62 ()]}]
+
+(The last two examples are not actually exact, as the actual layouts
+described will tile the 5th and subsequent windows in the monocle manner.)
+
+dwm's notions of master number and master factor are defined more
+generically in swm. Generally, changing them in swm works the way most
+dwm users would expect.
+
+The language is simple enough that you can probably gain intuition
+from the examples and then figure it out by experimenting (see the next
+section for an easy method).
+
+
+dmenu Integration
+-----------------
+First of all, in case you only think of dmenu as a fuzzy single-item
+selector: You can pick multiple items, and even pick an item multiple
+times, with Ctrl+Enter while navigating the menu using Ctrl+{n,p}; you
+can also use dmenu as an input box, where Tab can be used to copy an
+item, a bit of line editing capability is available, and the input can
+be confirmed by pressing Shift+Enter.
+
+In swm, by default, pressing Super+y runs dmenu to give you a menu of
+layout descriptions to choose from, where you may also write an ad-hoc
+layout or tweak an existing one.
+
+swm provides four types of gaps (internal/outer, horizontal/vertical) that
+are separately settable. Pressing Super+g gives you a menu of options for
+setting or changing the values of them, where making good use of dmenu
+will enable you to easily tune them. (But if you just want to toggle
+gaps off, press Super+Shift+g, and if you want to increase or decrease
+all gaps, scroll up and down with your mouse while holding Super.)
+
+vanitygaps, a dwm patch, offers a number of layouts with gaps, but to
+fully utilize what it offers, a user would need to bind eight keys each for
+choosing one layout and bind eight keys for increasing and decreasing the
+four types of gaps. This is a waste of spare keys given the infrequency
+of most of the actions, and even that doesn't provide as much freedom as
+swm does. So swm's menus serve to help out such situations by combining
+many infrequent actions into few menus and allowing doing things more
+sophisticated in case of need.
+
+You can easily construct such menus in config.c using convenience
+functions provided; see config.c for examples.
diff --git a/bar.c b/bar.c
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+#include <fcntl.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xft/Xft.h>
+
+#include "drw.h"
+#include "swm.h"
+#include "util.h"
+
+#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
+
+int barh;
+int sfdw = -1, sfdr = -1;
+
+static Drw *drw;
+static Scm *schemesel;
+static Scm *schemenorm;
+
+static int lrpad, boxs, boxw;
+
+static char blocks[256];
+static size_t nblocks;
+static int blockw[(sizeof(blocks) - 1) / 2];
+static int blocklpad[LENGTH(blockw) + 1];
+
+void
+barsetup(void)
+{
+ if (!(drw = drw_create(dpy, screen, root, sw, sh)))
+ die("cannot create drawer object");
+ if (!drw_fontset_create(drw, fonts, nfonts))
+ die("cannot load fonts");
+ lrpad = drw->fonts->h;
+ boxs = drw->fonts->h / 9;
+ boxw = drw->fonts->h / 6 + 2;
+ barh = drw->fonts->h + 2;
+ if (!(schemesel = drw_scm_create(drw, colsf, colsb))
+ || !(schemenorm = drw_scm_create(drw, colnf, colnb)))
+ die("cannot create color schemes");
+}
+
+void
+barcleanup(void)
+{
+ free(schemenorm);
+ free(schemesel);
+ drw_free(drw);
+}
+
+void
+initbar(Monitor *m)
+{
+ static XSetWindowAttributes wa = {
+ .override_redirect = True,
+ .background_pixmap = ParentRelative,
+ .event_mask = ButtonPressMask|ExposureMask
+ };
+ static XClassHint ch = { "swm", "swm" };
+
+ m->barwin = XCreateWindow(dpy, root, m->x, BY(m), m->w, barh, 0,
+ DefaultDepth(dpy, screen), CopyFromParent,
+ DefaultVisual(dpy, screen),
+ CWOverrideRedirect|CWBackPixmap|CWEventMask,
+ &wa);
+ XDefineCursor(dpy, m->barwin, curnormal);
+ XMapRaised(dpy, m->barwin);
+ XSetClassHint(dpy, m->barwin, &ch);
+}
+
+void
+runsprog(void)
+{
+ int fds[2];
+ pid_t pid;
+
+ if (!statusprog)
+ return;
+ if (sfdr >= 0 || sfdw >= 0)
+ endsprog();
+ switch ((pid = depart(fds))) {
+ case -1:
+ warn("cannot run status program");
+ return;
+ case 0:
+ execvp(statusprog[0], statusprog);
+ die("execvp '%s':", statusprog[0]);
+ default:
+ break;
+ }
+ sfdr = fds[0];
+ sfdw = fds[1];
+ if (fcntl(sfdr, F_SETFL, O_NONBLOCK) < 0
+ || fcntl(sfdr, F_SETFD, FD_CLOEXEC) < 0) {
+ warn("cannot set up read end for status: fcntl:");
+ endsprog();
+ }
+}
+
+void
+endsprog(void)
+{
+ if (sfdr >= 0)
+ close(sfdr);
+ if (sfdw >= 0)
+ close(sfdw);
+ sfdr = sfdw = -1;
+}
+
+void
+updatedrawable(void)
+{
+ drw_resize(drw, sw, barh);
+}
+
+void
+updatestatus(void)
+{
+ char *b, *p;
+ unsigned i, n;
+ char buf[sizeof(blocks)];
+ int r, e, t;
+ struct pollfd fd[1];
+
+ if (sfdr < 0) {
+ nblocks = 0;
+ pdrawbar(selmon);
+ return;
+ }
+ for (n = 0; (r = read(sfdr, buf, sizeof(buf))); n += r) {
+ if (r < 0) {
+ e = errno;
+ if (e == EAGAIN && n && blocks[n - 1] != '\n') {
+ fd->fd = sfdr;
+ fd->events = POLLIN;
+ if (poll(fd, 1, 1000) == 1) {
+ r = 0;
+ continue;
+ }
+ }
+ break;
+ }
+ if (n + r <= sizeof(blocks)) {
+ memcpy(blocks + n, buf, r);
+ } else {
+ memmove(blocks, blocks + n + r - sizeof(blocks),
+ sizeof(blocks) - r);
+ memcpy(blocks + sizeof(blocks) - r, buf, r);
+ n = sizeof(blocks) - r;
+ }
+ }
+ if (!r)
+ e = 0;
+ if (e == EAGAIN && !n)
+ return;
+ if (e != EAGAIN && e) {
+ errno = e;
+ warn("read:");
+ }
+ if (e != EAGAIN || !n || blocks[n - 1] != '\n') {
+ endsprog();
+ nblocks = 0;
+ pdrawbar(selmon);
+ return;
+ }
+ blocks[--n] = '\0';
+ if ((b = strrchr(blocks, '\n')))
+ memmove(blocks, b + 1, blocks + n - b);
+ nblocks = 0;
+ b = p = blocks;
+ p += t = strspn(p, " ");
+ while (*p) {
+ blocklpad[nblocks++] = t * lrpad / 2;
+ while (*p && (*p != ' ' || (p[1] && p[1] != ' ')))
+ *b++ = *p++;
+ p += t = strspn(p, " ");
+ *b++ = '\0';
+ }
+ for (i = 0, b = blocks; i < nblocks; ++i, b += strlen(b) + 1)
+ blockw[i] = TEXTW(b) - lrpad + blocklpad[i];
+ for (i = 1; i < nblocks; ++i) {
+ blocklpad[i] -= r = blocklpad[i] / 2;
+ blockw[i] -= r;
+ blockw[i - 1] += r;
+ }
+ if (t)
+ blockw[nblocks - 1] += t * lrpad / 2;
+ pdrawbar(selmon);
+}
+
+void
+drawbar(Monitor *m)
+{
+ unsigned occ = 0, urg = 0;
+ unsigned i, j;
+ int x, w, lw;
+ Client *c;
+ char *b;
+
+ if (!m->showbar)
+ return;
+ for (c = m->clients; c; c = c->next) {
+ occ |= c->tags;
+ if (c->isurgent && !ISVISIBLE(c))
+ urg |= c->tags;
+ }
+ x = 0;
+ for (i = 0; i < LENGTH(tags); ++i) {
+ w = TEXTW(tags[i]);
+ drw_setscheme(drw, m->tagset & 1 << i ? schemesel : schemenorm);
+ drw_text(drw, x, 0, w, barh, lrpad / 2, tags[i], urg & 1 << i);
+ if (i == m->selws)
+ drw_rect(drw, x + boxw, barh - boxs,
+ w - boxw * 2, boxs, 1, 0);
+ if (occ & 1 << i)
+ drw_rect(drw, x + boxs, boxs, boxw, boxw,
+ m == selmon && selmon->sel
+ && (selmon->sel->tags & 1 << i),
+ urg & 1 << i);
+ x += w;
+ }
+ m->tagsw = x;
+ if (m->lyt) {
+ lw = TEXTW(m->lytdesc);
+ drw_setscheme(drw, schemenorm);
+ x = drw_text(drw, x, 0, lw, barh, lrpad / 2, m->lytdesc, 0);
+ } else {
+ lw = 0;
+ }
+ w = 0;
+ if (m == selmon && m->w > x) {
+ drw_setscheme(drw, schemenorm);
+ i = nblocks;
+ while (i && w + blockw[i - 1] <= m->w - x)
+ w += blockw[--i];
+ for (b = blocks, j = 0; j < i; ++j)
+ b += strlen(b) + 1;
+ x = m->w - w;
+ for (; i < nblocks; ++i) {
+ x = drw_text(drw, x, 0, blockw[i], barh,
+ blocklpad[i], b, 0);
+ b += strlen(b) + 1;
+ }
+ }
+ m->statusw = w;
+ x = m->tagsw + lw;
+ if (m->w > x + m->statusw) {
+ w = m->w - x - m->statusw;
+ if (m->sel) {
+ drw_setscheme(drw, m == selmon? schemesel: schemenorm);
+ drw_text(drw, x, 0, w, barh, lrpad / 2, m->sel->title, 0);
+ if (!m->sel->hintsvalid)
+ updatesizehints(m->sel);
+ if (m->sel->isfloating)
+ drw_rect(drw, x + boxs, boxs, boxw, boxw,
+ m->sel->isfixed, 0);
+ } else {
+ drw_setscheme(drw, schemenorm);
+ drw_rect(drw, x, 0, w, barh, 1, 1);
+ }
+ }
+ drw_map(drw, m->barwin, 0, 0, m->w, barh);
+}
+
+int
+posinbar(Monitor *m, int clkx, int *n)
+{
+ int x, i;
+
+ if (clkx >= m->w - m->statusw) {
+ if (!n)
+ return BarStatus;
+ for (x = m->w, i = nblocks; i--;) {
+ x -= blockw[i];
+ if (x <= clkx) {
+ *n = nblocks - i;
+ return BarStatus;
+ }
+ }
+ } else if (clkx < m->tagsw) {
+ if (!n)
+ return BarTags;
+ for (x = i = 0; i < LENGTH(tags); ++i) {
+ x += TEXTW(tags[i]);
+ if (x > clkx) {
+ *n = i;
+ return BarTags;
+ }
+ }
+ } else if (clkx < m->tagsw + TEXTW(m->lytdesc)) {
+ return BarLytDesc;
+ } else {
+ return BarWinTitle;
+ }
+ return -1;
+}
diff --git a/client.c b/client.c
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "swm.h"
+#include "util.h"
+
+static char *gettitle(Client *c, Atom atom);
+static Atom getatomprop(Client *c, Atom prop);
+static int xerrordummy(Display *dpy, XErrorEvent *ee);
+
+void
+attach(Client *c)
+{
+ c->next = c->mon->clients;
+ c->mon->clients = c;
+}
+
+void
+detach(Client *c)
+{
+ Client **tc;
+
+ for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next)
+ ;
+ if (*tc)
+ *tc = c->next;
+}
+
+void
+attachstack(Client *c)
+{
+ c->snext = c->mon->stack;
+ c->mon->stack = c;
+}
+
+void
+detachstack(Client *c)
+{
+ Client **tc;
+
+ for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext)
+ ;
+ if (*tc)
+ *tc = c->snext;
+}
+
+Client *
+wintoclient(Window w)
+{
+ Monitor *m;
+ Client *c;
+
+ for (m = mons; m; m = m->next)
+ for (c = m->stack; c; c = c->snext)
+ if (c->win == w)
+ return c;
+ return NULL;
+}
+
+Client *
+nextclient(Client *c)
+{
+ Client *r = c;
+
+ do {
+ r = r->next ? r->next : c->mon->clients;
+ } while (r != c && !ISVISIBLE(r));
+ return r;
+}
+
+Client *
+prevclient(Client *c)
+{
+ Client *r = c, *t;
+
+ for (t = c->mon->clients; t != c; t = t->next)
+ if (ISVISIBLE(t))
+ r = t;
+ if (r == c)
+ for (t = c->next; t; t = t->next)
+ if (ISVISIBLE(t))
+ r = t;
+ return r;
+}
+
+void
+notifyconfigure(Client *c)
+{
+ XConfigureEvent ce;
+
+ ce.type = ConfigureNotify;
+ ce.display = dpy;
+ ce.event = c->win;
+ ce.window = c->win;
+ if (c->isfullscreen) {
+ ce.x = c->mon->x;
+ ce.y = c->mon->y;
+ ce.width = c->mon->w;
+ ce.height = c->mon->h;
+ ce.border_width = 0;
+ } else {
+ ce.x = c->x;
+ ce.y = c->y;
+ ce.width = c->w;
+ ce.height = c->h;
+ ce.border_width = borderw;
+ }
+ if (c->ishidden)
+ ce.x = -WIDTH(c);
+ ce.above = None;
+ ce.override_redirect = False;
+ XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce);
+}
+
+void
+configure(Client *c, int resize)
+{
+ int mask = CWX|CWY|CWBorderWidth;
+ XWindowChanges wc;
+
+ wc.border_width = borderw;
+ if (c->isfullscreen) {
+ wc.x = c->mon->x;
+ wc.y = c->mon->y;
+ wc.width = c->mon->w;
+ wc.height = c->mon->h;
+ wc.border_width = 0;
+ } else {
+ wc.x = c->x;
+ wc.y = c->y;
+ wc.width = c->w;
+ wc.height = c->h;
+ wc.border_width = borderw;
+ }
+ if ((c->ishidden = !ISVISIBLE(c)))
+ wc.x = -WIDTH(c);
+ if (resize)
+ mask |= CWWidth|CWHeight;
+ XConfigureWindow(dpy, c->win, mask, &wc);
+ if (!resize)
+ notifyconfigure(c);
+}
+
+char *
+gettitle(Client *c, Atom prop)
+{
+ size_t max = c->bytes + sizeof(c->bytes) - c->title;
+ char **list = NULL;
+ XTextProperty text;
+ int n;
+
+ if (!XGetTextProperty(dpy, c->win, &text, prop) || !text.nitems)
+ return NULL;
+ if (text.encoding == XA_STRING) {
+ *stpncpy(c->title, (char *)text.value, max - 1) = '\0';
+ } else if (XmbTextPropertyToTextList(dpy, &text, &list, &n) >= Success
+ && n > 0 && *list) {
+ *stpncpy(c->title, *list, max - 1) = '\0';
+ XFreeStringList(list);
+ } else {
+ XFree(text.value);
+ return NULL;
+ }
+ XFree(text.value);
+ return c->title;
+}
+
+void
+updatetitle(Client *c)
+{
+ if (!gettitle(c, netatom[NetWMName]) && !gettitle(c, XA_WM_NAME))
+ *stpncpy(c->title, "broken",
+ c->bytes + sizeof(c->bytes) - c->title - 1) = '\0';
+}
+
+Atom
+getatomprop(Client *c, Atom prop)
+{
+ int di;
+ unsigned long dl;
+ unsigned char *p = NULL;
+ Atom da, atom = None;
+
+ if (XGetWindowProperty(dpy, c->win, prop, 0L,
+ sizeof(atom), False, XA_ATOM,
+ &da, &di, &dl, &dl, &p) == Success && p) {
+ atom = *(Atom *)p;
+ XFree(p);
+ }
+ return atom;
+}
+
+void
+updatewindowtype(Client *c)
+{
+ Atom state = getatomprop(c, netatom[NetWMState]);
+ Atom wtype = getatomprop(c, netatom[NetWMWindowType]);
+
+ if (state == netatom[NetWMStateFullscreen])
+ setfullscreen(c, 1);
+ if (wtype == netatom[NetWMWindowTypeDialog])
+ setfloating(c, 1);
+}
+
+void
+updatewmhints(Client *c)
+{
+ XWMHints *wmh;
+
+ if ((wmh = XGetWMHints(dpy, c->win))) {
+ if (c != selmon->sel || !(wmh->flags & XUrgencyHint))
+ seturgent(c, wmh->flags & XUrgencyHint);
+ c->neverfocus = wmh->flags & InputHint ? !wmh->input : 0;
+ XFree(wmh);
+ } else {
+ seturgent(c, 0);
+ c->neverfocus = 0;
+ }
+}
+
+void
+updatesizehints(Client *c)
+{
+ long msize;
+ XSizeHints size;
+
+ if (!XGetWMNormalHints(dpy, c->win, &size, &msize))
+ size.flags = 0;
+ if (size.flags & PBaseSize) {
+ c->basew = size.base_width;
+ c->baseh = size.base_height;
+ } else {
+ c->basew = c->baseh = 0;
+ }
+ if (size.flags & PMinSize) {
+ c->minw = size.min_width;
+ c->minh = size.min_height;
+ } else {
+ c->minw = c->basew;
+ c->minh = c->baseh;
+ }
+ if (size.flags & PMaxSize) {
+ c->maxw = size.max_width;
+ c->maxh = size.max_height;
+ } else {
+ c->maxw = c->maxh = 0;
+ }
+ if (size.flags & PResizeInc) {
+ c->incw = size.width_inc;
+ c->inch = size.height_inc;
+ } else {
+ c->incw = c->inch = 0;
+ }
+ if (size.flags & PAspect) {
+ c->mina = (float)size.min_aspect.x / size.min_aspect.y;
+ c->maxa = (float)size.max_aspect.x / size.max_aspect.y;
+ } else {
+ c->maxa = c->mina = -1;
+ }
+ c->hintsvalid = 1;
+ c->isfixed = c->maxw && c->maxw == c->minw
+ && c->maxh && c->maxh == c->minh;
+ if (c->isfixed && !c->isfloating)
+ setfloating(c, 1);
+}
+
+void
+applysizehints(Client *c, int *w, int *h)
+{
+ int basew, baseh, t;
+
+ if (!c->hintsvalid)
+ updatesizehints(c);
+ basew = c->basew;
+ baseh = c->baseh;
+ if (*w < c->minw)
+ *w = c->minw;
+ if (c->maxw && *w > c->maxw)
+ *w = c->maxw;
+ if (*h < c->minh)
+ *h = c->minh;
+ if (c->maxh && *h > c->maxh)
+ *h = c->maxh;
+ if (*w > basew && *h > baseh) {
+ *w -= basew;
+ *h -= baseh;
+ if (c->maxa > 0 && *w > (t = *h * c->maxa))
+ *w = t;
+ if (c->mina > 0 && *h > (t = *w / c->mina))
+ *h = t;
+ *w += basew;
+ *h += baseh;
+ }
+ if (!basew)
+ basew = c->minw;
+ if (!baseh)
+ baseh = c->minh;
+ if (*w > basew && *h > baseh) {
+ *w -= basew;
+ *h -= baseh;
+ if (c->incw) {
+ *w -= t = *w % c->incw;
+ if (t > c->incw / 2
+ && *w + c->incw <= c->maxw - basew)
+ *w += c->incw;
+ }
+ if (c->inch) {
+ *h -= t = *h % c->inch;
+ if (t > c->inch / 2
+ && *h + c->inch <= c->maxh - baseh)
+ *h += c->inch;
+ }
+ *w += basew;
+ *h += baseh;
+ }
+}
+
+void
+elevate(Client *c)
+{
+ XWindowChanges wc;
+
+ detachstack(c);
+ attachstack(c);
+ if (c->isfullscreen || !c->mon->lyt || c->isfloating) {
+ XRaiseWindow(dpy, c->win);
+ } else {
+ wc.sibling = c->mon->barwin;
+ wc.stack_mode = Below;
+ XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc);
+ }
+}
+
+int
+sendevent(Client *c, Atom proto)
+{
+ int n;
+ Atom *protocols;
+ XEvent ev;
+
+ if (!XGetWMProtocols(dpy, c->win, &protocols, &n))
+ return 0;
+ while (n-- && protocols[n] != proto)
+ ;
+ XFree(protocols);
+ if (n < 0)
+ return 0;
+ ev.type = ClientMessage;
+ ev.xclient.window = c->win;
+ ev.xclient.message_type = wmatom[WMProtocols];
+ ev.xclient.format = 32;
+ ev.xclient.data.l[0] = proto;
+ ev.xclient.data.l[1] = CurrentTime;
+ XSendEvent(dpy, c->win, False, NoEventMask, &ev);
+ return 1;
+}
+
+void
+setclientstate(Client *c, long state)
+{
+ long data[] = { state, None };
+
+ XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState],
+ 32, PropModeReplace, (unsigned char *)data, 2);
+}
+
+void
+setfloating(Client *c, int val)
+{
+ if ((c->isfloating = !!val)) {
+ applysizehints(c, &c->w, &c->h);
+ configure(c, 1);
+ }
+ parrange(c->mon);
+ if (c == c->mon->sel)
+ pdrawbar(c->mon);
+}
+
+void
+setfullscreen(Client *c, int val)
+{
+ if (val) {
+ XChangeProperty(dpy, c->win, netatom[NetWMState],
+ XA_ATOM, 32, PropModeReplace,
+ (unsigned char *)&netatom[NetWMStateFullscreen],
+ 1);
+ c->isfullscreen = 1;
+ configure(c, 1);
+ elevate(c);
+ } else {
+ XChangeProperty(dpy, c->win, netatom[NetWMState],
+ XA_ATOM, 32, PropModeReplace, NULL, 0);
+ c->isfullscreen = 0;
+ configure(c, 1);
+ parrange(c->mon);
+ pdrawbar(c->mon);
+ }
+}
+
+void
+setscratch(Client *c, int val)
+{
+ if (!val && !(c->tags & TAGMASK))
+ return;
+ c->isscratch = !!val;
+ if (val && c->isfloating) {
+ c->x = c->mon->x + (c->mon->w - WIDTH(c)) / 2;
+ c->y = c->mon->y + (c->mon->h - HEIGHT(c)) / 2;
+ configure(c, 0);
+ }
+}
+
+void
+seturgent(Client *c, int val)
+{
+ c->isurgent = !!val;
+ if (c != selmon->sel)
+ XSetWindowBorder(dpy, c->win, val ? pixelurgent : pixelunfocus);
+ pdrawbar(c->mon);
+}
+
+void
+focus(Client *c)
+{
+ while (c && !ISVISIBLE(c))
+ c = c->snext;
+ if (selmon->sel) {
+ grabbuttons(selmon->sel, 0);
+ XSetWindowBorder(dpy, selmon->sel->win, selmon->sel->isurgent ?
+ pixelurgent : pixelunfocus);
+ selmon->sel = NULL;
+ }
+ if (c) {
+ if (selmon != c->mon) {
+ pdrawbar(selmon);
+ selmon = c->mon;
+ }
+ elevate(c);
+ grabbuttons(c, 1);
+ XSetWindowBorder(dpy, c->win, pixelfocus);
+ if (!c->neverfocus) {
+ XSetInputFocus(dpy, c->win, RevertToPointerRoot,
+ CurrentTime);
+ XChangeProperty(dpy, root, netatom[NetActiveWindow],
+ XA_WINDOW, 32, PropModeReplace,
+ (unsigned char *)&(c->win), 1);
+ }
+ sendevent(c, wmatom[WMTakeFocus]);
+ } else {
+ XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
+ XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
+ }
+ selmon->sel = c;
+ pdrawbar(selmon);
+}
+
+void
+sendtomon(Client *c, Monitor *m)
+{
+ if (c->mon == m)
+ return;
+ parrange(c->mon);
+ parrange(m);
+ if (c == selmon->sel)
+ focus(c->snext);
+ detach(c);
+ detachstack(c);
+ c->mon = m;
+ c->tags = 1 << m->selws;
+ attach(c);
+ attachstack(c);
+}
+
+Client *
+manage(Window w, XWindowAttributes *wa)
+{
+ XClassHint ch = { NULL, NULL };
+ Client *c, *t = NULL;
+ int istrans;
+ Window tw;
+
+ if (!(c = malloc(sizeof(*c)))) {
+ warn("malloc:");
+ return NULL;
+ }
+ c->win = w;
+ c->x = wa->x;
+ c->y = wa->y;
+ c->w = wa->width;
+ c->h = wa->height;
+ c->isfloating = c->isfullscreen = c->isscratch = c->isurgent = 0;
+ istrans = !!XGetTransientForHint(dpy, w, &tw);
+ if (istrans && (t = wintoclient(tw))) {
+ c->mon = t->mon;
+ c->tags = t->tags ? t->tags : 1 << selmon->selws;
+ } else {
+ c->mon = selmon;
+ c->tags = 1 << selmon->selws;
+ }
+ if (c->x < 0)
+ c->x = 0;
+ else if (c->x > sw - WIDTH(c))
+ c->x = sw - WIDTH(c);
+ if (c->y < 0)
+ c->y = 0;
+ else if (c->y > sh - HEIGHT(c))
+ c->y = sh - HEIGHT(c);
+ XGetClassHint(dpy, c->win, &ch);
+ c->appclass = c->bytes;
+ c->appname = stpncpy(c->appclass, ch.res_class ? ch.res_class : "",
+ sizeof(c->bytes) - 3);
+ *c->appname++ = '\0';
+ c->title = stpncpy(c->appname, ch.res_name ? ch.res_name : "",
+ c->bytes + sizeof(c->bytes) - c->appname - 2);
+ *c->title++ = '\0';
+ if (ch.res_class)
+ XFree(ch.res_class);
+ if (ch.res_name)
+ XFree(ch.res_name);
+ updatetitle(c);
+ updatesizehints(c);
+ updatewmhints(c);
+ updatewindowtype(c);
+ if (istrans && !c->isfloating)
+ setfloating(c, 1);
+ attach(c);
+ attachstack(c);
+ applyrules(c);
+ XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask
+ |PropertyChangeMask|StructureNotifyMask);
+ setclientstate(c, NormalState);
+ configure(c, 0);
+ if (wa->map_state == IsViewable) {
+ parrange(c->mon);
+ } else {
+ arrange(c->mon);
+ XMapWindow(dpy, c->win);
+ }
+ focus(selmon->stack);
+ if (c != selmon->sel) {
+ grabbuttons(c, 0);
+ if (!c->isurgent)
+ XSetWindowBorder(dpy, w, pixelunfocus);
+ }
+ pdrawbar(c->mon);
+ return c;
+}
+
+int
+xerrordummy(Display *dpy, XErrorEvent *ee)
+{
+ return 0;
+}
+
+void
+unmanage(Client *c, int destroyed)
+{
+ XWindowChanges wc;
+ int (*xerror)(Display *, XErrorEvent *);
+
+ detach(c);
+ detachstack(c);
+ if (selmon->sel == c) {
+ selmon->sel = NULL;
+ focus(selmon->stack);
+ }
+ if (!destroyed) {
+ wc.border_width = 0;
+ XGrabServer(dpy); /* avoid race conditions */
+ xerror = XSetErrorHandler(xerrordummy);
+ XSelectInput(dpy, c->win, NoEventMask);
+ XConfigureWindow(dpy, c->win, CWBorderWidth, &wc);
+ XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
+ setclientstate(c, WithdrawnState);
+ XSync(dpy, False);
+ XSetErrorHandler(xerror);
+ XUngrabServer(dpy);
+ }
+ parrange(c->mon);
+ free(c);
+}
diff --git a/config.c b/config.c
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <X11/keysym.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "swm.h"
+#include "util.h"
+
+#define ARGV(...) ((char **)(const char *[]){__VA_ARGS__, NULL})
+#define SPAWN(...) spawn(ARGV(__VA_ARGS__))
+#define CLEANMASK(X) ((X) & ~lockmasks[3] & (ShiftMask|ControlMask|MODMASK))
+
+static void gapmenu(void);
+static void lytmenu(void);
+
+const unsigned borderw = 3; /* border width of windows in pixels */
+const unsigned snap = 32; /* snap distance in pixels */
+
+/* i = inner, o = outer, h = horizontal, v = vertical */
+const int gapih = 15;
+const int gapiv = 10;
+const int gapoh = 15;
+const int gapov = 20;
+
+static const char *fonts_[] = {
+ "monospace:size=11:antialias=true:autohint=true",
+ "Font Awesome 5 Free Solid:size=11:antialias=true:autohint=true",
+};
+const char **fonts = fonts_;
+const size_t nfonts = LENGTH(fonts_);
+
+/* n = normal, s = selected, f = foreground, b = background */
+const char *colnf = "#bbbbbb";
+const char *colnb = "#222222";
+const char *colsf = "#eeeeee";
+const char *colsb = "#113355";
+
+/* border color for focused, unfocused or urgent windows */
+const char *colfocus = "#770000";
+const char *colunfocus = "#444444";
+const char *colurgent = "#ffff00";
+
+static const char rightstack[] = "[*]:.55 []";
+static const char monocle[] = "()";
+
+const char *defaultlayout = rightstack;
+
+char **statusprog = ARGV("infobar");
+
+static char **scratchterm = ARGV("st", "-n", "scratchpad", "-ig", "80x24");
+
+void
+applyrules(Client *c)
+{
+ /*
+ * In xprop(1)'s output:
+ * WM_CLASS(STRING) = app name, app class
+ * WM_NAME(STRING) = title
+ */
+ if (!strcmp(c->appname, "scratchpad"))
+ setscratch(c, 1);
+ if (!strcmp(c->appclass, "Gimp"))
+ setfloating(c, 1);
+ else if (!strcmp(c->appclass, "Firefox"))
+ c->tags = 1 << (LENGTH(tags) - 1);
+}
+
+void
+execmenu(void)
+{
+ execlp("dmenu", "dmenu", "-l", "20",
+ "-m", (char []){'0' + selmon->num, '\0'},
+ "-nf", colnf, "-sf", colsf,
+ "-nb", colnb, "-sb", colsb,
+ "-fn", fonts[0], NULL);
+ die("execlp `dmenu':");
+}
+
+void
+gapmenu(void)
+{
+ struct {
+ char *s;
+ int d;
+ int *p;
+ } a[] = {
+ { "ih", gapih, &selmon->gapih },
+ { "iv", gapiv, &selmon->gapiv },
+ { "oh", gapoh, &selmon->gapoh },
+ { "ov", gapov, &selmon->gapov },
+ };
+ char *r, s[3], t[3];
+ int i, n;
+
+ newmenu();
+ for (i = 0; i < 4; ++i) {
+ menuput("gap%s = %d", a[i].s, a[i].d);
+ menuput("gap%s += %d", a[i].s, borderw);
+ menuput("gap%s -= %d", a[i].s, borderw);
+ }
+ while ((r = menuget())) {
+ if (sscanf(r, "gap%2s %2s %d", s, t, &n) != 3)
+ continue;
+ for (i = 0; i < 4 && strcmp(a[i].s, s); ++i)
+ ;
+ if (i == 4)
+ continue;
+ switch (*t) {
+ case '=': *a[i].p = n; break;
+ case '+': *a[i].p += n; break;
+ case '-': *a[i].p -= n; break;
+ }
+ }
+ lyttile(selmon);
+}
+
+void
+lytmenu(void)
+{
+ char *s, *t;
+
+ newmenu();
+ menuput("%s # right stack", rightstack);
+ menuput("%s # monocle", monocle);
+ menuput(" [*]:.55 () # right deck");
+ menuput("[{*}:.55 {}] # bottom stack");
+ menuput(".62 [.62 -{-[() .62] .62}] # fibonacci spiral");
+ menuput(".62 [.62 {.62 [.62 ()]}] # fibonacci dwindle");
+ menuput("-{[*] [*]:.76} []:.19 # centered master");
+ menuput("({*}:.55 {}) # centered pseudo-floating master");
+ while ((s = menuget())) {
+ if ((t = strchr(s, '#')))
+ *t = '\0';
+ setlayout(s);
+ }
+}
+
+#define TAGKEYS \
+ case XK_1: case XK_2: case XK_3: \
+ case XK_4: case XK_5: case XK_6: \
+ case XK_7: case XK_8: case XK_9
+
+#define NEXTWS(X) ((X) < LENGTH(tags) - 1 ? (X) + 1 : 0)
+#define PREVWS(X) ((X) ? (X) - 1 : LENGTH(tags) - 1)
+
+void
+keypress(XEvent *e)
+{
+ XKeyEvent *ev = &e->xkey;
+ KeySym keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0);
+
+ switch (CLEANMASK(ev->state)) {
+ case MODMASK:
+ switch (keysym) {
+ TAGKEYS:
+ setworkspace(keysym - XK_1);
+ break;
+ case XK_comma:
+ setworkspace(PREVWS(selmon->selws));
+ break;
+ case XK_semicolon:
+ setworkspace(NEXTWS(selmon->selws));
+ break;
+ case XK_Tab:
+ setworkspace(selmon->altws);
+ break;
+ case XK_j:
+ case XK_k:
+ if (!selmon->sel) {
+ focus(selmon->stack);
+ break;
+ }
+ if (selmon->sel->isfullscreen)
+ break;
+ if (keysym == XK_j)
+ focus(nextclient(selmon->sel));
+ else
+ focus(prevclient(selmon->sel));
+ break;
+ case XK_Return:
+ SPAWN("st");
+ break;
+ case XK_apostrophe:
+ togglescratch(scratchterm);
+ break;
+ case XK_Escape:
+ SPAWN("slock");
+ sleep(1);
+ SPAWN("xset", "dpms", "force", "standby");
+ break;
+ case XK_p:
+ SPAWN("dmenu_run",
+ "-m", (char []){'0' + selmon->num, '\0'},
+ "-nf", colnf, "-nb", colnb,
+ "-sf", colsf, "-sb", colsb,
+ "-fn", fonts[0]);
+ break;
+ case XK_w:
+ SPAWN("firefox");
+ break;
+ case XK_d:
+ case XK_i:
+ incnmaster(keysym == XK_i ? 1 : -1);
+ break;
+ case XK_h:
+ case XK_l:
+ incmfact(keysym == XK_l ? 0.05 : -0.05);
+ break;
+ case XK_m:
+ setlayout(monocle);
+ break;
+ case XK_t:
+ setlayout(rightstack);
+ break;
+ case XK_y:
+ lytmenu();
+ break;
+ case XK_b:
+ selmon->showbar = !selmon->showbar;
+ XMoveWindow(dpy, selmon->barwin, selmon->x, BY(selmon));
+ parrange(selmon);
+ break;
+ case XK_g:
+ gapmenu();
+ break;
+ case XK_bracketleft:
+ focusmon(prevmon(selmon));
+ break;
+ case XK_bracketright:
+ focusmon(nextmon(selmon));
+ break;
+ case XK_period:
+ cyclescratch();
+ break;
+ default:
+ if (selmon->sel)
+ goto needfocus1;
+ break;
+ }
+ break;
+needfocus1:
+ switch (keysym) {
+ case XK_BackSpace:
+ if (!sendevent(selmon->sel, wmatom[WMDeleteWindow]))
+ XKillClient(dpy, selmon->sel->win);
+ break;
+ case XK_f:
+ setfullscreen(selmon->sel, !selmon->sel->isfullscreen);
+ break;
+ case XK_grave:
+ setscratch(selmon->sel, !selmon->sel->isscratch);
+ break;
+ case XK_space:
+ swap(selmon->sel, selmon->clients);
+ break;
+ }
+ break;
+ case MODMASK|ShiftMask:
+ switch (keysym) {
+ case XK_f:
+ setlayout(NULL);
+ break;
+ case XK_g:
+ selmon->nogaps = !selmon->nogaps;
+ lyttile(selmon);
+ break;
+ case XK_Return:
+ spawn(scratchterm);
+ break;
+ case XK_r:
+ restart = 1;
+ /* FALLTHROUGH */
+ case XK_q:
+ running = 0;
+ break;
+ case XK_s:
+ sfdr < 0 ? runsprog() : endsprog();
+ updatestatus();
+ break;
+ default:
+ if (selmon->sel)
+ goto needfocus2;
+ break;
+ }
+ break;
+needfocus2:
+ switch (keysym) {
+ case XK_0:
+ tag(~0);
+ break;
+ TAGKEYS:
+ tag(1 << (keysym - XK_1));
+ break;
+ case XK_BackSpace:
+ XKillClient(dpy, selmon->sel->win);
+ break;
+ case XK_bracketleft:
+ sendtomon(selmon->sel, prevmon(selmon));
+ break;
+ case XK_bracketright:
+ sendtomon(selmon->sel, nextmon(selmon));
+ break;
+ case XK_comma:
+ tag(1 << PREVWS(selmon->selws));
+ break;
+ case XK_semicolon:
+ tag(1 << NEXTWS(selmon->selws));
+ break;
+ case XK_j:
+ swap(selmon->sel, nextclient(selmon->sel));
+ break;
+ case XK_k:
+ swap(selmon->sel, prevclient(selmon->sel));
+ break;
+ case XK_space:
+ setfloating(selmon->sel, !selmon->sel->isfloating);
+ break;
+ }
+ break;
+ case MODMASK|ControlMask:
+ switch (keysym) {
+ case XK_0:
+ view(selmon->tagset ^ ~0);
+ break;
+ TAGKEYS:
+ view(selmon->tagset ^ (1 << (keysym - XK_1)));
+ break;
+ }
+ break;
+ case MODMASK|ControlMask|ShiftMask:
+ if (!selmon->sel)
+ break;
+ switch (keysym) {
+ case XK_0:
+ tag(selmon->sel->tags ^ ~0);
+ break;
+ TAGKEYS:
+ tag(selmon->sel->tags ^ (1 << (keysym - XK_1)));
+ break;
+ }
+ break;
+ }
+}
+
+void
+buttonpress(XEvent *e)
+{
+ XButtonPressedEvent *ev = &e->xbutton;
+ Monitor *m;
+ Client *c;
+ unsigned mod;
+ int n;
+
+ for (m = mons; m && m->barwin != ev->window; m = m->next)
+ ;
+ if (m) {
+ focusmon(m);
+ mod = CLEANMASK(ev->state);
+ switch (posinbar(m, ev->x, &n)) {
+ case BarTags:
+ if (!mod) {
+ if (ev->button == Button1)
+ setworkspace(n);
+ else if (ev->button == Button3)
+ view(selmon->tagset ^ (1 << n));
+ } else if (mod == MODMASK) {
+ if (ev->button == Button1)
+ tag(1 << n);
+ else if (ev->button == Button3 && selmon->sel)
+ tag(selmon->sel->tags ^ (1 << n));
+ }
+ break;
+ case BarLytDesc:
+ if (!mod) {
+ if (ev->button == Button1)
+ setlayout(rightstack);
+ else if (ev->button == Button3)
+ setlayout(monocle);
+ }
+ break;
+ case BarWinTitle:
+ if (!mod && ev->button == Button2 && selmon->sel)
+ swap(selmon->sel, selmon->clients);
+ break;
+ case BarStatus:
+ {
+ char m[5], *p = m;
+
+ *p++ = ' ';
+ if (mod & ControlMask)
+ *p++ = 'C';
+ if (mod & ShiftMask)
+ *p++ = 'S';
+ if (mod & MODMASK)
+ *p++ = 'M';
+ p[p[-1] == ' ' ? -1 : 0] = '\0';
+ dprintf(sfdw, "%d %d%s\n", n, ev->button, m);
+ }
+ break;
+ }
+ } else {
+ if ((c = wintoclient(ev->window))) {
+ if (selmon->sel != c)
+ focus(c);
+ XAllowEvents(dpy, ReplayPointer, CurrentTime);
+ }
+ if (CLEANMASK(ev->state) == MODMASK) {
+ switch (ev->button) {
+ case Button1:
+ movebymouse();
+ break;
+ case Button2:
+ if (c)
+ setfloating(c, !c->isfloating);
+ break;
+ case Button3:
+ resizebymouse();
+ break;
+ case Button4:
+ case Button5:
+ n = ev->button == Button4 ? borderw : -borderw;
+ selmon->gapih += n;
+ selmon->gapiv += n;
+ selmon->gapoh += n;
+ selmon->gapov += n;
+ lyttile(selmon);
+ break;
+ }
+ }
+ }
+}
diff --git a/config.h b/config.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+
+#define MODMASK Mod4Mask
+
+#define NOGAPS 0 /* 1 means no gaps initially */
+#define TOPBAR 1 /* 0 means bottom bar */
+#define SHOWBAR 1 /* 0 means no bar initially */
+#define RESIZEHINTS 1 /* 1 means respect size hints in tiled resizals */
+
+#define TAGS { "1", "2", "3", "4", "5", "6", "7", "8", "9" }
diff --git a/config.mk b/config.mk
@@ -0,0 +1,28 @@
+PREFIX = /usr/local
+
+X11INC = /usr/X11R6/include
+X11LIB = /usr/X11R6/lib
+
+# comment out to disable Xinerama
+XINERAMALIBS = -lXinerama
+XINERAMAFLAGS = -DXINERAMA
+
+FREETYPELIBS = -lfontconfig -lXft
+FREETYPEINC = /usr/include/freetype2
+
+# uncomment for OpenBSD
+#FREETYPEINC = ${X11INC}/freetype2
+
+INCS = -I${X11INC} -I${FREETYPEINC}
+LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS}
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L ${XINERAMAFLAGS}
+#CFLAGS = -g -pedantic -Wall -O0 ${INCS} ${CPPFLAGS}
+CFLAGS = -pedantic -Wall -Os ${INCS} ${CPPFLAGS} -Wno-deprecated-declarations
+LDFLAGS = ${LIBS}
+
+# uncomment for Solaris
+#CFLAGS = -fast ${INCS}
+#LDFLAGS = ${LIBS}
+
+CC = c99
diff --git a/drw.c b/drw.c
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xlib.h>
+#include <X11/Xft/Xft.h>
+
+#include "drw.h"
+#include "util.h"
+
+#define UTF_INVALID 0xFFFD
+#define UTF_SIZ 4
+
+static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
+static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
+static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+
+static long
+utf8decodebyte(const char c, size_t *i)
+{
+ for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
+ if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
+ return (unsigned char)c & ~utfmask[*i];
+ return 0;
+}
+
+static size_t
+utf8validate(long *u, size_t i)
+{
+ if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
+ *u = UTF_INVALID;
+ for (i = 1; *u > utfmax[i]; ++i)
+ ;
+ return i;
+}
+
+static size_t
+utf8decode(const char *c, long *u, size_t clen)
+{
+ size_t i, j, len, type;
+ long udecoded;
+
+ *u = UTF_INVALID;
+ if (!clen)
+ return 0;
+ udecoded = utf8decodebyte(c[0], &len);
+ if (!BETWEEN(len, 1, UTF_SIZ))
+ return 1;
+ for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
+ udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
+ if (type)
+ return j;
+ }
+ if (j < len)
+ return 0;
+ *u = udecoded;
+ utf8validate(u, len);
+
+ return len;
+}
+
+Drw *
+drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
+{
+ Drw *drw;
+
+ if (!(drw = malloc(sizeof(*drw))))
+ return NULL;
+ drw->dpy = dpy;
+ drw->screen = screen;
+ drw->root = root;
+ drw->w = w;
+ drw->h = h;
+ drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
+ drw->gc = XCreateGC(dpy, root, 0, NULL);
+ XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
+
+ return drw;
+}
+
+void
+drw_resize(Drw *drw, unsigned int w, unsigned int h)
+{
+ if (!drw)
+ return;
+
+ drw->w = w;
+ drw->h = h;
+ if (drw->drawable)
+ XFreePixmap(drw->dpy, drw->drawable);
+ drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen));
+}
+
+void
+drw_free(Drw *drw)
+{
+ XFreePixmap(drw->dpy, drw->drawable);
+ XFreeGC(drw->dpy, drw->gc);
+ drw_fontset_free(drw->fonts);
+ free(drw);
+}
+
+/* This function is an implementation detail. Library users should use
+ * drw_fontset_create instead.
+ */
+static Fnt *
+xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)
+{
+ Fnt *font;
+ XftFont *xfont = NULL;
+ FcPattern *pattern = NULL;
+
+ if (fontname) {
+ if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
+ fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname);
+ return NULL;
+ }
+ if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
+ fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname);
+ XftFontClose(drw->dpy, xfont);
+ return NULL;
+ }
+ } else if (fontpattern) {
+ if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
+ fprintf(stderr, "error, cannot load font from pattern.\n");
+ return NULL;
+ }
+ } else {
+ warn("no font specified");
+ return NULL;
+ }
+
+ if (!(font = malloc(sizeof(*font)))) {
+ warn("malloc:");
+ return NULL;
+ }
+ font->dpy = drw->dpy;
+ font->h = xfont->ascent + xfont->descent;
+ font->xfont = xfont;
+ font->pattern = pattern;
+ font->next = NULL;
+
+ return font;
+}
+
+static void
+xfont_free(Fnt *font)
+{
+ if (!font)
+ return;
+ if (font->pattern)
+ FcPatternDestroy(font->pattern);
+ XftFontClose(font->dpy, font->xfont);
+ free(font);
+}
+
+Fnt *
+drw_fontset_create(Drw* drw, const char *const fonts[], size_t fontcount)
+{
+ Fnt *cur, *ret = NULL;
+ size_t i;
+
+ if (!drw || !fonts)
+ return NULL;
+
+ for (i = 1; i <= fontcount; i++) {
+ if (!(cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
+ drw_fontset_free(ret);
+ return NULL;
+ }
+ cur->next = ret;
+ ret = cur;
+ }
+ return (drw->fonts = ret);
+}
+
+void
+drw_fontset_free(Fnt *font)
+{
+ if (font) {
+ drw_fontset_free(font->next);
+ xfont_free(font);
+ }
+}
+
+Clr *
+drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
+{
+ if (!drw || !dest || !clrname)
+ return NULL;
+
+ if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
+ DefaultColormap(drw->dpy, drw->screen),
+ clrname, dest)) {
+ warn("cannot allocate color `%s'", clrname);
+ return NULL;
+ }
+ return dest;
+}
+
+/* Wrapper to create color schemes. The caller has to call free(3) on the
+ * returned color scheme when done using it. */
+Scm *
+drw_scm_create(Drw *drw, const char *fg, const char *bg)
+{
+ Scm *ret;
+
+ if (!drw || !fg || !bg)
+ return NULL;
+
+ if (!(ret = malloc(sizeof(*ret)))) {
+ warn("malloc:");
+ return NULL;
+ }
+
+ if (!drw_clr_create(drw, &ret->fg, fg)
+ || !drw_clr_create(drw, &ret->bg, bg)) {
+ free(ret);
+ return NULL;
+ }
+ return ret;
+}
+
+void
+drw_setfontset(Drw *drw, Fnt *set)
+{
+ if (drw)
+ drw->fonts = set;
+}
+
+void
+drw_setscheme(Drw *drw, Scm *scm)
+{
+ if (drw)
+ drw->scheme = scm;
+}
+
+void
+drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)
+{
+ if (!drw || !drw->scheme)
+ return;
+ XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme->bg.pixel : drw->scheme->fg.pixel);
+ if (filled)
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ else
+ XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
+}
+
+int
+drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
+{
+ int i, ty, ellipsis_x = 0;
+ unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len;
+ XftDraw *d = NULL;
+ Fnt *usedfont, *curfont, *nextfont;
+ int utf8strlen, utf8charlen, render = x || y || w || h;
+ long utf8codepoint = 0;
+ const char *utf8str;
+ FcCharSet *fccharset;
+ FcPattern *fcpattern;
+ FcPattern *match;
+ XftResult result;
+ int charexists = 0, overflow = 0;
+ /* keep track of a couple codepoints for which we have no match. */
+ enum { nomatches_len = 64 };
+ static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches;
+ static unsigned int ellipsis_width = 0;
+
+ if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts)
+ return 0;
+
+ if (!render) {
+ w = invert ? invert : ~invert;
+ } else {
+ XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme->fg.pixel : drw->scheme->bg.pixel);
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ if (w < lpad) /* avoid underflowing w */
+ return x + w;
+ d = XftDrawCreate(drw->dpy, drw->drawable,
+ DefaultVisual(drw->dpy, drw->screen),
+ DefaultColormap(drw->dpy, drw->screen));
+ x += lpad;
+ w -= lpad;
+ }
+
+ usedfont = drw->fonts;
+ if (!ellipsis_width && render)
+ ellipsis_width = drw_fontset_getwidth(drw, "...");
+ while (1) {
+ ew = ellipsis_len = utf8strlen = 0;
+ utf8str = text;
+ nextfont = NULL;
+ while (*text) {
+ utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
+ for (curfont = drw->fonts; curfont; curfont = curfont->next) {
+ charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
+ if (charexists) {
+ drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL);
+ if (ew + ellipsis_width <= w) {
+ /* keep track where the ellipsis still fits */
+ ellipsis_x = x + ew;
+ ellipsis_w = w - ew;
+ ellipsis_len = utf8strlen;
+ }
+
+ if (ew + tmpw > w) {
+ overflow = 1;
+ /* called from drw_fontset_getwidth_clamp():
+ * it wants the width AFTER the overflow
+ */
+ if (!render)
+ x += tmpw;
+ else
+ utf8strlen = ellipsis_len;
+ } else if (curfont == usedfont) {
+ utf8strlen += utf8charlen;
+ text += utf8charlen;
+ ew += tmpw;
+ } else {
+ nextfont = curfont;
+ }
+ break;
+ }
+ }
+
+ if (overflow || !charexists || nextfont)
+ break;
+ else
+ charexists = 0;
+ }
+
+ if (utf8strlen) {
+ if (render) {
+ ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
+ XftDrawStringUtf8(d, invert ? &drw->scheme->bg : &drw->scheme->fg,
+ usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen);
+ }
+ x += ew;
+ w -= ew;
+ }
+ if (render && overflow)
+ drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert);
+
+ if (!*text || overflow) {
+ break;
+ } else if (nextfont) {
+ charexists = 0;
+ usedfont = nextfont;
+ } else {
+ /* Regardless of whether or not a fallback font is found, the
+ * character must be drawn.
+ */
+ charexists = 1;
+
+ for (i = 0; i < nomatches_len; ++i) {
+ /* avoid calling XftFontMatch if we know we won't find a match */
+ if (utf8codepoint == nomatches.codepoint[i])
+ goto no_match;
+ }
+
+ fccharset = FcCharSetCreate();
+ FcCharSetAddChar(fccharset, utf8codepoint);
+
+ if (!drw->fonts->pattern) {
+ /* Refer to the comment in xfont_create for more information. */
+ die("the first font in the cache must be loaded from a font string.");
+ }
+
+ fcpattern = FcPatternDuplicate(drw->fonts->pattern);
+ FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
+ FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
+
+ FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
+ FcDefaultSubstitute(fcpattern);
+ match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
+
+ FcCharSetDestroy(fccharset);
+ FcPatternDestroy(fcpattern);
+
+ if (match) {
+ usedfont = xfont_create(drw, NULL, match);
+ if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
+ for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
+ ; /* NOP */
+ curfont->next = usedfont;
+ } else {
+ xfont_free(usedfont);
+ nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint;
+no_match:
+ usedfont = drw->fonts;
+ }
+ }
+ }
+ }
+ if (d)
+ XftDrawDestroy(d);
+
+ return x + (render ? w : 0);
+}
+
+void
+drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
+{
+ if (!drw)
+ return;
+
+ XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
+ XSync(drw->dpy, False);
+}
+
+unsigned int
+drw_fontset_getwidth(Drw *drw, const char *text)
+{
+ if (!drw || !drw->fonts || !text)
+ return 0;
+ return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
+}
+
+unsigned int
+drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n)
+{
+ unsigned int tmp = 0;
+ if (drw && drw->fonts && text && n)
+ tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n);
+ return MIN(n, tmp);
+}
+
+void
+drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h)
+{
+ XGlyphInfo ext;
+
+ if (!font || !text)
+ return;
+
+ XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
+ if (w)
+ *w = ext.xOff;
+ if (h)
+ *h = font->h;
+}
diff --git a/drw.h b/drw.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+
+typedef struct Fnt {
+ Display *dpy;
+ unsigned int h;
+ struct _XftFont *xfont;
+ struct _FcPattern *pattern;
+ struct Fnt *next;
+} Fnt;
+
+typedef XftColor Clr;
+
+typedef struct {
+ Clr fg, bg;
+} Scm;
+
+typedef struct {
+ unsigned int w, h;
+ Display *dpy;
+ int screen;
+ Window root;
+ Drawable drawable;
+ GC gc;
+ Scm *scheme;
+ Fnt *fonts;
+} Drw;
+
+/* drawable abstraction */
+Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h);
+void drw_resize(Drw *drw, unsigned int w, unsigned int h);
+void drw_free(Drw *drw);
+
+/* font abstraction */
+Fnt *drw_fontset_create(Drw* drw, const char *const fonts[], size_t fontcount);
+void drw_fontset_free(Fnt* set);
+unsigned int drw_fontset_getwidth(Drw *drw, const char *text);
+unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n);
+void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h);
+
+/* colorscheme abstraction */
+Clr *drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
+Scm *drw_scm_create(Drw *drw, const char *fg, const char *bg);
+
+/* drawing context manipulation */
+void drw_setfontset(Drw *drw, Fnt *set);
+void drw_setscheme(Drw *drw, Scm *scm);
+
+/* drawing functions */
+void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert);
+int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
+
+/* Map functions */
+void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
diff --git a/evt.c b/evt.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "swm.h"
+#include "util.h"
+
+static void clientmessage(XEvent *e);
+static void configurenotify(XEvent *e);
+static void configurerequest(XEvent *e);
+static void destroynotify(XEvent *e);
+static void enternotify(XEvent *e);
+static void expose(XEvent *e);
+static void focusin(XEvent *e);
+static void mappingnotify(XEvent *e);
+static void maprequest(XEvent *e);
+static void motionnotify(XEvent *e);
+static void propertynotify(XEvent *e);
+static void unmapnotify(XEvent *e);
+
+void (*handler[LASTEvent]) (XEvent *) = {
+ [ButtonPress] = buttonpress,
+ [ClientMessage] = clientmessage,
+ [ConfigureNotify] = configurenotify,
+ [ConfigureRequest] = configurerequest,
+ [DestroyNotify] = destroynotify,
+ [EnterNotify] = enternotify,
+ [Expose] = expose,
+ [FocusIn] = focusin,
+ [KeyPress] = keypress,
+ [MappingNotify] = mappingnotify,
+ [MapRequest] = maprequest,
+ [MotionNotify] = motionnotify,
+ [PropertyNotify] = propertynotify,
+ [UnmapNotify] = unmapnotify,
+};
+
+void
+clientmessage(XEvent *e)
+{
+ XClientMessageEvent *ev = &e->xclient;
+ Client *c = wintoclient(ev->window);
+
+ if (!c)
+ return;
+ if (ev->message_type == netatom[NetWMState]) {
+ if (ev->data.l[1] == netatom[NetWMStateFullscreen]
+ || ev->data.l[2] == netatom[NetWMStateFullscreen])
+ setfullscreen(c, ev->data.l[0] < 2 ? ev->data.l[0]
+ : !c->isfullscreen);
+ } else if (ev->message_type == netatom[NetActiveWindow]) {
+ if (c != selmon->sel)
+ seturgent(c, 1);
+ }
+}
+
+void
+configurenotify(XEvent *e)
+{
+ XConfigureEvent *ev = &e->xconfigure;
+ Monitor *m;
+ Client *c;
+ int t, dirty;
+
+ if (ev->window == root) {
+ dirty = (sw != ev->width || sh != ev->height);
+ sw = ev->width;
+ sh = ev->height;
+ if ((t = updategeom()) < 0) {
+ warn("cannot update geometry information");
+ return;
+ }
+ if (!dirty && !t)
+ return;
+ updatedrawable();
+ for (m = mons; m; m = m->next) {
+ if (!m->barwin)
+ initbar(m);
+ XMoveResizeWindow(dpy, m->barwin, m->x, BY(m), m->w, barh);
+ for (c = m->clients; c; c = c->next)
+ configure(c, 1);
+ parrange(m);
+ }
+ focus(selmon->stack);
+ }
+}
+
+void
+configurerequest(XEvent *e)
+{
+ XConfigureRequestEvent *ev = &e->xconfigurerequest;
+ Client *c;
+ Monitor *m;
+ XWindowChanges wc;
+
+ if ((c = wintoclient(ev->window))) {
+ if (!c->isfullscreen && (c->isfloating || !selmon->lyt)) {
+ m = c->mon;
+ if (ev->value_mask & CWX)
+ c->x = m->x + ev->x;
+ if (ev->value_mask & CWY)
+ c->y = m->y + ev->y;
+ if (ev->value_mask & CWWidth)
+ c->w = ev->width;
+ if (ev->value_mask & CWHeight)
+ c->h = ev->height;
+ if (ev->value_mask & (CWX|CWY|CWWidth|CWHeight))
+ configure(c, ev->value_mask & (CWWidth|CWHeight));
+ } else {
+ notifyconfigure(c);
+ }
+ } else {
+ wc.x = ev->x;
+ wc.y = ev->y;
+ wc.width = ev->width;
+ wc.height = ev->height;
+ wc.border_width = ev->border_width;
+ wc.sibling = ev->above;
+ wc.stack_mode = ev->detail;
+ XConfigureWindow(dpy, ev->window, ev->value_mask, &wc);
+ }
+ XSync(dpy, False);
+}
+
+void
+destroynotify(XEvent *e)
+{
+ XDestroyWindowEvent *ev = &e->xdestroywindow;
+ Client *c;
+
+ if ((c = wintoclient(ev->window)))
+ unmanage(c, 1);
+}
+
+void
+enternotify(XEvent *e)
+{
+ XCrossingEvent *ev = &e->xcrossing;
+ Monitor *m;
+ Client *c;
+ int x, y;
+
+ if (ev->window != root
+ && (ev->mode != NotifyNormal || ev->detail == NotifyInferior))
+ return;
+ if (ev->window == root) {
+ getrootptr(&x, &y);
+ m = pttomon(x, y);
+ } else {
+ for (m = mons; m && m->barwin != ev->window; m = m->next)
+ ;
+ }
+ if (m) {
+ if (selmon != m)
+ focusmon(m);
+ } else if ((c = wintoclient(ev->window))) {
+ if (selmon->sel != c)
+ focus(c);
+ }
+}
+
+void
+expose(XEvent *e)
+{
+ XExposeEvent *ev = &e->xexpose;
+ Monitor *m;
+
+ if (ev->count != 0)
+ return;
+ for (m = mons; m; m = m->next)
+ if (m->barwin == ev->window)
+ pdrawbar(m);
+}
+
+/* there are some broken focus acquiring clients */
+void
+focusin(XEvent *e)
+{
+ XFocusChangeEvent *ev = &e->xfocus;
+
+ if (selmon->sel && ev->window != selmon->sel->win)
+ focus(selmon->sel);
+}
+
+void
+mappingnotify(XEvent *e)
+{
+ XMappingEvent *ev = &e->xmapping;
+
+ XRefreshKeyboardMapping(ev);
+ if (ev->request == MappingKeyboard)
+ grabkeys();
+}
+
+void
+maprequest(XEvent *e)
+{
+ XMapRequestEvent *ev = &e->xmaprequest;
+ XWindowAttributes wa;
+
+ if (!XGetWindowAttributes(dpy, ev->window, &wa)
+ || wa.override_redirect || wintoclient(ev->window))
+ return;
+ if (!manage(ev->window, &wa))
+ warn("cannot manage window 0x%x", ev->window);
+}
+
+void
+motionnotify(XEvent *e)
+{
+ XMotionEvent *ev = &e->xmotion;
+ Monitor *m;
+
+ if (ev->window == root) {
+ m = pttomon(ev->x_root, ev->y_root);
+ if (m && selmon != m)
+ focusmon(m);
+ }
+}
+
+void
+propertynotify(XEvent *e)
+{
+ XPropertyEvent *ev = &e->xproperty;
+ Client *c;
+ Window dw;
+
+ if (ev->state == PropertyDelete)
+ return;
+ if ((c = wintoclient(ev->window))) {
+ switch(ev->atom) {
+ case XA_WM_TRANSIENT_FOR:
+ if (XGetTransientForHint(dpy, c->win, &dw))
+ setfloating(c, 1);
+ break;
+ case XA_WM_NORMAL_HINTS:
+ c->hintsvalid = 0;
+ parrange(c->mon);
+ break;
+ case XA_WM_HINTS:
+ updatewmhints(c);
+ pdrawbar(c->mon);
+ break;
+ default:
+ if (ev->atom == netatom[NetWMName]
+ || ev->atom == XA_WM_NAME) {
+ updatetitle(c);
+ if (c == c->mon->sel)
+ pdrawbar(c->mon);
+ } else if (ev->atom == netatom[NetWMWindowType]) {
+ updatewindowtype(c);
+ }
+ break;
+ }
+ }
+}
+
+void
+unmapnotify(XEvent *e)
+{
+ XUnmapEvent *ev = &e->xunmap;
+ Client *c;
+
+ if ((c = wintoclient(ev->window)))
+ unmanage(c, 0);
+}
diff --git a/lyt.c b/lyt.c
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xlib.h>
+
+#include "swm.h"
+#include "util.h"
+
+enum { W, H, X, Y };
+
+static LytNode *parse(const char **ps, LytNode *parent, int rev);
+static Client *nexttiled(Client *c);
+static void makeplans(Monitor *m, LytNode *p, int area[2]);
+static void tile(Monitor *m, LytNode *p, int area[4]);
+static void getdesc(LytNode *n, int rev, char **ps, size_t *psize);
+
+LytNode *
+parse(const char **ps, LytNode *parent, int rev)
+{
+ LytNode *lyt = NULL, *n = NULL;
+ LytNode **end = &lyt;
+ const char *s = *ps, *num;
+ char b, d;
+ int rev2;
+
+ s += strspn(s, " \t");
+ while (*s && !strchr("}])", *s)) {
+ if (!(n = malloc(sizeof(*n)))) {
+ warn("malloc:");
+ goto fail;
+ }
+ n->type = '*';
+ n->trailing = 0;
+ n->fact = -1;
+ n->next = *end;
+ *end = n;
+ num = NULL;
+ rev2 = 0;
+ switch (*s) {
+ case '*':
+ ++s;
+ break;
+ case '-':
+ rev2 = 1;
+ /* FALLTHROUGH */
+ case '+':
+ if (!strchr("{[(", s[1])) {
+ num = s;
+ break;
+ }
+ ++s;
+ /* FALLTHROUGH */
+ case '{':
+ case '[':
+ case '(':
+ b = *s++;
+ d = strchr("{}[]()", b)[1];
+ n->type = rev2 ? d : b;
+ if (!(n->p.chain = parse(&s, n, rev2)))
+ n->p.chain = n;
+ if (*s++ != d) {
+ warn("`%c' mismatched in layout description", b);
+ goto fail;
+ }
+ s += strspn(s, " \t");
+ if (*s == ':')
+ num = s + 1;
+ break;
+ default:
+ num = s;
+ break;
+ }
+ if (num) {
+ errno = 0;
+ n->fact = strtof(num, (char **)&s);
+ if (errno || n->fact < 0 || n->fact > 1) {
+ if (errno) {
+ s = num + 1;
+ s += strcspn(s, " \t*+-{}[]()");
+ }
+ warn("invalid value `%.*s' in layout description",
+ (int)(s - num), num);
+ goto fail;
+ }
+ }
+ s += strspn(s, " \t");
+ if (!rev)
+ end = &n->next;
+ }
+ if (lyt) {
+ for (n = lyt; n->next; n = n->next)
+ n->ending = n->loaded = 0;
+ n->next = parent;
+ n->ending = 1;
+ }
+ *ps = s;
+ return lyt;
+fail:
+ lytfree(lyt);
+ return NULL;
+}
+
+LytNode *
+lytparse(const char *s)
+{
+ LytNode *r = parse(&s, NULL, 0);
+
+ if (*s) {
+ warn("trailing characters `%s' in layout description", s);
+ lytfree(r);
+ r = NULL;
+ }
+ return r;
+}
+
+void
+lytfree(LytNode *lyt)
+{
+ LytNode *n = lyt, *nx;
+
+ if (!n)
+ return;
+ do {
+ if (n->type != '*' && n->p.chain != n)
+ lytfree(n->p.chain);
+ nx = n->ending ? NULL : n->next;
+ free(n);
+ n = nx;
+ } while (n);
+}
+
+Client *
+nexttiled(Client *c)
+{
+ for (; c; c = c->next)
+ if (ISVISIBLE(c) && !c->isfullscreen && !c->isfloating)
+ return c;
+ return NULL;
+}
+
+void
+makeplans(Monitor *m, LytNode *p, int area[2])
+{
+ int w, h, u, v, t;
+ LytNode *n, *o;
+ Client *c;
+ int rect[2];
+
+ switch (p->type) {
+ case '*':
+ if (!RESIZEHINTS)
+ return;
+ c = p->p.client;
+ u = 2 * borderw;
+ v = 2 * borderw;
+ if (!m->nogaps) {
+ u += m->gapih;
+ v += m->gapiv;
+ }
+ w = c->maxw;
+ h = c->maxh;
+ c->maxw = area[W] - u;
+ c->maxh = area[H] - v;
+ if (!m->nogaps) {
+ c->maxw += MIN(m->gapih, m->gapoh);
+ c->maxh += MIN(m->gapiv, m->gapov);
+ }
+ if (w && w < c->maxw)
+ c->maxw = w;
+ if (h && h < c->maxh)
+ c->maxh = h;
+ p->plan[W] -= u;
+ p->plan[H] -= v;
+ if (p->plan[W] > 0 && p->plan[H] > 0)
+ applysizehints(c, &p->plan[W], &p->plan[H]);
+ p->plan[W] += u;
+ p->plan[H] += v;
+ c->maxw = w;
+ c->maxh = h;
+ return;
+ case '{':
+ case '}':
+ w = W, h = H;
+ break;
+ case '[':
+ case ']':
+ w = H, h = W;
+ break;
+ default:
+ w = h = 0;
+ for (n = p->p.chain; n != p && n->loaded; n = n->next) {
+ if (n->fact < 0) {
+ n->plan[W] = p->plan[W];
+ n->plan[H] = p->plan[H];
+ } else {
+ n->plan[W] = p->plan[W] * n->fact;
+ n->plan[H] = p->plan[H] * n->fact;
+ }
+ makeplans(m, n, area);
+ if (w < n->plan[W])
+ w = n->plan[W];
+ if (h < n->plan[H])
+ h = n->plan[H];
+ }
+ p->plan[W] = w;
+ p->plan[H] = h;
+ return;
+ }
+ u = p->plan[w];
+ v = 0;
+ for (n = p->p.chain; n != p && n->loaded; n = n->next) {
+ if (n->fact < 0) {
+ ++v;
+ } else {
+ n->plan[w] = p->plan[w] * n->fact;
+ if (n->plan[w] > u)
+ n->plan[w] = u;
+ u -= n->plan[w];
+ }
+ n->plan[h] = p->plan[h];
+ }
+ if (v) {
+ for (n = p->p.chain; v; n = n->next) {
+ if (n->fact < 0) {
+ u -= n->plan[w] = (u + v - 1) / v;
+ --v;
+ }
+ }
+ } else {
+ p->p.chain->plan[w] += u;
+ }
+ p->plan[w] = p->plan[h] = 0;
+ for (n = p->p.chain; n != p && n->loaded; n = n->next) {
+ rect[w] = area[w] - p->plan[w];
+ rect[h] = area[h];
+ t = n->plan[w];
+ makeplans(m, n, rect);
+ p->plan[w] += n->plan[w];
+ if ((u = t - n->plan[w])) {
+ v = 0;
+ for (o = n->next; o != p && o->loaded; o = o->next)
+ v += o->plan[w];
+ for (o = n->next; v > 0; o = o->next) {
+ t = u < 0 ? (u * o->plan[w] - v + 1) / v
+ : (u * o->plan[w] + v - 1) / v;
+ u -= t;
+ v -= o->plan[w];
+ if ((o->plan[w] += t) < 0)
+ o->plan[w] = 0;
+ }
+ }
+ if (p->plan[h] < n->plan[h])
+ p->plan[h] = n->plan[h];
+ }
+}
+
+void
+tile(Monitor *m, LytNode *p, int area[4])
+{
+ int x, y, w, h, t, rev;
+ int gih, giv;
+ LytNode *n;
+ Client *c;
+ int rect[4];
+
+ if (m->nogaps) {
+ gih = giv = 0;
+ } else {
+ gih = m->gapih;
+ giv = m->gapiv;
+ }
+ switch (p->type) {
+ case '*':
+ c = p->p.client;
+ w = p->plan[W];
+ h = p->plan[H];
+ x = area[X] + (area[W] - w) / 2;
+ y = area[Y] + (area[H] - h) / 2;
+ w -= 2 * borderw + gih;
+ h -= 2 * borderw + giv;
+ x += gih / 2;
+ y += giv / 2;
+ if (w <= 0)
+ w = 1;
+ if (h <= 0)
+ h = 1;
+ if ((t = c->w != w || c->h != h)) {
+ c->w = w;
+ c->h = h;
+ }
+ if (t || c->x != x || c->y != y) {
+ c->x = x;
+ c->y = y;
+ configure(c, t);
+ }
+ return;
+ case '{':
+ case '}':
+ w = W, h = H;
+ x = X, y = Y;
+ rev = p->type == '}';
+ break;
+ case '[':
+ case ']':
+ w = H, h = W;
+ x = Y, y = X;
+ rev = p->type == ']';
+ break;
+ default:
+ for (n = p->p.chain; n != p && n->loaded; n = n->next)
+ tile(m, n, area);
+ return;
+ }
+ t = (area[w] - p->plan[w]) / 2;
+ rect[x] = rev ? area[x] + area[w] - t : area[x] + t;
+ rect[y] = area[y];
+ rect[h] = area[h];
+ for (n = p->p.chain; n != p && n->loaded; n = n->next) {
+ rect[w] = n->plan[w];
+ if (rev)
+ rect[x] -= rect[w];
+ tile(m, n, rect);
+ if (!rev)
+ rect[x] += rect[w];
+ }
+}
+
+void
+lyttile(Monitor *m)
+{
+ int u, v;
+ int area[4];
+ LytNode *n, *t;
+ Client *c;
+ static LytNode r = {
+ .type = '{',
+ .ending = 1,
+ .trailing = 0,
+ .fact = -1,
+ .next = NULL,
+ };
+
+ if (!(n = m->lyt) || !(c = nexttiled(m->clients)))
+ return;
+ do {
+ n->loaded = !!c;
+ t = n;
+ if (n->type != '*' && n->p.chain != n) {
+ n = n->p.chain;
+ } else {
+ if (n->type == '*') {
+ n->p.client = c;
+ if (c)
+ c = nexttiled(c->next);
+ }
+ while (n->ending && (n = n->next))
+ ;
+ if (!n)
+ break;
+ n = n->next;
+ }
+ } while (n);
+ if (c) {
+ do {
+ if (!(n = malloc(sizeof(*n)))) {
+ warn("some windows won't be tiled:");
+ break;
+ }
+ n->type = '*';
+ n->ending = 1;
+ n->loaded = 1;
+ n->trailing = 1;
+ n->fact = -1;
+ n->p.client = c;
+ if (t->type == '*') {
+ n->next = t->next;
+ t->ending = 0;
+ t->next = n;
+ } else {
+ n->next = t;
+ t->p.chain = n;
+ }
+ t = n;
+ } while ((c = nexttiled(c->next)));
+ }
+ if (m->nogaps) {
+ u = v = 0;
+ } else {
+ u = 2 * m->gapoh - m->gapih;
+ v = 2 * m->gapov - m->gapiv;
+ }
+ if (m->lyt->type != '*' && m->lyt->ending) {
+ n = m->lyt;
+ } else {
+ n = &r;
+ n->p.chain = m->lyt;
+ for (t = m->lyt; !t->ending; t = t->next)
+ ;
+ t->next = n;
+ }
+ n->plan[W] = area[W] = m->w - u;
+ n->plan[H] = area[H] = WH(m) - v;
+ area[X] = m->x + u / 2;
+ area[Y] = WY(m) + v / 2;
+ makeplans(m, n, area);
+ tile(m, n, area);
+ if (n == &r) {
+ n->p.chain = NULL;
+ for (t = m->lyt; !t->ending; t = t->next)
+ ;
+ t->next = NULL;
+ }
+}
+
+void
+getdesc(LytNode *n, int rev, char **ps, size_t *psize)
+{
+ char b, d;
+ int t;
+
+ if (rev && !n->ending && !n->next->trailing) {
+ getdesc(n->next, 1, ps, psize);
+ if (*psize < 1)
+ return;
+ *(*ps)++ = ' ';
+ --*psize;
+ }
+ if (n->type != '*') {
+ if (strchr("{}", n->type))
+ b = '{', d = '}';
+ else if (strchr("[]", n->type))
+ b = '[', d = ']';
+ else
+ b = '(', d = ')';
+ if (n->type == d) {
+ if (*psize < 1)
+ return;
+ *(*ps)++ = '-';
+ ++*psize;
+ }
+ if (*psize < 1)
+ return;
+ *(*ps)++ = b;
+ --*psize;
+ if (n->p.chain != n && !n->p.chain->trailing)
+ getdesc(n->p.chain, n->type == d, ps, psize);
+ if (*psize < 1)
+ return;
+ *(*ps)++ = d;
+ --*psize;
+ if (n->fact >= 0) {
+ if (*psize < 1)
+ return;
+ *(*ps)++ = ':';
+ --*psize;
+ }
+ } else if (n->fact < 0) {
+ if (*psize < 1)
+ return;
+ *(*ps)++ = '*';
+ --*psize;
+ }
+ if (n->fact >= 0) {
+ if (*psize < 3)
+ return;
+ snprintf(*ps, *psize, ".%d", (int)(n->fact * 100 + .5));
+ *ps += (t = strlen(*ps));
+ *psize -= t;
+ }
+ if (!rev && !n->ending && !n->next->trailing) {
+ if (*psize < 1)
+ return;
+ *(*ps)++ = ' ';
+ --*psize;
+ getdesc(n->next, 0, ps, psize);
+ }
+}
+
+void
+updatelytdesc(Monitor *m)
+{
+ char *buf = m->lytdesc;
+ size_t size = sizeof(m->lytdesc);
+
+ if (!m->lyt) {
+ *buf = '\0';
+ return;
+ }
+ getdesc(m->lyt, 0, &buf, &size);
+ if (!size)
+ --buf;
+ *buf = '\0';
+}
diff --git a/main.c b/main.c
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+#include <errno.h>
+#include <locale.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <X11/cursorfont.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xproto.h>
+#include <X11/Xutil.h>
+
+#include "swm.h"
+#include "util.h"
+
+extern char **environ;
+
+int running = 1;
+int restart;
+int pending;
+Display *dpy;
+Window root;
+int screen;
+int sw, sh;
+Monitor *mons, *selmon;
+Atom wmatom[WMLast], netatom[NetLast];
+const char *tags[] = TAGS;
+unsigned long pixelfocus, pixelunfocus, pixelurgent;
+Cursor curnormal, curresize, curmove;
+unsigned lockmasks[4];
+
+static int xerror(Display *dpy, XErrorEvent *ee);
+static int xerrorstart(Display *dpy, XErrorEvent *ee);
+static void checkotherwm(void);
+static void initatoms(void);
+static unsigned long *getpixel(const char *name, unsigned long *pixel);
+static unsigned getnumlockmask(void);
+static void dumpmonitors(char *var);
+static void loadmonitors(char *s);
+static void dumpclients(char *var, Monitor *m);
+static void loadclients(int n, char *s);
+static void dumpworkspaces(char *var, Monitor *m);
+static void loadworkspaces(int n, char *s);
+static void dumpstate(void);
+static void loadstate(void);
+static void setup(void);
+static void scan(void);
+static void run(void);
+static void cleanup(void);
+
+static int (*xerrorxlib)(Display *, XErrorEvent *);
+
+int
+xerror(Display *dpy, XErrorEvent *ee)
+{
+ unsigned char r = ee->request_code;
+ unsigned char e = ee->error_code;
+
+ warn("Xlib error: request code=%d, error code=%d", r, e);
+ switch (e) {
+ case BadAccess:
+ if (r == X_GrabButton
+ || r == X_GrabKey)
+ return 0;
+ break;
+ case BadDrawable:
+ if (r == X_PolyText8
+ || r == X_PolyFillRectangle
+ || r == X_PolySegment
+ || r == X_CopyArea)
+ return 0;
+ break;
+ case BadMatch:
+ if (r == X_SetInputFocus
+ || r == X_ConfigureWindow)
+ return 0;
+ break;
+ case BadWindow:
+ return 0;
+ default:
+ break;
+ }
+ return xerrorxlib(dpy, ee); /* might call exit */
+}
+
+int
+xerrorstart(Display *dpy, XErrorEvent *ee)
+{
+ die("another window manager is already running");
+ return -1;
+}
+
+void
+checkotherwm(void)
+{
+ xerrorxlib = XSetErrorHandler(xerrorstart);
+ XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask);
+ XSync(dpy, False);
+ XSetErrorHandler(xerror);
+ XSync(dpy, False);
+}
+
+void
+initatoms(void)
+{
+ static const struct {
+ Atom *p;
+ const char *s;
+ } a[] = {
+ { wmatom + WMProtocols, "WM_PROTOCOLS" },
+ { wmatom + WMDeleteWindow, "WM_DELETE_WINDOW" },
+ { wmatom + WMTakeFocus, "WM_TAKE_FOCUS" },
+ { wmatom + WMState, "WM_STATE" },
+ { netatom + NetSupported, "_NET_SUPPORTED" },
+ { netatom + NetActiveWindow, "_NET_ACTIVE_WINDOW" },
+ { netatom + NetWMName, "_NET_WM_NAME" },
+ { netatom + NetWMWindowType, "_NET_WM_WINDOW_TYPE" },
+ { netatom + NetWMState, "_NET_WM_STATE" },
+ { netatom + NetWMWindowTypeDialog,"_NET_WM_WINDOW_TYPE_DIALOG"},
+ { netatom + NetWMStateFullscreen, "_NET_WM_STATE_FULLSCREEN" },
+ };
+ int i;
+
+ for (i = 0; i < LENGTH(a); ++i)
+ *a[i].p = XInternAtom(dpy, a[i].s, False);
+}
+
+unsigned long *
+getpixel(const char *name, unsigned long *pixel)
+{
+ Colormap cmap = DefaultColormap(dpy, screen);
+ XColor color[2];
+
+ if (!XAllocNamedColor(dpy, cmap, name, color, color + 1)) {
+ warn("cannot allocate color `%s'", name);
+ return NULL;
+ }
+ *pixel = color->pixel;
+ return pixel;
+}
+
+unsigned
+getnumlockmask(void)
+{
+ XModifierKeymap *modmap = XGetModifierMapping(dpy);
+ KeyCode k;
+ int i, n;
+
+ if (!modmap) {
+ warn("cannot get modifier mapping");
+ return 0;
+ }
+ k = XKeysymToKeycode(dpy, XK_Num_Lock);
+ n = modmap->max_keypermod;
+ for (i = 0; i < 8 * n && modmap->modifiermap[i] != k; ++i)
+ ;
+ XFreeModifiermap(modmap);
+ return i < 8 * n ? 1 << (i / n) : 0;
+}
+
+void
+dumpmonitors(char *var)
+{
+ char *buf, *p;
+ Monitor *m;
+ int n;
+
+ n = 0;
+ for (m = mons; m; m = m->next)
+ n += 6;
+ if (!n)
+ return;
+ if (!(buf = malloc(n))) {
+ warn("cannot dump state of clients:");
+ return;
+ }
+ p = buf;
+ for (m = mons; m; m = m->next) {
+ sprintf(p, "%d %c%c", m->selws, m->showbar ? 'B' : 'b',
+ m->nogaps ? 'g' : 'G');
+ p += strlen(p);
+ *p++ = ':';
+ }
+ *--p = '\0';
+ if (setenv(var, buf, 1) < 0)
+ warn("setenv `%s':", var);
+ free(buf);
+}
+
+void
+loadmonitors(char *val)
+{
+ Monitor *m = mons;
+ int i, l;
+
+ for (val = strtok(val, ":"); val && m; val = strtok(NULL, ":")) {
+ if (sscanf(val, "%i %n", &i, &l) != 1)
+ continue;
+ if ((unsigned)i < LENGTH(tags)) {
+ wssync(m);
+ m->selws = i;
+ wsload(m);
+ }
+ for (; val[l]; ++l) {
+ switch (val[l]) {
+ case 'b':
+ case 'B':
+ m->showbar = val[l] == 'B';
+ XMoveWindow(dpy, m->barwin, m->x, BY(m));
+ break;
+ case 'g':
+ case 'G':
+ m->nogaps = val[l] == 'g';
+ break;
+ }
+ }
+ m = m->next;
+ }
+}
+
+void
+dumpclients(char *var, Monitor *m)
+{
+ char *buf, *p;
+ Client *c;
+ size_t n;
+
+ n = 0;
+ for (c = m->clients; c; c = c->next)
+ n += 18 + (LENGTH(tags) + 3) / 4;
+ if (!n)
+ return;
+ if (!(buf = malloc(n))) {
+ warn("cannot dump state of clients:");
+ return;
+ }
+ p = buf;
+ for (c = m->clients; c; c = c->next) {
+ sprintf(p, "0x%x 0x%x ", (unsigned)c->win, c->tags);
+ p += strlen(p);
+ if (c->isfloating)
+ *p++ = 'f';
+ if (c->isfullscreen)
+ *p++ = 'F';
+ if (c->isscratch)
+ *p++ = 's';
+ if (p[-1] == ' ')
+ --p;
+ *p++ = ':';
+ }
+ *--p = '\0';
+ if (setenv(var, buf, 1) < 0)
+ warn("setenv `%s':", var);
+ free(buf);
+}
+
+void
+loadclients(int n, char *val)
+{
+ Monitor *m;
+ Client *c, **tc;
+ int win, t, l;
+
+ for (m = mons; m && m->num != n; m = m->next)
+ ;
+ if (!m)
+ m = mons;
+ for (val = strtok(val, ":"); val; val = strtok(NULL, ":")) {
+ if (sscanf(val, "%i %i %n", &win, &t, &l) != 2
+ || !(c = wintoclient(win)))
+ continue;
+ if (c->mon != m)
+ sendtomon(c, m);
+ detach(c);
+ c->next = NULL;
+ for (tc = &m->clients; *tc; tc = &(*tc)->next)
+ ;
+ *tc = c;
+ tc = &c->next;
+ for (; val[l]; ++l) {
+ switch (val[l]) {
+ case 'f':
+ setfloating(c, 1);
+ break;
+ case 'F':
+ setfullscreen(c, 1);
+ break;
+ case 's':
+ setscratch(c, 1);
+ break;
+ }
+ }
+ if ((t &= TAGMASK) || c->isscratch)
+ c->tags = t;
+ }
+}
+
+void
+dumpworkspaces(char *var, Monitor *m)
+{
+ char *buf, *p;
+ int i;
+
+ buf = malloc(LENGTH(tags)
+ * ((LENGTH(tags) + 3) / 4 + 1 + sizeof(m->lytdesc)));
+ if (!buf) {
+ warn("cannot dump state of workspaces:", var);
+ return;
+ }
+ wssync(m);
+ p = buf;
+ i = m->selws;
+ for (m->selws = 0; m->selws < LENGTH(tags); ++m->selws) {
+ wsload(m);
+ sprintf(p, "0x%x %s", m->tagset, m->lytdesc);
+ p += strlen(p);
+ *p++ = ';';
+ }
+ m->selws = i;
+ wsload(m);
+ *--p = '\0';
+ if (setenv(var, buf, 1) < 0)
+ warn("setenv `%s':", var);
+ free(buf);
+}
+
+void
+loadworkspaces(int n, char *s)
+{
+ Monitor *m;
+ LytNode *lyt;
+ int i, t, l;
+
+ for (m = mons; m && m->num != n; m = m->next)
+ ;
+ if (!m)
+ return;
+ wssync(m);
+ i = m->selws;
+ for (s = strtok(s, ";"), m->selws = 0;
+ s && m->selws < LENGTH(tags);
+ s = strtok(NULL, ";"), ++m->selws) {
+ if (sscanf(s, "%i %n", &t, &l) != 1)
+ continue;
+ wsload(m);
+ m->tagset = t & TAGMASK;
+ if ((lyt = lytparse(s + l))) {
+ lytfree(m->lyt);
+ m->lyt = lyt;
+ }
+ wssync(m);
+ }
+ m->selws = i;
+ wsload(m);
+}
+
+void
+dumpstate(void)
+{
+ char s[32];
+ Monitor *m;
+
+ strcpy(s, "SWM_MONITORS");
+ dumpmonitors(s);
+ for (m = mons; m; m = m->next) {
+ sprintf(s, "SWM_MON%d_CLIENTS", m->num);
+ dumpclients(s, m);
+ sprintf(s, "SWM_MON%d_WORKSPACES", m->num);
+ dumpworkspaces(s, m);
+ }
+ if (selmon->sel) {
+ sprintf(s, "0x%x", (unsigned)selmon->sel->win);
+ if (setenv("SWM_FOCUS", s, 1))
+ warn("setenv `%s':", s);
+ }
+}
+
+void
+loadstate(void)
+{
+ int i, n, l;
+ char *entry, *val;
+ Client *c;
+
+ if (!environ)
+ return;
+ for (i = 0; environ[i]; ++i) {
+ if (strncmp(environ[i], "SWM_", 4)
+ || !strncmp(environ[i], "SWM_FOCUS=", 10))
+ continue;
+ if (!(entry = strdup(environ[i]))) {
+ warn("cannot load state from `%s':", environ[i]);
+ continue;
+ }
+ if (!(val = strchr(entry, '='))) {
+ free(entry);
+ continue;
+ }
+ *val++ = '\0';
+ l = 0;
+ if (!strcmp(entry, "SWM_MONITORS"))
+ loadmonitors(val);
+ else if (sscanf(entry, "SWM_MON%d_CLIENTS%n", &n, &l) == 1
+ && !entry[l])
+ loadclients(n, val);
+ else if (sscanf(entry, "SWM_MON%d_WORKSPACES%n", &n, &l) == 1
+ && !entry[l])
+ loadworkspaces(n, val);
+ unsetenv(entry);
+ free(entry);
+ --i;
+ }
+ val = getenv("SWM_FOCUS");
+ if (val && sscanf(val, "%i", &i) == 1 && (c = wintoclient(i)))
+ focus(c);
+ else
+ focus(selmon->stack);
+ unsetenv("SWM_FOCUS");
+}
+
+void
+setup(void)
+{
+ XSetWindowAttributes wa;
+ struct sigaction sa;
+ Monitor *m;
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT|SA_RESTART;
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGCHLD, &sa, NULL);
+ while (waitpid(-1, NULL, WNOHANG) > 0)
+ ;
+ screen = DefaultScreen(dpy);
+ sw = DisplayWidth(dpy, screen);
+ sh = DisplayHeight(dpy, screen);
+ root = RootWindow(dpy, screen);
+ initatoms();
+ if (updategeom() < 0)
+ die("cannot set up monitor objects");
+ curnormal = XCreateFontCursor(dpy, XC_left_ptr);
+ curresize = XCreateFontCursor(dpy, XC_sizing);
+ curmove = XCreateFontCursor(dpy, XC_fleur);
+ if (!getpixel(colfocus, &pixelfocus)
+ || !getpixel(colunfocus, &pixelunfocus)
+ || !getpixel(colurgent, &pixelurgent))
+ die("cannot initialize border pixels");
+ barsetup();
+ for (m = mons; m; m = m->next)
+ initbar(m);
+ XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32,
+ PropModeReplace, (unsigned char *)netatom, NetLast);
+ wa.cursor = curnormal;
+ wa.event_mask = SubstructureRedirectMask
+ |StructureNotifyMask|SubstructureNotifyMask
+ |PointerMotionMask|ButtonPressMask
+ |EnterWindowMask|LeaveWindowMask;
+ XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa);
+ XSelectInput(dpy, root, wa.event_mask);
+ lockmasks[1] = LockMask;
+ lockmasks[2] = getnumlockmask();
+ lockmasks[3] = lockmasks[1]|lockmasks[2];
+ grabkeys();
+ focus(selmon->stack);
+}
+
+void
+scan(void)
+{
+ Window t, *wins = NULL;
+ XWindowAttributes wa;
+ unsigned n, i, j;
+
+ if (!XQueryTree(dpy, root, &t, &t, &wins, &n) || !wins)
+ return;
+ for (i = j = 0; i < n; ++i) {
+ if (!XGetWindowAttributes(dpy, wins[i], &wa)
+ || wa.override_redirect
+ || (wa.map_state != IsViewable
+ && getstate(wins[i]) != IconicState))
+ continue;
+ if (XGetTransientForHint(dpy, wins[i], &t))
+ wins[j++] = wins[i];
+ else
+ manage(wins[i], &wa);
+ }
+ while (j--) {
+ if (!XGetWindowAttributes(dpy, wins[j], &wa))
+ continue;
+ manage(wins[j], &wa);
+ }
+ XFree(wins);
+ loadstate();
+}
+
+void
+run(void)
+{
+ struct pollfd fds[2];
+ XEvent ev;
+ Monitor *m;
+
+ runsprog();
+ fds[0].fd = ConnectionNumber(dpy);
+ fds[0].events = fds[1].events = POLLIN;
+ for (;;) {
+ while (XPending(dpy)) {
+ XNextEvent(dpy, &ev);
+ if (!handler[ev.type])
+ continue;
+ handler[ev.type](&ev);
+ if (!running)
+ return;
+ }
+ do {
+ if (pending) {
+ for (m = mons; m; m = m->next) {
+ if (m->pending & PArrange)
+ arrange(m);
+ if (m->pending & PDrawbar)
+ drawbar(m);
+ m->pending = 0;
+ }
+ XSync(dpy, False);
+ pending = 0;
+ }
+ fds[1].fd = sfdr;
+ if (poll(fds, 2, -1) < 0) {
+ if (errno == EINTR)
+ continue;
+ die("poll:");
+ }
+ if (fds[1].revents)
+ updatestatus();
+ } while (!fds[0].revents);
+ if (fds[0].revents != POLLIN)
+ die("X connection broken");
+ }
+}
+
+void
+cleanup(void)
+{
+ Monitor *m;
+
+ if (restart)
+ dumpstate();
+ for (m = mons; m; m = m->next)
+ while (m->stack)
+ unmanage(m->stack, 0);
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ endsprog();
+ barcleanup();
+ XFreeCursor(dpy, curnormal);
+ XFreeCursor(dpy, curresize);
+ XFreeCursor(dpy, curmove);
+ cleanupgeom();
+ XSync(dpy, False);
+ XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
+ XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc > 1)
+ die("excessive arguments");
+ progname = argv[0];
+ if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
+ die("no locale support");
+ if (!(dpy = XOpenDisplay(NULL)))
+ die("cannot open display");
+ checkotherwm();
+ setup();
+#ifdef __OpenBSD__
+ if (pledge("stdio rpath proc exec", NULL) == -1)
+ die("pledge:");
+#endif
+ scan();
+ run();
+ cleanup();
+ XCloseDisplay(dpy);
+ if (restart) {
+ execvp(argv[0], argv);
+ die("execvp '%s':", argv[0]);
+ }
+ return 0;
+}
diff --git a/misc.c b/misc.c
@@ -0,0 +1,594 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+#include <stdarg.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <X11/Xlib.h>
+
+#include "swm.h"
+#include "util.h"
+
+#define MOUSEMASK (ButtonPressMask|ButtonReleaseMask|PointerMotionMask)
+
+static LytNode *master(LytNode *lyt);
+
+static const unsigned ctrlshift[] = {
+ 0, ControlMask, ShiftMask, ControlMask|ShiftMask
+};
+static FILE *menufpr, *menufpw;
+
+int
+depart(int *fds)
+{
+ int fds1[2], fds2[2];
+ struct sigaction sa;
+
+ if (fds) {
+ if (pipe(fds1) < 0 || pipe(fds2) < 0) {
+ warn("pipe:");
+ return -1;
+ }
+ }
+ switch (fork()) {
+ case -1:
+ if (fds) {
+ close(fds1[0]);
+ close(fds1[1]);
+ close(fds2[0]);
+ close(fds2[1]);
+ }
+ return -1;
+ case 0:
+ if (dpy)
+ close(ConnectionNumber(dpy));
+ if (fds) {
+ close(fds1[1]);
+ close(fds2[0]);
+ if (fds1[0] != 0) {
+ if (dup2(fds1[0], 0) < 0)
+ warn("dup2:");
+ close(fds1[0]);
+ }
+ if (fds2[1] != 1) {
+ if (dup2(fds2[1], 1) < 0)
+ warn("dup2:");
+ close(fds2[1]);
+ }
+ }
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = SIG_DFL;
+ if (sigaction(SIGCHLD, &sa, NULL) < 0)
+ warn("sigaction:");
+ if (setsid() < 0)
+ warn("setsid:");
+ return 0;
+ default:
+ if (fds) {
+ close(fds1[0]);
+ close(fds2[1]);
+ fds[0] = fds2[0];
+ fds[1] = fds1[1];
+ }
+ return 1;
+ }
+}
+
+void
+spawn(char **cmd)
+{
+ if (depart(NULL) == 0) {
+ execvp(cmd[0], cmd);
+ die("execvp '%s':", cmd[0]);
+ }
+}
+
+int
+getrootptr(int *x, int *y)
+{
+ int di;
+ unsigned dui;
+ Window dw;
+
+ return XQueryPointer(dpy, root, &dw, &dw, x, y, &di, &di, &dui);
+}
+
+long
+getstate(Window w)
+{
+ int format;
+ long result = -1;
+ unsigned char *p = NULL;
+ unsigned long n, extra;
+ Atom real;
+
+ if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False,
+ wmatom[WMState], &real, &format, &n,
+ &extra, (unsigned char **)&p) != Success)
+ return -1;
+ if (n != 0)
+ result = *p;
+ XFree(p);
+ return result;
+}
+
+void
+grabbuttons(Client *c, int focused)
+{
+ int i, j;
+
+ XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
+ if (!focused)
+ XGrabButton(dpy, AnyButton, AnyModifier, c->win,
+ False, ButtonPressMask|ButtonReleaseMask,
+ GrabModeSync, GrabModeSync, None, None);
+ for (i = 0; i < 4; ++i)
+ for (j = 0; j < 4; ++j)
+ XGrabButton(dpy, AnyButton,
+ MODMASK|lockmasks[i]|ctrlshift[j],
+ c->win, False,
+ ButtonPressMask|ButtonReleaseMask,
+ GrabModeAsync, GrabModeSync, None, None);
+}
+
+void
+grabkeys(void)
+{
+ int i, j;
+
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ for (i = 0; i < 4; ++i)
+ for (j = 0; j < 4; ++j)
+ XGrabKey(dpy, AnyKey,
+ MODMASK|lockmasks[i]|ctrlshift[j],
+ root, True, GrabModeAsync, GrabModeAsync);
+}
+
+void
+swap(Client *c, Client *d)
+{
+ Client **tc, **td;
+ Monitor *m;
+ Client *t;
+
+ if (!c || !d || c == d)
+ return;
+ tc = &c->mon->clients;
+ while (*tc && *tc != c && *tc != d)
+ tc = &(*tc)->next;
+ if (!*tc)
+ return;
+ if (*tc == d) {
+ t = c;
+ c = d;
+ d = t;
+ }
+ if (c->next == d) {
+ c->next = d->next;
+ d->next = c;
+ *tc = d;
+ } else {
+ td = c->mon == d->mon ? &c->next : &d->mon->clients;
+ while (*td && *td != d)
+ td = &(*td)->next;
+ if (!*td)
+ return;
+ *tc = d;
+ *td = c;
+ t = c->next;
+ c->next = d->next;
+ d->next = t;
+ if (c->mon != d->mon) {
+ m = c->mon;
+ c->mon = d->mon;
+ d->mon = m;
+ }
+ }
+ parrange(c->mon);
+}
+
+void
+tag(unsigned ts)
+{
+ if (selmon->sel && (ts &= TAGMASK)) {
+ selmon->sel->tags = ts;
+ focus(selmon->stack);
+ parrange(selmon);
+ pdrawbar(selmon);
+ }
+}
+
+void
+view(unsigned ts)
+{
+ selmon->tagset = ts & TAGMASK;
+ focus(selmon->stack);
+ parrange(selmon);
+ pdrawbar(selmon);
+}
+
+void
+setworkspace(unsigned ws)
+{
+ if (ws >= LENGTH(tags))
+ return;
+ wssync(selmon);
+ selmon->altws = selmon->selws;
+ selmon->selws = ws;
+ wsload(selmon);
+ focus(selmon->stack);
+ parrange(selmon);
+ pdrawbar(selmon);
+}
+
+void
+setlayout(const char *desc)
+{
+ LytNode *lyt = NULL;
+
+ if (desc && !(lyt = lytparse(desc))) {
+ warn("failed to parse layout `%s'", desc);
+ return;
+ }
+ lytfree(selmon->lyt);
+ selmon->lyt = lyt;
+ updatelytdesc(selmon);
+ parrange(selmon);
+ pdrawbar(selmon);
+}
+
+LytNode *
+master(LytNode *lyt)
+{
+ LytNode *n = lyt;
+
+ if (!n)
+ return NULL;
+ while (n->fact < 0) {
+ if (n->type == '*' || n->p.chain == n)
+ return NULL;
+ n = n->p.chain;
+ }
+ return n;
+}
+
+void
+incnmaster(int i)
+{
+ LytNode *p, *n, *nn;
+
+ if (!(p = master(selmon->lyt)))
+ return;
+ if (i > 0) {
+ n = p->p.chain;
+ if (n != p)
+ while (!n->ending)
+ n = n->next;
+ while (i--) {
+ if (!(nn = malloc(sizeof(*nn)))) {
+ warn("error while adding masters:");
+ break;
+ }
+ nn->type = '*';
+ nn->ending = 1;
+ nn->trailing = 0;
+ nn->fact = -1;
+ nn->p.client = NULL;
+ nn->next = p;
+ if (n == p) {
+ n = p->p.chain = nn;
+ } else {
+ n->ending = 0;
+ n = n->next = nn;
+ }
+ }
+ } else {
+ while (p->p.chain != p && i++) {
+ n = p->p.chain;
+ if (n->ending) {
+ if (n->type != '*')
+ break;
+ free(n);
+ p->p.chain = p;
+ } else {
+ while (!n->next->ending)
+ n = n->next;
+ if (n->next->type != '*')
+ break;
+ free(n->next);
+ n->next = p;
+ n->ending = 1;
+ }
+ }
+ }
+ updatelytdesc(selmon);
+ lyttile(selmon);
+ pdrawbar(selmon);
+}
+
+void
+incmfact(float f)
+{
+ LytNode *p;
+
+ if (!(p = master(selmon->lyt)))
+ return;
+ f += p->fact;
+ p->fact = f < 0 ? 0 : f > 1 ? 1 : f;
+ updatelytdesc(selmon);
+ lyttile(selmon);
+ pdrawbar(selmon);
+}
+
+void
+movebymouse(void)
+{
+ int opx, opy, ocx, ocy;
+ int npx, npy, ncx, ncy;
+ Client *c = selmon->sel;
+ Time lasttime = 0;
+ Monitor *m;
+ XEvent ev;
+
+ if (!c || c->isfullscreen)
+ return;
+ ocx = c->x;
+ ocy = c->y;
+ if (XGrabPointer(dpy, root, False, MOUSEMASK,
+ GrabModeAsync, GrabModeAsync,
+ None, curmove, CurrentTime) != GrabSuccess)
+ return;
+ if (!getrootptr(&opx, &opy))
+ return;
+ npx = opx;
+ npy = opy;
+ do {
+ XMaskEvent(dpy, MOUSEMASK|ExposureMask
+ |SubstructureRedirectMask, &ev);
+ switch(ev.type) {
+ case ConfigureRequest:
+ case Expose:
+ case MapRequest:
+ handler[ev.type](&ev);
+ break;
+ case MotionNotify:
+ if ((ev.xmotion.time - lasttime) <= (1000 / 60))
+ continue;
+ lasttime = ev.xmotion.time;
+ npx = ev.xmotion.x;
+ npy = ev.xmotion.y;
+ ncx = ocx - opx + npx;
+ ncy = ocy - opy + npy;
+ if (abs(selmon->x - ncx) < snap)
+ ncx = selmon->x;
+ else if (abs((selmon->x + selmon->w)
+ - (ncx + WIDTH(c))) < snap)
+ ncx = selmon->x + selmon->w - WIDTH(c);
+ if (abs(WY(selmon) - ncy) < snap)
+ ncy = WY(selmon);
+ else if (abs((WY(selmon) + WH(selmon))
+ - (ncy + HEIGHT(c))) < snap)
+ ncy = WY(selmon) + WH(selmon) - HEIGHT(c);
+ if (!c->isfloating && selmon->lyt
+ && (abs(ncx - c->x) > snap || abs(ncy - c->y) > snap))
+ setfloating(c, 1);
+ if (!selmon->lyt || c->isfloating) {
+ if (c->x == ncx && c->y == ncy)
+ break;
+ c->x = ncx;
+ c->y = ncy;
+ configure(c, 0);
+ XSync(dpy, False);
+ } else if (abs(ncx - c->x) > snap
+ || abs(ncy - c->y) > snap) {
+ setfloating(c, 1);
+ }
+ break;
+ }
+ } while (ev.type != ButtonRelease);
+ XUngrabPointer(dpy, CurrentTime);
+ if (!(m = pttomon(c->x + WIDTH(c) / 2, c->y + HEIGHT(c) / 2)))
+ m = pttomon(npx, npy);
+ if (m && m != selmon) {
+ sendtomon(c, m);
+ focus(c);
+ }
+}
+
+void
+resizebymouse(void)
+{
+ int nw, nh;
+ Client *c = selmon->sel;
+ Time lasttime = 0;
+ Monitor *m;
+ XEvent ev;
+
+ if (!c || c->isfullscreen)
+ return;
+ if (XGrabPointer(dpy, root, False, MOUSEMASK,
+ GrabModeAsync, GrabModeAsync,
+ None, curresize, CurrentTime) != GrabSuccess)
+ return;
+ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0,
+ c->w + borderw - 1, c->h + borderw - 1);
+ do {
+ XMaskEvent(dpy, MOUSEMASK|ExposureMask
+ |SubstructureRedirectMask, &ev);
+ switch(ev.type) {
+ case ConfigureRequest:
+ case Expose:
+ case MapRequest:
+ handler[ev.type](&ev);
+ break;
+ case MotionNotify:
+ if ((ev.xmotion.time - lasttime) <= (1000 / 60))
+ continue;
+ lasttime = ev.xmotion.time;
+
+ nw = MAX(ev.xmotion.x - c->x - 2 * borderw + 1, 1);
+ nh = MAX(ev.xmotion.y - c->y - 2 * borderw + 1, 1);
+ if (!selmon->lyt || c->isfloating) {
+ applysizehints(c, &nw, &nh);
+ if (c->w == nw && c->h == nh)
+ break;
+ c->w = nw;
+ c->h = nh;
+ configure(c, 1);
+ XSync(dpy, False);
+ } else if (abs(nw - c->w) > snap
+ || abs(nh - c->h) > snap) {
+ setfloating(c, 1);
+ }
+ break;
+ }
+ } while (ev.type != ButtonRelease);
+ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0,
+ c->w + borderw - 1, c->h + borderw - 1);
+ XUngrabPointer(dpy, CurrentTime);
+ while (XCheckMaskEvent(dpy, EnterWindowMask, &ev))
+ ;
+ m = pttomon(c->x + WIDTH(c) / 2, c->y + HEIGHT(c) / 2);
+ if (m && m != selmon) {
+ sendtomon(c, m);
+ focus(c);
+ }
+}
+
+void
+togglescratch(char **cmd)
+{
+ Client *c, *d;
+
+ for (c = selmon->stack; c && !c->isscratch; c = c->snext)
+ ;
+ if (!c) {
+ if (cmd)
+ spawn(cmd);
+ return;
+ }
+ for (d = c; d && (!d->isscratch || !ISVISIBLE(d)); d = d->snext)
+ ;
+ if (d) {
+ d->tags &= ~selmon->tagset;
+ if (selmon->sel == d)
+ focus(selmon->stack);
+ if (d != c) {
+ detachstack(d);
+ attachstack(d);
+ }
+ } else {
+ if (!(c->tags |= selmon->tagset))
+ return;
+ focus(c);
+ }
+ parrange(selmon);
+ pdrawbar(selmon);
+}
+
+void
+cyclescratch(void)
+{
+ Client *c;
+
+ if (!selmon->sel || !selmon->sel->isscratch) {
+ c = selmon->clients;
+ while (c && (!c->isscratch || ISVISIBLE(c)))
+ c = c->next;
+ if (!c)
+ return;
+ } else {
+ c = selmon->sel;
+ do {
+ c = c->next ? c->next : selmon->clients;
+ } while (c != selmon->sel && (!c->isscratch || ISVISIBLE(c)));
+ if (c == selmon->sel)
+ return;
+ selmon->sel->tags &= ~selmon->tagset;
+ }
+ if (!(c->tags |= selmon->tagset))
+ return;
+ focus(c);
+ parrange(selmon);
+ pdrawbar(selmon);
+}
+
+void
+newmenu(void)
+{
+ int fds[2];
+
+ if (menufpw)
+ fclose(menufpw);
+ if (menufpr)
+ fclose(menufpr);
+ menufpw = menufpr = NULL;
+ switch (depart(fds)) {
+ case -1:
+ warn("failed to start a menu");
+ return;
+ case 0:
+ execmenu();
+ return;
+ default:
+ break;
+ }
+ if (!(menufpr = fdopen(fds[0], "r"))
+ || !(menufpw = fdopen(fds[1], "w"))) {
+ warn("failed to start a menu: fdopen:");
+ if (menufpr)
+ fclose(menufpr);
+ else
+ close(fds[0]);
+ close(fds[1]);
+ return;
+ }
+}
+
+void
+menuput(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!menufpw)
+ return;
+ va_start(ap, fmt);
+ vfprintf(menufpw, fmt, ap);
+ fputc('\n', menufpw);
+ va_end(ap);
+}
+
+char *
+menuget(void)
+{
+ static char *line = NULL;
+ static size_t n;
+ ssize_t len;
+
+ if (menufpw) {
+ fclose(menufpw);
+ menufpw = NULL;
+ }
+ if ((len = getline(&line, &n, menufpr)) != -1) {
+ if (len && line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ return line;
+ }
+ fclose(menufpr);
+ menufpr = NULL;
+ free(line);
+ line = NULL;
+ n = 0;
+ return NULL;
+}
diff --git a/monitor.c b/monitor.c
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+#include <stdlib.h>
+#include <X11/Xlib.h>
+#ifdef XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+
+#include "swm.h"
+#include "util.h"
+
+static Monitor *createmon(void);
+static void cleanupmon(Monitor *mon);
+static void showhide(Client *c);
+static void restack(Monitor *m);
+
+void
+wsload(Monitor *m)
+{
+ Workspace *w = m->workspaces + m->selws;
+
+ m->lyt = w->lyt;
+ w->lyt = NULL;
+ m->tagset = w->tagset;
+ updatelytdesc(m);
+}
+
+void
+wssync(Monitor *m)
+{
+ Workspace *w = m->workspaces + m->selws;
+
+ w->lyt = m->lyt;
+ w->tagset = m->tagset;
+}
+
+Monitor *
+createmon(void)
+{
+ Monitor *m;
+ unsigned i;
+
+ if (!(m = malloc(sizeof(*m)))) {
+ warn("malloc:");
+ return NULL;
+ }
+ m->nogaps = NOGAPS;
+ m->showbar = SHOWBAR;
+ m->gapih = gapih;
+ m->gapiv = gapiv;
+ m->gapoh = gapoh;
+ m->gapov = gapov;
+ m->clients = m->stack = m->sel = NULL;
+ m->next = NULL;
+ m->pending = 0;
+ for (i = 0; i < LENGTH(tags); ++i) {
+ m->workspaces[i].lyt = lytparse(defaultlayout);
+ m->workspaces[i].tagset = 1 << i;
+ }
+ m->selws = m->altws = 0;
+ wsload(m);
+ pdrawbar(m);
+ return m;
+}
+
+void
+cleanupmon(Monitor *mon)
+{
+ Monitor *m;
+ int i;
+
+ if (mon == mons) {
+ mons = mons->next;
+ } else {
+ for (m = mons; m && m->next != mon; m = m->next)
+ ;
+ m->next = mon->next;
+ }
+ wssync(mon);
+ for (i = 0; i < LENGTH(tags); ++i)
+ lytfree(mon->workspaces[i].lyt);
+ XUnmapWindow(dpy, mon->barwin);
+ XDestroyWindow(dpy, mon->barwin);
+ free(mon);
+}
+
+#ifdef XINERAMA
+static int
+isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info)
+{
+ while (n--)
+ if (unique[n].x_org == info->x_org
+ && unique[n].y_org == info->y_org
+ && unique[n].width == info->width
+ && unique[n].height == info->height)
+ return 0;
+ return 1;
+}
+#endif /* XINERAMA */
+
+int
+updategeom(void)
+{
+ int dirty = 0;
+
+#ifdef XINERAMA
+ if (XineramaIsActive(dpy)) {
+ XineramaScreenInfo *info;
+ Monitor *m, **pm;
+ int i, n, nn;
+ Client *c;
+
+ /* only consider unique geometries as separate screens */
+ info = XineramaQueryScreens(dpy, &n);
+ nn = 0;
+ for (i = 0; i < n; ++i)
+ if (isuniquegeom(info, nn, info + i))
+ info[nn++] = info[i];
+ for (n = 0, pm = &mons; n < nn && *pm; ++n, pm = &(*pm)->next)
+ ;
+ if (n < nn) {
+ /* new monitors */
+ for (i = n; i < nn; ++i) {
+ if (!(*pm = createmon()))
+ break;
+ pm = &(*pm)->next;
+ }
+ if (i < nn) {
+ warn("cannot handle some new screens");
+ nn = i;
+ }
+ } else if (n > nn) {
+ /* removed monitors */
+ while (*pm) {
+ while ((c = (*pm)->clients)) {
+ dirty = 1;
+ sendtomon(c, mons);
+ }
+ if (*pm == selmon)
+ focusmon(mons);
+ cleanupmon(*pm);
+ }
+ }
+ for (m = mons, i = 0; m; m = m->next, ++i) {
+ if (i >= n
+ || info[i].x_org != m->x || info[i].width != m->w
+ || info[i].y_org != m->y || info[i].height != m->h) {
+ dirty = 1;
+ m->num = i;
+ m->x = info[i].x_org;
+ m->y = info[i].y_org;
+ m->w = info[i].width;
+ m->h = info[i].height;
+ }
+ }
+ XFree(info);
+ } else
+#endif /* XINERAMA */
+ {
+ /* default monitor setup */
+ if (!mons) {
+ if (!(mons = createmon())) {
+ warn("cannot handle default screen");
+ return -1;
+ }
+ dirty = 1;
+ mons->x = mons->y = 0;
+ mons->w = sw;
+ mons->h = sh;
+ } else if (mons->w != sw || mons->h != sh) {
+ dirty = 1;
+ mons->w = sw;
+ mons->h = sh;
+ }
+ }
+ if (dirty)
+ focusmon(mons);
+ return dirty;
+}
+
+void
+cleanupgeom(void)
+{
+ while (mons)
+ cleanupmon(mons);
+}
+
+Monitor *
+nextmon(Monitor *m)
+{
+ return m->next ? m->next : mons;
+}
+
+Monitor *
+prevmon(Monitor *m)
+{
+ Monitor *n = m == mons ? NULL : m;
+
+ for (m = mons; m->next != n; m = m->next)
+ ;
+ return m;
+}
+
+Monitor *
+pttomon(int x, int y)
+{
+ Monitor *m = selmon;
+
+ do {
+ if (x >= m->x && x < m->x + m->w
+ && y >= m->y && y < m->y + m->h)
+ return m;
+ m = m->next ? m->next : mons;
+ } while (m != selmon);
+ return NULL;
+}
+
+void
+focusmon(Monitor *m)
+{
+ if (selmon) {
+ focus(NULL);
+ pdrawbar(selmon);
+ }
+ selmon = m;
+ if (selmon) {
+ pdrawbar(selmon);
+ focus(selmon->stack);
+ }
+}
+
+void
+showhide(Client *c)
+{
+ for (; c; c = c->snext) {
+ if (c->ishidden == !ISVISIBLE(c))
+ continue;
+ if (c->ishidden) {
+ configure(c, 0);
+ } else {
+ showhide(c->snext);
+ configure(c, 0);
+ return;
+ }
+ }
+}
+
+void
+restack(Monitor *m)
+{
+ XWindowChanges fwc, twc;
+ XWindowChanges *wc = NULL;
+ Client *c;
+
+ fwc.stack_mode = twc.stack_mode = Below;
+ fwc.sibling = None;
+ twc.sibling = m->barwin;
+ for (c = m->stack; c; c = c->snext) {
+ if (c->ishidden)
+ continue;
+ if (!c->isfullscreen && m->lyt && !c->isfloating) {
+ wc = &twc;
+ } else if (fwc.sibling != None) {
+ wc = &fwc;
+ } else {
+ XRaiseWindow(dpy, c->win);
+ fwc.sibling = c->win;
+ continue;
+ }
+ XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, wc);
+ wc->sibling = c->win;
+ }
+}
+
+void
+arrange(Monitor *m)
+{
+ XEvent ev;
+
+ showhide(m->stack);
+ restack(m);
+ lyttile(m);
+ XSync(dpy, False);
+ while (XCheckMaskEvent(dpy, EnterWindowMask, &ev))
+ ;
+}
diff --git a/swm.h b/swm.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+
+#include "config.h"
+
+#define LENGTH(X) (sizeof(X) / sizeof(X[0]))
+
+#define ISVISIBLE(C) ((C)->tags & (C)->mon->tagset)
+
+#define WIDTH(C) ((C)->isfullscreen ? (C)->mon->w : (C)->w + 2 * borderw)
+#define HEIGHT(C) ((C)->isfullscreen ? (C)->mon->h : (C)->h + 2 * borderw)
+
+#define BY(M) ((M)->showbar ? TOPBAR ? (M)->y : (M)->y + (M)->h - barh : -barh)
+#define WY(M) ((M)->showbar && TOPBAR ? (M)->y + barh : (M)->y)
+#define WH(M) ((M)->showbar ? (M)->h - barh : (M)->h)
+
+#define parrange(M) (pending = 1, (M)->pending |= PArrange)
+#define pdrawbar(M) (pending = 1, (M)->pending |= PDrawbar)
+
+#define TAGMASK ((1 << LENGTH(tags)) - 1)
+
+enum { WMProtocols, WMDeleteWindow, WMTakeFocus, WMState, WMLast };
+
+enum { NetSupported, NetActiveWindow, NetWMName, NetWMWindowType,
+ NetWMState, NetWMWindowTypeDialog, NetWMStateFullscreen, NetLast };
+
+enum { BarTags, BarLytDesc, BarWinTitle, BarStatus };
+
+enum { PArrange = 1, PDrawbar = 2 };
+
+typedef struct Client Client;
+typedef struct LytNode LytNode;
+typedef struct Workspace Workspace;
+typedef struct Monitor Monitor;
+
+struct Client {
+ char hintsvalid, isfixed, isfloating, isfullscreen;
+ char ishidden, isscratch, isurgent, neverfocus;
+ int x, y, w, h;
+ int maxw, maxh, minw, minh;
+ int basew, baseh, incw, inch;
+ float mina, maxa;
+ unsigned tags;
+ Window win;
+ char *appclass, *appname, *title;
+ Monitor *mon;
+ Client *next, *snext;
+ char bytes[256];
+};
+
+struct LytNode {
+ char type, ending, loaded, trailing;
+ float fact;
+ int plan[2];
+ LytNode *next;
+ union {
+ LytNode *chain;
+ Client *client;
+ } p;
+};
+
+struct Workspace {
+ unsigned tagset;
+ LytNode *lyt;
+};
+
+struct Monitor {
+ char nogaps, showbar;
+ int pending;
+ int num;
+ int selws, altws;
+ int x, y, w, h;
+ int gapih, gapiv, gapoh, gapov;
+ int tagsw, statusw;
+ Window barwin;
+ unsigned tagset;
+ Client *clients, *sel, *stack;
+ LytNode *lyt;
+ Monitor *next;
+ char lytdesc[256];
+ Workspace workspaces[LENGTH((char *[])TAGS)];
+};
+
+/* bar.c */
+void barsetup(void);
+void barcleanup(void);
+void initbar(Monitor *m);
+void runsprog(void);
+void endsprog(void);
+void updatedrawable(void);
+void updatestatus(void);
+void drawbar(Monitor *m);
+int posinbar(Monitor *m, int x, int *n);
+
+/* client.c */
+void attach(Client *c);
+void detach(Client *c);
+void attachstack(Client *c);
+void detachstack(Client *c);
+Client *wintoclient(Window w);
+Client *nextclient(Client *c);
+Client *prevclient(Client *c);
+void notifyconfigure(Client *c);
+void configure(Client *c, int resize);
+void updatetitle(Client *c);
+void updatewindowtype(Client *c);
+void updatewmhints(Client *c);
+void updatesizehints(Client *c);
+void applysizehints(Client *c, int *w, int *h);
+void elevate(Client *c);
+int sendevent(Client *c, Atom proto);
+void setclientstate(Client *c, long state);
+void setfloating(Client *c, int val);
+void setfullscreen(Client *c, int val);
+void setscratch(Client *c, int val);
+void seturgent(Client *c, int val);
+void focus(Client *c);
+void sendtomon(Client *c, Monitor *m);
+Client *manage(Window w, XWindowAttributes *wa);
+void unmanage(Client *c, int destroyed);
+
+/* config.c */
+void applyrules(Client *c);
+void execmenu(void);
+void keypress(XEvent *e);
+void buttonpress(XEvent *e);
+
+/* lyt.c */
+LytNode *lytparse(const char *desc);
+void lytfree(LytNode *lyt);
+void lyttile(Monitor *m);
+void updatelytdesc(Monitor *m);
+
+/* misc.c */
+int depart(int *pipe);
+void spawn(char **argv);
+int getrootptr(int *x, int *y);
+long getstate(Window w);
+void grabbuttons(Client *c, int focused);
+void grabkeys(void);
+void swap(Client *c, Client *d);
+void tag(unsigned ts);
+void view(unsigned ts);
+void setworkspace(unsigned ws);
+void setlayout(const char *desc);
+void incnmaster(int i);
+void incmfact(float f);
+void movebymouse(void);
+void resizebymouse(void);
+void togglescratch(char **cmd);
+void cyclescratch(void);
+void newmenu(void);
+void menuput(const char *fmt, ...);
+char *menuget(void);
+
+/* monitor.c */
+void wsload(Monitor *m);
+void wssync(Monitor *m);
+int updategeom(void);
+void cleanupgeom(void);
+Monitor *nextmon(Monitor *m);
+Monitor *prevmon(Monitor *m);
+Monitor *pttomon(int x, int y);
+void focusmon(Monitor *m);
+void arrange(Monitor *m);
+
+/* bar.c */
+extern int barh;
+extern int sfdw, sfdr;
+
+/* config.c */
+extern const unsigned borderw, snap;
+extern const int gapih, gapiv, gapoh, gapov;
+extern const char **fonts;
+extern const size_t nfonts;
+extern const char *colnf, *colnb, *colsf, *colsb;
+extern const char *colfocus, *colunfocus, *colurgent;
+extern const char *defaultlayout;
+extern char **statusprog;
+
+/* evt.c */
+extern void (*handler[LASTEvent])(XEvent *);
+
+/* main.c */
+extern int running;
+extern int restart;
+extern int pending;
+extern Display *dpy;
+extern Window root;
+extern int screen;
+extern int sw, sh; /* X display screen geometry width, height */
+extern Monitor *mons, *selmon;
+extern Atom wmatom[WMLast], netatom[NetLast];
+extern const char *tags[LENGTH((char *[])TAGS)];
+extern unsigned long pixelfocus, pixelunfocus, pixelurgent;
+extern Cursor curnormal, curresize, curmove;
+extern unsigned lockmasks[4];
diff --git a/util.c b/util.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+const char *progname;
+
+void
+vwarn(const char *fmt, va_list ap)
+{
+ int e = 0;
+ char c = '\0';
+
+ if (fmt[0]) {
+ c = fmt[strlen(fmt) - 1];
+ if (c == ':')
+ e = errno;
+ }
+ if (progname)
+ fprintf(stderr, "%s: ", progname);
+ vfprintf(stderr, fmt, ap);
+ if (c == ':')
+ fprintf(stderr, " %s", strerror(e));
+ fputc('\n', stderr);
+}
+
+void
+warn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarn(fmt, ap);
+ va_end(ap);
+}
+
+void
+die(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarn(fmt, ap);
+ va_end(ap);
+ exit(1);
+}
diff --git a/util.h b/util.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) Raymond Cole <rc@wolog.xyz>
+ *
+ * Licensed under the GNU General Public License; either version 3 of
+ * the License, or (at your option) any later version. See the LICENSE
+ * file for details.
+ *
+ * Portions of the code originally belong to dwm as developed by Anselm
+ * R. Garbe et al. See <https://git.suckless.org/dwm/plain/LICENSE>
+ * for the copyright and license details of those portions.
+ */
+
+#define MAX(A, B) ((A) > (B) ? (A) : (B))
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
+
+#ifdef va_arg
+void vwarn(const char *fmt, va_list ap);
+#endif
+void warn(const char *fmt, ...);
+void die(const char *fmt, ...);
+
+extern const char *progname;