swm

sigma window manager
Log | Files | Refs | README | LICENSE

commit a0e445f5ea171c3bf26e42e3445bd4edc213d875
Author: Raymond Cole <rc@wolog.xyz>
Date:   Thu, 24 Oct 2024 20:58:34 +0000

Initial commit

Diffstat:
A.gitignore | 2++
ALICENSE | 674+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AMakefile | 31+++++++++++++++++++++++++++++++
AREADME | 326+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abar.c | 313+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aclient.c | 595+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.c | 445+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.h | 20++++++++++++++++++++
Aconfig.mk | 28++++++++++++++++++++++++++++
Adrw.c | 453+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adrw.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aevt.c | 277+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alyt.c | 505+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amain.c | 601+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amisc.c | 594+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amonitor.c | 295+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aswm.h | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autil.c | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autil.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;