diff options
Diffstat (limited to 'bin')
-rw-r--r-- | bin/.gitignore | 20 | ||||
-rw-r--r-- | bin/LICENSE | 661 | ||||
-rw-r--r-- | bin/Makefile | 48 | ||||
-rw-r--r-- | bin/README | 156 | ||||
-rw-r--r-- | bin/bri.c | 83 | ||||
-rw-r--r-- | bin/dtch.c | 247 | ||||
-rw-r--r-- | bin/fbatt.c | 127 | ||||
-rw-r--r-- | bin/fbclock.c | 131 | ||||
-rw-r--r-- | bin/gfx/cocoa.m | 162 | ||||
-rw-r--r-- | bin/gfx/fb.c | 87 | ||||
-rw-r--r-- | bin/gfx/gfx.h | 24 | ||||
-rw-r--r-- | bin/gfx/none.c | 24 | ||||
-rw-r--r-- | bin/gfx/x11.c | 135 | ||||
-rw-r--r-- | bin/gfxx.c | 492 | ||||
-rw-r--r-- | bin/glitch.c | 488 | ||||
-rw-r--r-- | bin/hnel.c | 118 | ||||
-rw-r--r-- | bin/klon.c | 357 | ||||
-rw-r--r-- | bin/pbd.c | 136 | ||||
-rw-r--r-- | bin/pngo.c | 717 | ||||
-rw-r--r-- | bin/scheme.c | 219 | ||||
-rw-r--r-- | bin/wake.c | 53 | ||||
-rw-r--r-- | bin/watch.c | 93 | ||||
-rw-r--r-- | bin/xx.c | 133 |
23 files changed, 4711 insertions, 0 deletions
diff --git a/bin/.gitignore b/bin/.gitignore new file mode 100644 index 00000000..d3450745 --- /dev/null +++ b/bin/.gitignore @@ -0,0 +1,20 @@ +*.o +tags +atch +dtch +gfxx +glitch +hnel +pbcopy +pbd +pbpaste +pngo +scheme +wake +xx +klon +watch +bri +fbatt +fbclock +scheme.png diff --git a/bin/LICENSE b/bin/LICENSE new file mode 100644 index 00000000..dba13ed2 --- /dev/null +++ b/bin/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero 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 your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +<http://www.gnu.org/licenses/>. diff --git a/bin/Makefile b/bin/Makefile new file mode 100644 index 00000000..bbcc8c4b --- /dev/null +++ b/bin/Makefile @@ -0,0 +1,48 @@ +ANY_BINS = atch dtch gfxx glitch hnel pbcopy pbd pbpaste pngo scheme wake xx +BSD_BINS = klon watch +LIN_BINS = bri fbatt fbclock +ALL_BINS = $(ANY_BINS) $(BSD_BINS) $(LIN_BINS) +GFX ?= none + +CFLAGS += -Wall -Wextra -Wpedantic +LDLIBS = -ledit -lncurses -lutil -lz +LDLIBS_cocoa = -framework Cocoa +LDLIBS_x11 = -lX11 + +any: .gitignore tags $(ANY_BINS) + +bsd: any $(BSD_BINS) + +linux: any $(LIN_BINS) + +.gitignore: Makefile + echo '*.o' tags $(ALL_BINS) scheme.png | tr ' ' '\n' > .gitignore + +tags: *.c + ctags -w *.c + +atch: dtch + ln -f dtch atch + +gfxx: gfxx.o gfx/$(GFX).o + $(CC) $(LDFLAGS) gfxx.o gfx/$(GFX).o $(LDLIBS) $(LDLIBS_$(GFX)) -o $@ + +pbcopy pbpaste: pbd + ln -f pbd $@ + +scheme.png: scheme + ./scheme -t -g > scheme.png + +setuid: bri + chown root bri + chmod u+s bri + +clean: + rm -f tags *.o gfx/*.o $(ALL_BINS) + +link: + mkdir -p ~/.local/bin + ln -s -f $(ALL_BINS:%=$(PWD)/%) ~/.local/bin + +unlink: + rm -f $(ALL_BINS:%=~/.local/bin/%) diff --git a/bin/README b/bin/README new file mode 100644 index 00000000..c15f6f5a --- /dev/null +++ b/bin/README @@ -0,0 +1,156 @@ + +Tools primarily targetting Darwin and FreeBSD. Some don't build on Linux +and some only build on Linux. All code licensed AGPLv3. See LICENSE. + + bri + +Backlight brightness control for Linux through /sys/class/backlight. + + bri 255 + bri --- + bri ++ + + dtch/atch + +Session detach and attach. Simple implementation of part of screen(1) by +lending out the master end of a PTY over a UNIX domain socket. Detach +with ^Q. + + dtch a nvim & disown + atch a + + fbatt + +Battery charge indicator panel for the Linux framebuffer through +/sys/class/power_supply. + + fbclock + +Clock panel for the Linux framebuffer. Renders PSF2 bitmap fonts. + + gfxx + +Graphics data explorer. Build with GFX={cocoa,fb,x11}. Dumps PNGs. + + -c {indexed,grayscale,rgb} set color space + -p PATH load palette + -e {l,b} set byte order + -E {l,b} set bit order + -b NNNN set pad, R, G, B bits + -n N set offset + -f flip + -m mirror + -w N set width + -z N set scale + -o PREFIX set output prefix + + q quit + x dump one frame + X dump each frame + o print options + [] switch color spaces + p sample palette + P dump palette + {} switch bits presets + e swap byte order + E swap bit order + hl offset by byte + jk offset by pixel + HL offset by row + JK offset by square + ,. adjust width + <> half/double width + f flip + m mirror + -+ zoom + 0-9 set bits + + glitch + +PNG glitcher based on pngo. + + -c write to stdout + -o PATH write to file + -p broken Paeth predictor + -f filter when reconstructing + -r reconstruct when filtering + -d LIST declare pattern of comma-separated filters + -a LIST apply pattern of comma-separated filters + + hnel + +The tr(1) of PTYs, for remapping keys. Originally for preserving HJKL in +alternate keyboard layouts. Toggle remapping with ^S. + + hnel '[]{}' '{}[]' vi + + klon + +Klondike solitaire for curses. BSD-only for arc4random_uniform. + + q quit + u undo + ' ' draw + w waste + a-d foundations + 1-7 tableau + ^M auto-foundation + + pbd/pbcopy/pbpaste + +TCP server which pipes into macOS pbcopy(1) and from pbpaste(1), and +pbcopy and pbpaste implementations that connect to it. Used to share +the macOS pasteboard over SSH with RemoteForward 7062 127.0.0.1:7062. +This used to make nvim's "+ register work but they seem to have changed +their detection. + + pbd & disown + ssh tux.local + pbpaste + + pngo + +PNG optimizer. Does not support interlaced PNGs. + + - Discards ancillary chunks + - Discards unnecessary alpha channel + - Converts unnecessary truecolor to grayscale + - Indexes color if possible + - Reduces bit depth if possible + - Applies a simple filter type heuristic + - Applies zlib's best compression + + pngo foo.png + pngo -o bar.png foo.png + pngo -c foo.png | xx + + scheme + +Color scheme for terminals. + + -a generate ANSI palette + -t generate Terminal.app palette + -x output hex + -g output PNG + + wake + +Broadcasts a wake-on-LAN packet to one of my machines. + + watch + +Executes a command each time files change. BSD-only for kqueue(2). + + watch watch.c make + watch wake.c watch.c -- make wake watch + + xx + +Hexdump tool. + + -a toggle ASCII + -c N set columns + -g N set grouping + -r reverse hexdump + -s toggle offsets + -z skip zeros diff --git a/bin/bri.c b/bin/bri.c new file mode 100644 index 00000000..c3ec723c --- /dev/null +++ b/bin/bri.c @@ -0,0 +1,83 @@ +/* Copyright (c) 2017, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +static const char *CLASS = "/sys/class/backlight"; + +int main(int argc, char *argv[]) { + int error; + + const char *input = (argc > 1) ? argv[1] : NULL; + + error = chdir(CLASS); + if (error) err(EX_OSFILE, "%s", CLASS); + + DIR *dir = opendir("."); + if (!dir) err(EX_OSFILE, "%s", CLASS); + + struct dirent *entry; + while (NULL != (errno = 0, entry = readdir(dir))) { + if (entry->d_name[0] == '.') continue; + + error = chdir(entry->d_name); + if (error) err(EX_OSFILE, "%s/%s", CLASS, entry->d_name); + break; + } + if (!entry) { + if (errno) err(EX_IOERR, "%s", CLASS); + errx(EX_CONFIG, "%s: empty", CLASS); + } + + FILE *actual = fopen("actual_brightness", "r"); + if (!actual) err(EX_OSFILE, "%s/actual_brightness", CLASS); + + unsigned value; + int match = fscanf(actual, "%u", &value); + if (match == EOF) err(EX_IOERR, "%s/actual_brightness", CLASS); + if (match < 1) errx(EX_DATAERR, "%s/actual_brightness", CLASS); + + if (!input) { + printf("%u\n", value); + return EX_OK; + } + + if (input[0] == '+' || input[0] == '-') { + size_t count = strnlen(input, 16); + if (input[0] == '+') { + value += 16 * count; + } else { + value -= 16 * count; + } + } else { + value = strtoul(input, NULL, 0); + } + + FILE *brightness = fopen("brightness", "w"); + if (!brightness) err(EX_OSFILE, "%s/brightness", CLASS); + + int size = fprintf(brightness, "%u", value); + if (size < 0) err(EX_IOERR, "brightness"); + + return EX_OK; +} diff --git a/bin/dtch.c b/bin/dtch.c new file mode 100644 index 00000000..17bbf118 --- /dev/null +++ b/bin/dtch.c @@ -0,0 +1,247 @@ +/* Copyright (c) 2017, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <sysexits.h> +#include <termios.h> +#include <unistd.h> + +#if defined __FreeBSD__ +#include <libutil.h> +#elif defined __linux__ +#include <pty.h> +#else +#include <util.h> +#endif + +static struct passwd *getUser(void) { + uid_t uid = getuid(); + struct passwd *user = getpwuid(uid); + if (!user) err(EX_OSFILE, "/etc/passwd"); + return user; +} + +static struct sockaddr_un sockAddr(const char *home, const char *name) { + struct sockaddr_un addr = { .sun_family = AF_LOCAL }; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.dtch/%s", home, name); + return addr; +} + +static char z; +static struct iovec iov = { .iov_base = &z, .iov_len = 1 }; + +static ssize_t sendFd(int sock, int fd) { + size_t size = CMSG_LEN(sizeof(int)); + char buf[size]; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = buf, + .msg_controllen = size, + }; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = size; + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsg) = fd; + + return sendmsg(sock, &msg, 0); +} + +static int recvFd(int sock) { + size_t size = CMSG_LEN(sizeof(int)); + char buf[size]; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = buf, + .msg_controllen = size, + }; + + ssize_t n = recvmsg(sock, &msg, 0); + if (n < 0) return -1; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg || cmsg->cmsg_type != SCM_RIGHTS) { + errno = ENOMSG; + return -1; + } + + return *(int *)CMSG_DATA(cmsg); +} + +static struct sockaddr_un addr; +static void unlinkAddr(void) { + unlink(addr.sun_path); +} + +static int dtch(int argc, char *argv[]) { + int error; + + const struct passwd *user = getUser(); + + const char *name = user->pw_name; + if (argc > 1) { + name = argv[1]; + argv++; + argc--; + } + if (argc > 1) { + argv++; + } else { + argv[0] = user->pw_shell; + } + + int home = open(user->pw_dir, 0); + if (home < 0) err(EX_CANTCREAT, "%s", user->pw_dir); + + error = mkdirat(home, ".dtch", 0700); + if (error && errno != EEXIST) err(EX_CANTCREAT, "%s/.dtch", user->pw_dir); + + close(home); + + int server = socket(PF_LOCAL, SOCK_STREAM, 0); + if (server < 0) err(EX_OSERR, "socket"); + + addr = sockAddr(user->pw_dir, name); + error = bind(server, (struct sockaddr *)&addr, sizeof(addr)); + if (error) err(EX_CANTCREAT, "%s", addr.sun_path); + fcntl(server, F_SETFD, FD_CLOEXEC); + atexit(unlinkAddr); + + int pty; + pid_t pid = forkpty(&pty, NULL, NULL, NULL); + if (pid < 0) err(EX_OSERR, "forkpty"); + + if (!pid) { + execvp(*argv, argv); + err(EX_NOINPUT, "%s", *argv); + } + + error = listen(server, 0); + if (error) err(EX_OSERR, "listen"); + + for (;;) { + int client = accept(server, NULL, NULL); + if (client < 0) err(EX_IOERR, "accept"); + + ssize_t size = sendFd(client, pty); + if (size < 0) warn("sendmsg"); + + size = recv(client, &z, sizeof(z), 0); + if (size < 0) warn("recv"); + + close(client); + + int status; + pid_t dead = waitpid(pid, &status, WNOHANG); + if (dead < 0) err(EX_OSERR, "waitpid(%d)", pid); + if (dead) return WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE; + } +} + +static struct termios saveTerm; +static void restoreTerm(void) { + tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm); + printf( + "\x1b[?1049l" // rmcup + "\x1b\x63\x1b[!p\x1b[?3;4l\x1b[4l\x1b>" // reset + ); +} + +static int atch(int argc, char *argv[]) { + int error; + + const struct passwd *user = getUser(); + const char *name = (argc > 1) ? argv[1] : user->pw_name; + + int client = socket(PF_LOCAL, SOCK_STREAM, 0); + if (client < 0) err(EX_OSERR, "socket"); + + struct sockaddr_un addr = sockAddr(user->pw_dir, name); + error = connect(client, (struct sockaddr *)&addr, sizeof(addr)); + if (error) err(EX_NOINPUT, "%s", addr.sun_path); + + int pty = recvFd(client); + if (pty < 0) err(EX_IOERR, "recvmsg"); + + struct winsize window; + error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window); + if (error) err(EX_IOERR, "TIOCGWINSZ"); + + struct winsize redraw = { .ws_row = 1, .ws_col = 1 }; + error = ioctl(pty, TIOCSWINSZ, &redraw); + if (error) err(EX_IOERR, "TIOCSWINSZ"); + + error = ioctl(pty, TIOCSWINSZ, &window); + if (error) err(EX_IOERR, "TIOCSWINSZ"); + + error = tcgetattr(STDIN_FILENO, &saveTerm); + if (error) err(EX_IOERR, "tcgetattr"); + atexit(restoreTerm); + + struct termios raw; + cfmakeraw(&raw); + error = tcsetattr(STDIN_FILENO, TCSADRAIN, &raw); + if (error) err(EX_IOERR, "tcsetattr"); + + char buf[4096]; + struct pollfd fds[2] = { + { .fd = STDIN_FILENO, .events = POLLIN }, + { .fd = pty, .events = POLLIN }, + }; + while (0 < poll(fds, 2, -1)) { + if (fds[0].revents) { + ssize_t size = read(STDIN_FILENO, buf, sizeof(buf)); + if (size < 0) err(EX_IOERR, "read(%d)", STDIN_FILENO); + + if (size == 1 && buf[0] == CTRL('Q')) return EX_OK; + + size = write(pty, buf, size); + if (size < 0) err(EX_IOERR, "write(%d)", pty); + } + + if (fds[1].revents) { + ssize_t size = read(pty, buf, sizeof(buf)); + if (size < 0) err(EX_IOERR, "read(%d)", pty); + + size = write(STDOUT_FILENO, buf, size); + if (size < 0) err(EX_IOERR, "write(%d)", STDOUT_FILENO); + } + } + err(EX_IOERR, "poll"); +} + +int main(int argc, char *argv[]) { + switch (argv[0][0]) { + case 'd': return dtch(argc, argv); + case 'a': return atch(argc, argv); + default: return EX_USAGE; + } +} diff --git a/bin/fbatt.c b/bin/fbatt.c new file mode 100644 index 00000000..6c0052e4 --- /dev/null +++ b/bin/fbatt.c @@ -0,0 +1,127 @@ +/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/fb.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sysexits.h> +#include <unistd.h> + +static const char *CLASS = "/sys/class/power_supply"; + +static const uint32_t RIGHT = 5 * 8 + 1; // fbclock width. +static const uint32_t WIDTH = 8; +static const uint32_t HEIGHT = 16; + +static const uint32_t BG = 0x1D2021; +static const uint32_t BORDER = 0xA99A84; +static const uint32_t GRAY = 0x938374; +static const uint32_t YELLOW = 0xD89A22; +static const uint32_t RED = 0xCC241D; + +int main() { + int error; + + DIR *dir = opendir(CLASS); + if (!dir) err(EX_OSFILE, "%s", CLASS); + + FILE *chargeFull = NULL; + FILE *chargeNow = NULL; + + const struct dirent *entry; + while (NULL != (errno = 0, entry = readdir(dir))) { + if (entry->d_name[0] == '.') continue; + + error = chdir(CLASS); + if (error) err(EX_OSFILE, "%s", CLASS); + + error = chdir(entry->d_name); + if (error) err(EX_OSFILE, "%s/%s", CLASS, entry->d_name); + + chargeFull = fopen("charge_full", "r"); + chargeNow = fopen("charge_now", "r"); + if (chargeFull && chargeNow) break; + } + if (!chargeFull || !chargeNow) { + if (errno) err(EX_OSFILE, "%s", CLASS); + errx(EX_CONFIG, "%s: empty", CLASS); + } + closedir(dir); + + const char *path = getenv("FRAMEBUFFER"); + if (!path) path = "/dev/fb0"; + + int fb = open(path, O_RDWR); + if (fb < 0) err(EX_OSFILE, "%s", path); + + struct fb_var_screeninfo info; + error = ioctl(fb, FBIOGET_VSCREENINFO, &info); + if (error) err(EX_IOERR, "%s", path); + + size_t size = 4 * info.xres * info.yres; + uint32_t *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0); + if (buf == MAP_FAILED) err(EX_IOERR, "%s", path); + + for (;;) { + int match; + + rewind(chargeFull); + fflush(chargeFull); + uint32_t full; + match = fscanf(chargeFull, "%u", &full); + if (match == EOF) err(EX_IOERR, "charge_full"); + if (match < 1) errx(EX_DATAERR, "charge_full"); + + rewind(chargeNow); + fflush(chargeNow); + uint32_t now; + match = fscanf(chargeNow, "%u", &now); + if (match == EOF) err(EX_IOERR, "charge_now"); + if (match < 1) errx(EX_DATAERR, "charge_now"); + + uint32_t percent = 100 * now / full; + uint32_t height = 16 * now / full; + + for (int i = 0; i < 60; ++i, sleep(1)) { + uint32_t left = info.xres - RIGHT - WIDTH; + + for (uint32_t y = 0; y <= HEIGHT; ++y) { + buf[y * info.xres + left - 1] = BORDER; + buf[y * info.xres + left + WIDTH] = BORDER; + } + for (uint32_t x = left; x < left + WIDTH; ++x) { + buf[HEIGHT * info.xres + x] = BORDER; + } + + for (uint32_t y = 0; y < HEIGHT; ++y) { + for (uint32_t x = left; x < left + WIDTH; ++x) { + buf[y * info.xres + x] = + (HEIGHT - 1 - y > height) ? BG + : (percent <= 10) ? RED + : (percent <= 30) ? YELLOW + : GRAY; + } + } + } + } +} diff --git a/bin/fbclock.c b/bin/fbclock.c new file mode 100644 index 00000000..605fa4e0 --- /dev/null +++ b/bin/fbclock.c @@ -0,0 +1,131 @@ +/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <err.h> +#include <fcntl.h> +#include <linux/fb.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sysexits.h> +#include <time.h> +#include <unistd.h> +#include <zlib.h> + +static const uint32_t PSF2_MAGIC = 0x864AB572; +struct Psf2Header { + uint32_t magic; + uint32_t version; + uint32_t headerSize; + uint32_t flags; + uint32_t glyphCount; + uint32_t glyphSize; + uint32_t glyphHeight; + uint32_t glyphWidth; +}; + +static const uint32_t BG = 0x1D2021; +static const uint32_t FG = 0xA99A84; + +int main() { + size_t len; + + const char *fontPath = getenv("FONT"); + if (!fontPath) { + fontPath = "/usr/share/kbd/consolefonts/Lat2-Terminus16.psfu.gz"; + } + + gzFile font = gzopen(fontPath, "r"); + if (!font) err(EX_NOINPUT, "%s", fontPath); + + struct Psf2Header header; + len = gzfread(&header, sizeof(header), 1, font); + if (!len && gzeof(font)) errx(EX_DATAERR, "%s: missing header", fontPath); + if (!len) errx(EX_IOERR, "%s", gzerror(font, NULL)); + + if (header.magic != PSF2_MAGIC) { + errx( + EX_DATAERR, "%s: invalid header magic %08x", + fontPath, header.magic + ); + } + if (header.headerSize != sizeof(struct Psf2Header)) { + errx( + EX_DATAERR, "%s: weird header size %d", + fontPath, header.headerSize + ); + } + + uint8_t glyphs[128][header.glyphSize]; + len = gzfread(glyphs, header.glyphSize, 128, font); + if (!len && gzeof(font)) errx(EX_DATAERR, "%s: missing glyphs", fontPath); + if (!len) errx(EX_IOERR, "%s", gzerror(font, NULL)); + + gzclose(font); + + const char *fbPath = getenv("FRAMEBUFFER"); + if (!fbPath) fbPath = "/dev/fb0"; + + int fb = open(fbPath, O_RDWR); + if (fb < 0) err(EX_OSFILE, "%s", fbPath); + + struct fb_var_screeninfo info; + int error = ioctl(fb, FBIOGET_VSCREENINFO, &info); + if (error) err(EX_IOERR, "%s", fbPath); + + size_t size = 4 * info.xres * info.yres; + uint32_t *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0); + if (buf == MAP_FAILED) err(EX_IOERR, "%s", fbPath); + + for (;;) { + time_t t = time(NULL); + if (t < 0) err(EX_OSERR, "time"); + const struct tm *local = localtime(&t); + if (!local) err(EX_OSERR, "localtime"); + + char str[64]; + len = strftime(str, sizeof(str), "%H:%M", local); + assert(len); + + for (int i = 0; i < (60 - local->tm_sec); ++i, sleep(1)) { + uint32_t left = info.xres - header.glyphWidth * len; + uint32_t bottom = header.glyphHeight; + + for (uint32_t y = 0; y < bottom; ++y) { + buf[y * info.xres + left - 1] = FG; + } + for (uint32_t x = left - 1; x < info.xres; ++x) { + buf[bottom * info.xres + x] = FG; + } + + for (const char *s = str; *s; ++s) { + const uint8_t *glyph = glyphs[(unsigned)*s]; + uint32_t stride = header.glyphSize / header.glyphHeight; + for (uint32_t y = 0; y < header.glyphHeight; ++y) { + for (uint32_t x = 0; x < header.glyphWidth; ++x) { + uint8_t bits = glyph[y * stride + x / 8]; + uint8_t bit = bits >> (7 - x % 8) & 1; + buf[y * info.xres + left + x] = bit ? FG : BG; + } + } + left += header.glyphWidth; + } + } + } +} diff --git a/bin/gfx/cocoa.m b/bin/gfx/cocoa.m new file mode 100644 index 00000000..4837a386 --- /dev/null +++ b/bin/gfx/cocoa.m @@ -0,0 +1,162 @@ +/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Cocoa/Cocoa.h> +#import <err.h> +#import <stdbool.h> +#import <stdint.h> +#import <stdlib.h> +#import <sysexits.h> + +#import "gfx.h" + +#define UNUSED __attribute__((unused)) + +@interface BufferView : NSView { + size_t bufSize; + uint32_t *buf; + CGColorSpaceRef colorSpace; + CGDataProviderRef dataProvider; +} +@end + +@implementation BufferView +- (instancetype) initWithFrame: (NSRect) frameRect { + colorSpace = CGColorSpaceCreateDeviceRGB(); + return [super initWithFrame: frameRect]; +} + +- (void) setWindowTitle { + [[self window] setTitle: [NSString stringWithUTF8String: status()]]; +} + +- (void) draw { + draw(buf, [self frame].size.width, [self frame].size.height); + [self setNeedsDisplay: YES]; +} + +- (void) setFrameSize: (NSSize) newSize { + [super setFrameSize: newSize]; + size_t newBufSize = 4 * newSize.width * newSize.height; + if (newBufSize > bufSize) { + bufSize = newBufSize; + buf = malloc(bufSize); + if (!buf) err(EX_OSERR, "malloc(%zu)", bufSize); + CGDataProviderRelease(dataProvider); + dataProvider = CGDataProviderCreateWithData(NULL, buf, bufSize, NULL); + } + [self draw]; +} + +- (void) drawRect: (NSRect) UNUSED dirtyRect { + NSSize size = [self frame].size; + CGContextRef ctx = [[NSGraphicsContext currentContext] CGContext]; + CGImageRef image = CGImageCreate( + size.width, size.height, + 8, 32, 4 * size.width, + colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, + dataProvider, + NULL, false, kCGRenderingIntentDefault + ); + CGContextDrawImage(ctx, [self frame], image); + CGImageRelease(image); +} + +- (BOOL) acceptsFirstResponder { + return YES; +} + +- (void) keyDown: (NSEvent *) event { + char in; + BOOL converted = [ + [event characters] + getBytes: &in + maxLength: 1 + usedLength: NULL + encoding: NSASCIIStringEncoding + options: 0 + range: NSMakeRange(0, 1) + remainingRange: NULL + ]; + if (converted) { + if (!input(in)) { + [NSApp terminate: self]; + } + [self setWindowTitle]; + [self draw]; + } +} +@end + +@interface Delegate : NSObject <NSApplicationDelegate> +@end + +@implementation Delegate +- (BOOL) applicationShouldTerminateAfterLastWindowClosed: + (NSApplication *) UNUSED sender { + return YES; +} +@end + +int main(int argc, char *argv[]) { + int error = init(argc, argv); + if (error) return error; + + [NSApplication sharedApplication]; + [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular]; + [NSApp setDelegate: [Delegate new]]; + + NSString *name = [[NSProcessInfo processInfo] processName]; + NSMenu *menu = [NSMenu new]; + [ + menu + addItemWithTitle: @"Close Window" + action: @selector(performClose:) + keyEquivalent: @"w" + ]; + [menu addItem: [NSMenuItem separatorItem]]; + [ + menu + addItemWithTitle: [@"Quit " stringByAppendingString: name] + action: @selector(terminate:) + keyEquivalent: @"q" + ]; + NSMenuItem *menuItem = [NSMenuItem new]; + [menuItem setSubmenu: menu]; + [NSApp setMainMenu: [NSMenu new]]; + [[NSApp mainMenu] addItem: menuItem]; + + NSUInteger style = NSTitledWindowMask + | NSClosableWindowMask + | NSMiniaturizableWindowMask + | NSResizableWindowMask; + NSWindow *window = [ + [NSWindow alloc] + initWithContentRect: NSMakeRect(0, 0, 800, 600) + styleMask: style + backing: NSBackingStoreBuffered + defer: YES + ]; + [window center]; + + BufferView *view = [[BufferView alloc] initWithFrame: [window frame]]; + [window setContentView: view]; + [view setWindowTitle]; + + [window makeKeyAndOrderFront: nil]; + [NSApp activateIgnoringOtherApps: YES]; + [NSApp run]; +} diff --git a/bin/gfx/fb.c b/bin/gfx/fb.c new file mode 100644 index 00000000..94a3245e --- /dev/null +++ b/bin/gfx/fb.c @@ -0,0 +1,87 @@ +/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <fcntl.h> +#include <linux/fb.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sysexits.h> +#include <termios.h> +#include <unistd.h> + +#include "gfx.h" + +static struct termios saveTerm; +static void restoreTerm(void) { + tcsetattr(STDERR_FILENO, TCSADRAIN, &saveTerm); +} + +int main(int argc, char *argv[]) { + int error; + + error = init(argc, argv); + if (error) return error; + + const char *path = getenv("FRAMEBUFFER"); + if (!path) path = "/dev/fb0"; + + int fb = open(path, O_RDWR); + if (fb < 0) err(EX_OSFILE, "%s", path); + + struct fb_var_screeninfo info; + error = ioctl(fb, FBIOGET_VSCREENINFO, &info); + if (error) err(EX_IOERR, "%s", path); + + size_t size = 4 * info.xres * info.yres; + uint32_t *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0); + if (buf == MAP_FAILED) err(EX_IOERR, "%s", path); + + error = tcgetattr(STDERR_FILENO, &saveTerm); + if (error) err(EX_IOERR, "tcgetattr"); + atexit(restoreTerm); + + struct termios term = saveTerm; + term.c_lflag &= ~(ICANON | ECHO); + error = tcsetattr(STDERR_FILENO, TCSADRAIN, &term); + if (error) err(EX_IOERR, "tcsetattr"); + + uint32_t saveBg = buf[0]; + + uint32_t back[info.xres * info.yres]; + for (;;) { + draw(back, info.xres, info.yres); + memcpy(buf, back, size); + + char in; + ssize_t len = read(STDERR_FILENO, &in, 1); + if (len < 0) err(EX_IOERR, "read"); + if (!len) return EX_DATAERR; + + if (!input(in)) { + for (uint32_t i = 0; i < info.xres * info.yres; ++i) { + buf[i] = saveBg; + } + fprintf(stderr, "%s\n", status()); + return EX_OK; + } + } +} diff --git a/bin/gfx/gfx.h b/bin/gfx/gfx.h new file mode 100644 index 00000000..cd59ea3d --- /dev/null +++ b/bin/gfx/gfx.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +extern int init(int argc, char *argv[]); +extern const char *status(void); +extern void draw(uint32_t *buf, size_t width, size_t height); +extern bool input(char in); diff --git a/bin/gfx/none.c b/bin/gfx/none.c new file mode 100644 index 00000000..6decb24b --- /dev/null +++ b/bin/gfx/none.c @@ -0,0 +1,24 @@ +/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <sysexits.h> + +#include "gfx.h" + +int main() { + errx(EX_CONFIG, "no gfx frontend"); +} diff --git a/bin/gfx/x11.c b/bin/gfx/x11.c new file mode 100644 index 00000000..108d6459 --- /dev/null +++ b/bin/gfx/x11.c @@ -0,0 +1,135 @@ +/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <X11/Xlib.h> +#include <err.h> +#include <sysexits.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdbool.h> + +#include "gfx.h" + +static size_t width = 800; +static size_t height = 600; + +static Display *display; +static Window window; +static Atom WM_DELETE_WINDOW; +static GC windowGc; +static XImage *image; + +static size_t bufSize; +static uint32_t *buf; + +static size_t pixmapWidth; +static size_t pixmapHeight; +static Pixmap pixmap; + +static void createWindow(void) { + display = XOpenDisplay(NULL); + if (!display) errx(EX_UNAVAILABLE, "XOpenDisplay: %s", XDisplayName(NULL)); + + Window root = DefaultRootWindow(display); + window = XCreateSimpleWindow(display, root, 0, 0, width, height, 0, 0, 0); + + WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", false); + XSetWMProtocols(display, window, &WM_DELETE_WINDOW, 1); + + windowGc = XCreateGC(display, window, 0, NULL); + + image = XCreateImage(display, NULL, 24, ZPixmap, 0, NULL, width, height, 32, 0); +} + +static void resizePixmap(void) { + size_t newSize = 4 * width * height; + if (newSize > bufSize) { + bufSize = newSize; + free(buf); + buf = malloc(bufSize); + if (!buf) err(EX_OSERR, "malloc(%zu)", bufSize); + } + + image->data = (char *)buf; + image->width = width; + image->height = height; + image->bytes_per_line = 4 * width; + + if (width > pixmapWidth || height > pixmapHeight) { + pixmapWidth = width; + pixmapHeight = height; + if (pixmap) XFreePixmap(display, pixmap); + pixmap = XCreatePixmap(display, window, pixmapWidth, pixmapHeight, 24); + } +} + +static void drawWindow(void) { + draw(buf, width, height); + XPutImage(display, pixmap, windowGc, image, 0, 0, 0, 0, width, height); + XCopyArea(display, pixmap, window, windowGc, 0, 0, width, height, 0, 0); +} + +int main(int argc, char *argv[]) { + int error = init(argc, argv); + if (error) return error; + + createWindow(); + resizePixmap(); + drawWindow(); + + XStoreName(display, window, status()); + XMapWindow(display, window); + + XEvent event; + XSelectInput(display, window, ExposureMask | StructureNotifyMask | KeyPressMask); + for (;;) { + XNextEvent(display, &event); + + switch (event.type) { + case KeyPress: { + XKeyEvent key = event.xkey; + KeySym sym = XLookupKeysym(&key, key.state & ShiftMask); + if (sym > 0x80) break; + if (!input(sym)) return EX_OK; + XStoreName(display, window, status()); + drawWindow(); + } break; + + case ConfigureNotify: { + XConfigureEvent configure = event.xconfigure; + width = configure.width; + height = configure.height; + resizePixmap(); + drawWindow(); + } break; + + case Expose: { + XExposeEvent expose = event.xexpose; + XCopyArea( + display, pixmap, window, windowGc, + expose.x, expose.y, + expose.width, expose.height, + expose.x, expose.y + ); + } break; + + case ClientMessage: { + XClientMessageEvent client = event.xclient; + if ((Atom)client.data.l[0] == WM_DELETE_WINDOW) return EX_OK; + } break; + } + } +} diff --git a/bin/gfxx.c b/bin/gfxx.c new file mode 100644 index 00000000..8a43fd2f --- /dev/null +++ b/bin/gfxx.c @@ -0,0 +1,492 @@ +/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <arpa/inet.h> +#include <err.h> +#include <fcntl.h> +#include <math.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sysexits.h> +#include <unistd.h> +#include <zlib.h> + +#include "gfx/gfx.h" + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MASK(b) ((1 << (b)) - 1) + +#define RGB(r, g, b) ((uint32_t)(r) << 16 | (uint32_t)(g) << 8 | (uint32_t)(b)) +#define GRAY(n) RGB(n, n, n) + +static enum { + COLOR_INDEXED, + COLOR_GRAYSCALE, + COLOR_RGB, + COLOR__COUNT, +} space = COLOR_RGB; +static const char *COLOR__STR[COLOR__COUNT] = { "indexed", "grayscale", "rgb" }; +static uint32_t palette[256]; + +static enum { + ENDIAN_LITTLE, + ENDIAN_BIG, +} byteOrder, bitOrder; + +enum { PAD, R, G, B }; +static uint8_t bits[4] = { 8, 8, 8, 8 }; +#define BITS_COLOR (bits[R] + bits[G] + bits[B]) +#define BITS_TOTAL (bits[PAD] + BITS_COLOR) + +static size_t offset; +static size_t width = 16; +static bool flip; +static bool mirror; +static size_t scale = 1; + +static const char *prefix = "gfxx"; + +static size_t size; +static uint8_t *data; + +int init(int argc, char *argv[]) { + const char *pal = NULL; + const char *path = NULL; + + int opt; + while (0 < (opt = getopt(argc, argv, "c:p:b:e:E:n:fmw:z:o:"))) { + switch (opt) { + case 'c': switch (optarg[0]) { + case 'i': space = COLOR_INDEXED; break; + case 'g': space = COLOR_GRAYSCALE; break; + case 'r': space = COLOR_RGB; break; + default: return EX_USAGE; + } break; + case 'p': pal = optarg; break; + case 'e': switch (optarg[0]) { + case 'l': byteOrder = ENDIAN_LITTLE; break; + case 'b': byteOrder = ENDIAN_BIG; break; + default: return EX_USAGE; + } break; + case 'E': switch (optarg[0]) { + case 'l': bitOrder = ENDIAN_LITTLE; break; + case 'b': bitOrder = ENDIAN_BIG; break; + default: return EX_USAGE; + } break; + case 'b': { + if (strlen(optarg) < 4) return EX_USAGE; + for (int i = 0; i < 4; ++i) { + bits[i] = optarg[i] - '0'; + } + } break; + case 'n': offset = strtoul(optarg, NULL, 0); break; + case 'f': flip ^= true; break; + case 'm': mirror ^= true; break; + case 'w': width = strtoul(optarg, NULL, 0); break; + case 'z': scale = strtoul(optarg, NULL, 0); break; + case 'o': prefix = optarg; break; + default: return EX_USAGE; + } + } + if (argc > optind) path = argv[optind]; + if (!width || !scale) return EX_USAGE; + + if (pal) { + FILE *file = fopen(pal, "r"); + if (!file) err(EX_NOINPUT, "%s", pal); + fread(palette, 4, 256, file); + if (ferror(file)) err(EX_IOERR, "%s", pal); + fclose(file); + } else { + for (int i = 0; i < 256; ++i) { + double h = i / 256.0 * 6.0; + double x = 1.0 - fabs(fmod(h, 2.0) - 1.0); + double r = 255.0, g = 255.0, b = 255.0; + if (h <= 1.0) { g *= x; b = 0.0; } + else if (h <= 2.0) { r *= x; b = 0.0; } + else if (h <= 3.0) { r = 0.0; b *= x; } + else if (h <= 4.0) { r = 0.0; g *= x; } + else if (h <= 5.0) { r *= x; g = 0.0; } + else if (h <= 6.0) { g = 0.0; b *= x; } + palette[i] = (uint32_t)r << 16 | (uint32_t)g << 8 | (uint32_t)b; + } + } + + if (path) { + int fd = open(path, O_RDONLY); + if (fd < 0) err(EX_NOINPUT, "%s", path); + + struct stat stat; + int error = fstat(fd, &stat); + if (error) err(EX_IOERR, "%s", path); + size = stat.st_size; + + data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) err(EX_IOERR, "%s", path); + + } else { + size = 1024 * 1024; + data = malloc(size); + if (!data) err(EX_OSERR, "malloc(%zu)", size); + + size = fread(data, 1, size, stdin); + if (ferror(stdin)) err(EX_IOERR, "(stdin)"); + } + + return EX_OK; +} + +static char options[128]; +static void formatOptions(void) { + snprintf( + options, sizeof(options), + "gfxx -c %s -e%c -E%c -b %hhu%hhu%hhu%hhu -n %#zx %s%s-w %zu -z %zu", + COLOR__STR[space], + "lb"[byteOrder], + "lb"[bitOrder], + bits[PAD], bits[R], bits[G], bits[B], + offset, + flip ? "-f " : "", + mirror ? "-m " : "", + width, + scale + ); +} + +const char *status(void) { + formatOptions(); + return options; +} + +struct Iter { + uint32_t *buf; + size_t bufWidth; + size_t bufHeight; + size_t left; + size_t x; + size_t y; +}; + +static struct Iter iter(uint32_t *buf, size_t bufWidth, size_t bufHeight) { + struct Iter it = { .buf = buf, .bufWidth = bufWidth, .bufHeight = bufHeight }; + if (mirror) it.x = width - 1; + if (flip) it.y = bufHeight / scale - 1; + return it; +} + +static bool nextX(struct Iter *it) { + if (mirror) { + if (it->x == it->left) return false; + it->x--; + } else { + it->x++; + if (it->x == it->left + width) return false; + } + return true; +} + +static bool nextY(struct Iter *it) { + if (flip) { + if (it->y == 0) { + it->left += width; + it->y = it->bufHeight / scale; + } + it->y--; + } else { + it->y++; + if (it->y == it->bufHeight / scale) { + it->left += width; + it->y = 0; + } + } + it->x = it->left; + if (mirror) it->x += width - 1; + return (it->left < it->bufWidth / scale); +} + +static bool next(struct Iter *it) { + return nextX(it) || nextY(it); +} + +static void put(const struct Iter *it, uint32_t pixel) { + size_t scaledX = it->x * scale; + size_t scaledY = it->y * scale; + for (size_t fillY = scaledY; fillY < scaledY + scale; ++fillY) { + if (fillY >= it->bufHeight) break; + for (size_t fillX = scaledX; fillX < scaledX + scale; ++fillX) { + if (fillX >= it->bufWidth) break; + it->buf[fillY * it->bufWidth + fillX] = pixel; + } + } +} + +static uint8_t interp(uint8_t b, uint32_t n) { + if (b == 8) return n; + if (b == 0) return 0; + return n * MASK(8) / MASK(b); +} + +static uint32_t interpolate(uint32_t rgb) { + uint32_t r, g, b; + if (bitOrder == ENDIAN_LITTLE) { + b = rgb & MASK(bits[B]); + g = (rgb >>= bits[B]) & MASK(bits[G]); + r = (rgb >>= bits[G]) & MASK(bits[R]); + } else { + r = rgb & MASK(bits[R]); + g = (rgb >>= bits[R]) & MASK(bits[G]); + b = (rgb >>= bits[G]) & MASK(bits[B]); + } + return RGB(interp(bits[R], r), interp(bits[G], g), interp(bits[B], b)); +} + +static void drawBits(struct Iter *it) { + for (size_t i = offset; i < size; ++i) { + for (uint8_t b = 0; b < 8; b += BITS_TOTAL) { + uint8_t n; + if (byteOrder == ENDIAN_BIG) { + n = data[i] >> (8 - BITS_TOTAL - b) & MASK(BITS_TOTAL); + } else { + n = data[i] >> b & MASK(BITS_TOTAL); + } + + if (space == COLOR_INDEXED) { + put(it, palette[n]); + } else if (space == COLOR_GRAYSCALE) { + put(it, GRAY(interp(BITS_COLOR, n & MASK(BITS_COLOR)))); + } else if (space == COLOR_RGB) { + put(it, interpolate(n)); + } + + if (!next(it)) return; + } + } +} + +static void drawBytes(struct Iter *it) { + uint8_t bytes = (BITS_TOTAL + 7) / 8; + for (size_t i = offset; i + bytes <= size; i += bytes) { + uint32_t n = 0; + for (size_t b = 0; b < bytes; ++b) { + n <<= 8; + n |= (byteOrder == ENDIAN_BIG) ? data[i + b] : data[i + bytes - b - 1]; + } + + if (space == COLOR_INDEXED) { + put(it, palette[n & 0xFF]); + } else if (space == COLOR_GRAYSCALE) { + put(it, GRAY(interp(BITS_COLOR, n & MASK(BITS_COLOR)))); + } else if (space == COLOR_RGB) { + put(it, interpolate(n)); + } + + if (!next(it)) return; + } +} + +static struct { + unsigned counter; + char path[FILENAME_MAX]; + FILE *file; +} out; +static void outOpen(const char *ext) { + snprintf(out.path, sizeof(out.path), "%s%04u.%s", prefix, ++out.counter, ext); + out.file = fopen(out.path, "wx"); + if (out.file) { + printf("%s\n", out.path); + } else { + warn("%s", out.path); + } +} + +static uint32_t crc; +static void pngWrite(const void *ptr, size_t size) { + fwrite(ptr, size, 1, out.file); + if (ferror(out.file)) err(EX_IOERR, "%s", out.path); + crc = crc32(crc, ptr, size); +} +static void pngUint(uint32_t host) { + uint32_t net = htonl(host); + pngWrite(&net, 4); +} +static void pngChunk(const char *type, uint32_t size) { + pngUint(size); + crc = crc32(0, Z_NULL, 0); + pngWrite(type, 4); +} + +static void pngDump(uint32_t *src, size_t srcWidth, size_t srcHeight) { + int error; + + size_t stride = 1 + 3 * srcWidth; + uint8_t data[stride * srcHeight]; + for (size_t y = 0; y < srcHeight; ++y) { + data[y * stride] = 0; + for (size_t x = 0; x < srcWidth; ++x) { + uint8_t *p = &data[y * stride + 1 + 3 * x]; + p[0] = src[y * srcWidth + x] >> 16; + p[1] = src[y * srcWidth + x] >> 8; + p[2] = src[y * srcWidth + x]; + } + } + + uLong deflateSize = compressBound(sizeof(data)); + uint8_t deflate[deflateSize]; + error = compress(deflate, &deflateSize, data, sizeof(data)); + if (error != Z_OK) errx(EX_SOFTWARE, "compress: %d", error); + + outOpen("png"); + if (!out.file) return; + + const uint8_t SIGNATURE[] = { 0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n' }; + const uint8_t HEADER[] = { 8, 2, 0, 0, 0 }; // 8-bit truecolor + const char SOFTWARE[] = "Software"; + formatOptions(); + uint8_t sbit[3] = { MAX(bits[R], 1), MAX(bits[G], 1), MAX(bits[B], 1) }; + + pngWrite(SIGNATURE, sizeof(SIGNATURE)); + + pngChunk("IHDR", 4 + 4 + sizeof(HEADER)); + pngUint(srcWidth); + pngUint(srcHeight); + pngWrite(HEADER, sizeof(HEADER)); + pngUint(crc); + + pngChunk("tEXt", sizeof(SOFTWARE) + strlen(options)); + pngWrite(SOFTWARE, sizeof(SOFTWARE)); + pngWrite(options, strlen(options)); + pngUint(crc); + + pngChunk("sBIT", sizeof(sbit)); + pngWrite(sbit, sizeof(sbit)); + pngUint(crc); + + pngChunk("IDAT", deflateSize); + pngWrite(deflate, deflateSize); + pngUint(crc); + + pngChunk("IEND", 0); + pngUint(crc); + + error = fclose(out.file); + if (error) err(EX_IOERR, "%s", out.path); +} + +static enum { + DUMP_NONE, + DUMP_ONE, + DUMP_ALL, +} dump; + +void draw(uint32_t *buf, size_t bufWidth, size_t bufHeight) { + memset(buf, 0, 4 * bufWidth * bufHeight); + struct Iter it = iter(buf, bufWidth, bufHeight); + if (BITS_TOTAL >= 8) { + drawBytes(&it); + } else { + drawBits(&it); + } + if (dump) pngDump(buf, bufWidth, bufHeight); + if (dump == DUMP_ONE) dump = DUMP_NONE; +} + +static void palSample(void) { + size_t temp = scale; + scale = 1; + draw(palette, 256, 1); + scale = temp; +} + +static void palDump(void) { + outOpen("dat"); + if (!out.file) return; + + fwrite(palette, 4, 256, out.file); + if (ferror(out.file)) err(EX_IOERR, "%s", out.path); + + int error = fclose(out.file); + if (error) err(EX_IOERR, "%s", out.path); +} + +static const uint8_t PRESETS[][4] = { + { 0, 0, 1, 0 }, + { 0, 1, 1, 0 }, + { 1, 1, 1, 1 }, + { 2, 2, 2, 2 }, + { 0, 3, 3, 2 }, + { 4, 4, 4, 4 }, + { 1, 5, 5, 5 }, + { 0, 5, 6, 5 }, + { 0, 8, 8, 8 }, + { 8, 8, 8, 8 }, +}; +#define PRESETS_LEN (sizeof(PRESETS) / sizeof(PRESETS[0])) + +static uint8_t preset = PRESETS_LEN - 1; +static void setPreset(void) { + bits[PAD] = PRESETS[preset][PAD]; + bits[R] = PRESETS[preset][R]; + bits[G] = PRESETS[preset][G]; + bits[B] = PRESETS[preset][B]; +} + +static void setBit(char in) { + static uint8_t bit = 0; + bits[bit++] = in - '0'; + bit &= 3; +} + +bool input(char in) { + size_t pixel = (BITS_TOTAL + 7) / 8; + size_t row = width * BITS_TOTAL / 8; + switch (in) { + case 'q': return false; + break; case 'x': dump = DUMP_ONE; + break; case 'X': dump ^= DUMP_ALL; + break; case 'o': formatOptions(); printf("%s\n", options); + break; case '[': if (!space--) space = COLOR__COUNT - 1; + break; case ']': if (++space == COLOR__COUNT) space = 0; + break; case 'p': palSample(); + break; case 'P': palDump(); + break; case '{': if (!preset--) preset = PRESETS_LEN - 1; setPreset(); + break; case '}': if (++preset == PRESETS_LEN) preset = 0; setPreset(); + break; case 'e': byteOrder ^= ENDIAN_BIG; + break; case 'E': bitOrder ^= ENDIAN_BIG; + break; case 'h': if (offset) offset--; + break; case 'j': offset += pixel; + break; case 'k': if (offset >= pixel) offset -= pixel; + break; case 'l': offset++; + break; case 'H': if (offset >= row) offset -= row; + break; case 'J': offset += width * row; + break; case 'K': if (offset >= width * row) offset -= width * row; + break; case 'L': offset += row; + break; case '.': width++; + break; case ',': if (width > 1) width--; + break; case '>': width *= 2; + break; case '<': if (width > 1) width /= 2; + break; case 'f': flip ^= true; + break; case 'm': mirror ^= true; + break; case '+': scale++; + break; case '-': if (scale > 1) scale--; + break; default: if (in >= '0' && in <= '9') setBit(in); + } + return true; +} diff --git a/bin/glitch.c b/bin/glitch.c new file mode 100644 index 00000000..ea9c083d --- /dev/null +++ b/bin/glitch.c @@ -0,0 +1,488 @@ +/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <arpa/inet.h> +#include <assert.h> +#include <err.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> +#include <zlib.h> + +#define PACKED __attribute__((packed)) + +#define CRC_INIT (crc32(0, Z_NULL, 0)) + +static const char *path; +static FILE *file; +static uint32_t crc; + +static void readExpect(void *ptr, size_t size, const char *expect) { + fread(ptr, size, 1, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + if (feof(file)) errx(EX_DATAERR, "%s: missing %s", path, expect); + crc = crc32(crc, ptr, size); +} + +static void writeExpect(const void *ptr, size_t size) { + fwrite(ptr, size, 1, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + crc = crc32(crc, ptr, size); +} + +static const uint8_t SIGNATURE[8] = { + 0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n' +}; + +static void readSignature(void) { + uint8_t signature[8]; + readExpect(signature, 8, "signature"); + if (0 != memcmp(signature, SIGNATURE, 8)) { + errx(EX_DATAERR, "%s: invalid signature", path); + } +} + +static void writeSignature(void) { + writeExpect(SIGNATURE, sizeof(SIGNATURE)); +} + +struct PACKED Chunk { + uint32_t size; + char type[4]; +}; + +static const char *typeStr(struct Chunk chunk) { + static char buf[5]; + memcpy(buf, chunk.type, 4); + return buf; +} + +static struct Chunk readChunk(void) { + struct Chunk chunk; + readExpect(&chunk, sizeof(chunk), "chunk"); + chunk.size = ntohl(chunk.size); + crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type)); + return chunk; +} + +static void writeChunk(struct Chunk chunk) { + chunk.size = htonl(chunk.size); + writeExpect(&chunk, sizeof(chunk)); + crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type)); +} + +static void readCrc(void) { + uint32_t expected = crc; + uint32_t found; + readExpect(&found, sizeof(found), "CRC32"); + found = ntohl(found); + if (found != expected) { + errx( + EX_DATAERR, "%s: expected CRC32 %08X, found %08X", + path, expected, found + ); + } +} + +static void writeCrc(void) { + uint32_t net = htonl(crc); + writeExpect(&net, sizeof(net)); +} + +static void skipChunk(struct Chunk chunk) { + uint8_t discard[chunk.size]; + readExpect(discard, sizeof(discard), "chunk data"); + readCrc(); +} + +static struct PACKED { + uint32_t width; + uint32_t height; + uint8_t depth; + enum PACKED { + GRAYSCALE = 0, + TRUECOLOR = 2, + INDEXED = 3, + GRAYSCALE_ALPHA = 4, + TRUECOLOR_ALPHA = 6, + } color; + uint8_t compression; + uint8_t filter; + uint8_t interlace; +} header; + +static size_t lineSize(void) { + switch (header.color) { + case GRAYSCALE: return (header.width * 1 * header.depth + 7) / 8; + case TRUECOLOR: return (header.width * 3 * header.depth + 7) / 8; + case INDEXED: return (header.width * 1 * header.depth + 7) / 8; + case GRAYSCALE_ALPHA: return (header.width * 2 * header.depth + 7) / 8; + case TRUECOLOR_ALPHA: return (header.width * 4 * header.depth + 7) / 8; + default: abort(); + } +} + +static size_t dataSize(void) { + return (1 + lineSize()) * header.height; +} + +static void readHeader(void) { + struct Chunk ihdr = readChunk(); + if (0 != memcmp(ihdr.type, "IHDR", 4)) { + errx(EX_DATAERR, "%s: expected IHDR, found %s", path, typeStr(ihdr)); + } + if (ihdr.size != sizeof(header)) { + errx( + EX_DATAERR, "%s: expected IHDR size %zu, found %u", + path, sizeof(header), ihdr.size + ); + } + readExpect(&header, sizeof(header), "header"); + readCrc(); + header.width = ntohl(header.width); + header.height = ntohl(header.height); + if (!header.width) errx(EX_DATAERR, "%s: invalid width 0", path); + if (!header.height) errx(EX_DATAERR, "%s: invalid height 0", path); +} + +static void writeHeader(void) { + struct Chunk ihdr = { .size = sizeof(header), .type = "IHDR" }; + writeChunk(ihdr); + header.width = htonl(header.width); + header.height = htonl(header.height); + writeExpect(&header, sizeof(header)); + writeCrc(); + header.width = ntohl(header.width); + header.height = ntohl(header.height); +} + +static struct { + uint32_t len; + uint8_t entries[256][3]; +} palette; + +static void readPalette(void) { + struct Chunk chunk; + for (;;) { + chunk = readChunk(); + if (0 == memcmp(chunk.type, "PLTE", 4)) break; + skipChunk(chunk); + } + palette.len = chunk.size / 3; + readExpect(palette.entries, chunk.size, "palette data"); + readCrc(); +} + +static void writePalette(void) { + struct Chunk plte = { .size = 3 * palette.len, .type = "PLTE" }; + writeChunk(plte); + writeExpect(palette.entries, plte.size); + writeCrc(); +} + +static uint8_t *data; + +static void readData(void) { + data = malloc(dataSize()); + if (!data) err(EX_OSERR, "malloc(%zu)", dataSize()); + + struct z_stream_s stream = { .next_out = data, .avail_out = dataSize() }; + int error = inflateInit(&stream); + if (error != Z_OK) errx(EX_SOFTWARE, "%s: inflateInit: %s", path, stream.msg); + + for (;;) { + struct Chunk chunk = readChunk(); + if (0 == memcmp(chunk.type, "IDAT", 4)) { + uint8_t idat[chunk.size]; + readExpect(idat, sizeof(idat), "image data"); + readCrc(); + + stream.next_in = idat; + stream.avail_in = sizeof(idat); + int error = inflate(&stream, Z_SYNC_FLUSH); + if (error == Z_STREAM_END) break; + if (error != Z_OK) errx(EX_DATAERR, "%s: inflate: %s", path, stream.msg); + + } else if (0 == memcmp(chunk.type, "IEND", 4)) { + errx(EX_DATAERR, "%s: missing IDAT chunk", path); + } else { + skipChunk(chunk); + } + } + + inflateEnd(&stream); + if (stream.total_out != dataSize()) { + errx( + EX_DATAERR, "%s: expected data size %zu, found %lu", + path, dataSize(), stream.total_out + ); + } +} + +static void writeData(void) { + uLong size = compressBound(dataSize()); + uint8_t deflate[size]; + int error = compress2(deflate, &size, data, dataSize(), Z_BEST_SPEED); + if (error != Z_OK) errx(EX_SOFTWARE, "%s: compress2: %d", path, error); + + struct Chunk idat = { .size = size, .type = "IDAT" }; + writeChunk(idat); + writeExpect(deflate, size); + writeCrc(); +} + +static void writeEnd(void) { + struct Chunk iend = { .size = 0, .type = "IEND" }; + writeChunk(iend); + writeCrc(); +} + +enum PACKED Filter { + NONE, + SUB, + UP, + AVERAGE, + PAETH, +}; +#define FILTER_COUNT (PAETH + 1) + +static struct { + bool brokenPaeth; + bool filt; + bool recon; + uint8_t declareFilter; + uint8_t applyFilter; + enum Filter declareFilters[255]; + enum Filter applyFilters[255]; +} options; + +struct Bytes { + uint8_t x; + uint8_t a; + uint8_t b; + uint8_t c; +}; + +static uint8_t paethPredictor(struct Bytes f) { + int32_t p = (int32_t)f.a + (int32_t)f.b - (int32_t)f.c; + int32_t pa = abs(p - (int32_t)f.a); + int32_t pb = abs(p - (int32_t)f.b); + int32_t pc = abs(p - (int32_t)f.c); + if (pa <= pb && pa <= pc) return f.a; + if (options.brokenPaeth) { + if (pb < pc) return f.b; + } else { + if (pb <= pc) return f.b; + } + return f.c; +} + +static uint8_t recon(enum Filter type, struct Bytes f) { + switch (type) { + case NONE: return f.x; + case SUB: return f.x + f.a; + case UP: return f.x + f.b; + case AVERAGE: return f.x + ((uint32_t)f.a + (uint32_t)f.b) / 2; + case PAETH: return f.x + paethPredictor(f); + default: abort(); + } +} + +static uint8_t filt(enum Filter type, struct Bytes f) { + switch (type) { + case NONE: return f.x; + case SUB: return f.x - f.a; + case UP: return f.x - f.b; + case AVERAGE: return f.x - ((uint32_t)f.a + (uint32_t)f.b) / 2; + case PAETH: return f.x - paethPredictor(f); + default: abort(); + } +} + +static struct Line { + enum Filter type; + uint8_t data[]; +} **lines; + +static void scanlines(void) { + lines = calloc(header.height, sizeof(*lines)); + if (!lines) err(EX_OSERR, "calloc(%u, %zu)", header.height, sizeof(*lines)); + + size_t stride = 1 + lineSize(); + for (uint32_t y = 0; y < header.height; ++y) { + lines[y] = (struct Line *)&data[y * stride]; + if (lines[y]->type >= FILTER_COUNT) { + errx(EX_DATAERR, "%s: invalid filter type %hhu", path, lines[y]->type); + } + } +} + +static struct Bytes origBytes(uint32_t y, size_t i) { + size_t pixelSize = lineSize() / header.width; + if (!pixelSize) pixelSize = 1; + bool a = (i >= pixelSize), b = (y > 0), c = (a && b); + return (struct Bytes) { + .x = lines[y]->data[i], + .a = a ? lines[y]->data[i - pixelSize] : 0, + .b = b ? lines[y - 1]->data[i] : 0, + .c = c ? lines[y - 1]->data[i - pixelSize] : 0, + }; +} + +static void reconData(void) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0; i < lineSize(); ++i) { + if (options.filt) { + lines[y]->data[i] = filt(lines[y]->type, origBytes(y, i)); + } else { + lines[y]->data[i] = recon(lines[y]->type, origBytes(y, i)); + } + } + lines[y]->type = NONE; + } +} + +static void filterData(void) { + for (uint32_t y = header.height - 1; y < header.height; --y) { + uint8_t filter[FILTER_COUNT][lineSize()]; + uint32_t heuristic[FILTER_COUNT] = {0}; + enum Filter minType = NONE; + for (enum Filter type = NONE; type < FILTER_COUNT; ++type) { + for (size_t i = 0; i < lineSize(); ++i) { + if (options.recon) { + filter[type][i] = recon(type, origBytes(y, i)); + } else { + filter[type][i] = filt(type, origBytes(y, i)); + } + heuristic[type] += abs((int8_t)filter[type][i]); + } + if (heuristic[type] < heuristic[minType]) minType = type; + } + + if (options.declareFilter) { + lines[y]->type = options.declareFilters[y % options.declareFilter]; + } else { + lines[y]->type = minType; + } + + if (options.applyFilter) { + enum Filter type = options.applyFilters[y % options.applyFilter]; + memcpy(lines[y]->data, filter[type], lineSize()); + } else { + memcpy(lines[y]->data, filter[minType], lineSize()); + } + } +} + +static void glitch(const char *inPath, const char *outPath) { + if (inPath) { + path = inPath; + file = fopen(path, "r"); + if (!file) err(EX_NOINPUT, "%s", path); + } else { + path = "(stdin)"; + file = stdin; + } + + readSignature(); + readHeader(); + if (header.color == INDEXED) readPalette(); + readData(); + fclose(file); + + scanlines(); + reconData(); + filterData(); + free(lines); + + if (outPath) { + path = outPath; + file = fopen(path, "w"); + if (!file) err(EX_CANTCREAT, "%s", path); + } else { + path = "(stdout)"; + file = stdout; + } + + writeSignature(); + writeHeader(); + if (header.color == INDEXED) writePalette(); + writeData(); + writeEnd(); + free(data); + + int error = fclose(file); + if (error) err(EX_IOERR, "%s", path); +} + +static enum Filter parseFilter(const char *s) { + switch (s[0]) { + case 'N': case 'n': return NONE; + case 'S': case 's': return SUB; + case 'U': case 'u': return UP; + case 'A': case 'a': return AVERAGE; + case 'P': case 'p': return PAETH; + default: errx(EX_USAGE, "invalid filter type %s", s); + } +} + +static uint8_t parseFilters(enum Filter *filters, const char *s) { + uint8_t len = 0; + do { + filters[len++] = parseFilter(s); + s = strchr(s, ','); + } while (s++); + return len; +} + +int main(int argc, char *argv[]) { + bool stdio = false; + char *output = NULL; + + int opt; + while (0 < (opt = getopt(argc, argv, "a:cd:fo:pr"))) { + switch (opt) { + case 'a': { + options.applyFilter = parseFilters(options.applyFilters, optarg); + } break; + case 'c': stdio = true; break; + case 'd': { + options.declareFilter = parseFilters(options.declareFilters, optarg); + } break; + case 'f': options.filt = true; break; + case 'o': output = optarg; break; + case 'p': options.brokenPaeth = true; break; + case 'r': options.recon = true; break; + default: return EX_USAGE; + } + } + + if (argc - optind == 1 && (output || stdio)) { + glitch(argv[optind], output); + } else if (optind < argc) { + for (int i = optind; i < argc; ++i) { + glitch(argv[i], argv[i]); + } + } else { + glitch(NULL, output); + } + + return EX_OK; +} diff --git a/bin/hnel.c b/bin/hnel.c new file mode 100644 index 00000000..709ea2fc --- /dev/null +++ b/bin/hnel.c @@ -0,0 +1,118 @@ +/* Copyright (c) 2017, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <poll.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <sysexits.h> +#include <termios.h> +#include <unistd.h> + +#if defined __FreeBSD__ +#include <libutil.h> +#elif defined __linux__ +#include <pty.h> +#else +#include <util.h> +#endif + +static struct termios saveTerm; +static void restoreTerm(void) { + tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm); +} + +int main(int argc, char *argv[]) { + int error; + + if (argc < 4) return EX_USAGE; + + char table[256] = {0}; + if (strlen(argv[1]) != strlen(argv[2])) return EX_USAGE; + for (const char *from = argv[1], *to = argv[2]; *from; ++from, ++to) { + table[(unsigned)*from] = *to; + } + + error = tcgetattr(STDERR_FILENO, &saveTerm); + if (error) err(EX_IOERR, "tcgetattr"); + atexit(restoreTerm); + + struct termios raw; + cfmakeraw(&raw); + error = tcsetattr(STDERR_FILENO, TCSADRAIN, &raw); + if (error) err(EX_IOERR, "tcsetattr"); + + struct winsize window; + error = ioctl(STDERR_FILENO, TIOCGWINSZ, &window); + if (error) err(EX_IOERR, "TIOCGWINSZ"); + + int pty; + pid_t pid = forkpty(&pty, NULL, NULL, &window); + if (pid < 0) err(EX_OSERR, "forkpty"); + + if (!pid) { + execvp(argv[3], &argv[3]); + err(EX_NOINPUT, "%s", argv[3]); + } + + bool enable = true; + + char buf[4096]; + struct pollfd fds[2] = { + { .fd = STDIN_FILENO, .events = POLLIN }, + { .fd = pty, .events = POLLIN }, + }; + while (0 < poll(fds, 2, -1)) { + if (fds[0].revents & POLLIN) { + ssize_t readSize = read(STDIN_FILENO, buf, sizeof(buf)); + if (readSize < 0) err(EX_IOERR, "read(%d)", STDIN_FILENO); + + if (readSize == 1) { + if (buf[0] == CTRL('S')) { + enable ^= true; + continue; + } + + unsigned char c = buf[0]; + if (enable && table[c]) buf[0] = table[c]; + } + + ssize_t writeSize = write(pty, buf, readSize); + if (writeSize < 0) err(EX_IOERR, "write(%d)", pty); + if (writeSize < readSize) errx(EX_IOERR, "short write(%d)", pty); + } + + if (fds[1].revents & POLLIN) { + ssize_t readSize = read(pty, buf, sizeof(buf)); + if (readSize < 0) err(EX_IOERR, "read(%d)", pty); + + ssize_t writeSize = write(STDOUT_FILENO, buf, readSize); + if (writeSize < 0) err(EX_IOERR, "write(%d)", STDOUT_FILENO); + if (writeSize < readSize) { + errx(EX_IOERR, "short write(%d)", STDOUT_FILENO); + } + } + + int status; + pid_t dead = waitpid(pid, &status, WNOHANG); + if (dead < 0) err(EX_OSERR, "waitpid(%d)", pid); + if (dead) return WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE; + } + err(EX_IOERR, "poll"); +} diff --git a/bin/klon.c b/bin/klon.c new file mode 100644 index 00000000..40469290 --- /dev/null +++ b/bin/klon.c @@ -0,0 +1,357 @@ +/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <curses.h> +#include <locale.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +typedef uint8_t Card; + +#define MASK_RANK (0x0F) +#define MASK_SUIT (0x30) +#define MASK_COLOR (0x10) +#define MASK_UP (0x40) +#define MASK_SELECT (0x80) + +enum { + SUIT_CLUB = 0x00, + SUIT_DIAMOND = 0x10, + SUIT_SPADE = 0x20, + SUIT_HEART = 0x30, +}; + +struct Stack { + Card data[52]; + uint8_t index; +}; +#define EMPTY { .data = {0}, .index = 52 } + +static void push(struct Stack *stack, Card card) { + assert(stack->index > 0); + stack->data[--stack->index] = card; +} + +static Card pop(struct Stack *stack) { + assert(stack->index < 52); + return stack->data[stack->index++]; +} + +static Card get(const struct Stack *stack, uint8_t i) { + if (stack->index + i > 51) return 0; + return stack->data[stack->index + i]; +} + +static uint8_t len(const struct Stack *stack) { + return 52 - stack->index; +} + +struct State { + struct Stack stock; + struct Stack waste; + struct Stack found[4]; + struct Stack table[7]; +}; + +static struct State g = { + .stock = { + .data = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, + }, + .index = 0, + }, + .waste = EMPTY, + .found = { EMPTY, EMPTY, EMPTY, EMPTY }, + .table = { + EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY + }, +}; + +static struct State save; + +static void checkpoint(void) { + memcpy(&save, &g, sizeof(struct State)); +} + +static void undo(void) { + memcpy(&g, &save, sizeof(struct State)); +} + +static void shuffle(void) { + for (int i = 51; i > 0; --i) { + int j = arc4random_uniform(i + 1); + uint8_t x = g.stock.data[i]; + g.stock.data[i] = g.stock.data[j]; + g.stock.data[j] = x; + } +} + +static void deal(void) { + for (int i = 0; i < 7; ++i) { + for (int j = i; j < 7; ++j) { + push(&g.table[j], pop(&g.stock)); + } + } +} + +static void reveal(void) { + for (int i = 0; i < 7; ++i) { + if (get(&g.table[i], 0)) { + push(&g.table[i], pop(&g.table[i]) | MASK_UP); + } + } +} + +static void draw(void) { + if (get(&g.stock, 0)) push(&g.waste, pop(&g.stock) | MASK_UP); + if (get(&g.stock, 0)) push(&g.waste, pop(&g.stock) | MASK_UP); + if (get(&g.stock, 0)) push(&g.waste, pop(&g.stock) | MASK_UP); +} + +static void wasted(void) { + uint8_t n = len(&g.waste); + for (int i = 0; i < n; ++i) { + push(&g.stock, pop(&g.waste) & ~MASK_UP); + } +} + +static void transfer(struct Stack *dest, struct Stack *src, uint8_t n) { + struct Stack temp = EMPTY; + for (int i = 0; i < n; ++i) { + push(&temp, pop(src)); + } + for (int i = 0; i < n; ++i) { + push(dest, pop(&temp)); + } +} + +static bool canFound(const struct Stack *found, Card card) { + if (!get(found, 0)) return (card & MASK_RANK) == 1; + if ((card & MASK_SUIT) != (get(found, 0) & MASK_SUIT)) return false; + return (card & MASK_RANK) == (get(found, 0) & MASK_RANK) + 1; +} + +static bool canTable(const struct Stack *table, Card card) { + if (!get(table, 0)) return (card & MASK_RANK) == 13; + if ((card & MASK_COLOR) == (get(table, 0) & MASK_COLOR)) return false; + return (card & MASK_RANK) == (get(table, 0) & MASK_RANK) - 1; +} + +enum { + PAIR_EMPTY = 1, + PAIR_BACK, + PAIR_BLACK, + PAIR_RED, +}; + +static void curse(void) { + setlocale(LC_CTYPE, ""); + + initscr(); + cbreak(); + noecho(); + keypad(stdscr, true); + set_escdelay(100); + curs_set(0); + + start_color(); + assume_default_colors(-1, -1); + init_pair(PAIR_EMPTY, COLOR_WHITE, COLOR_BLACK); + init_pair(PAIR_BACK, COLOR_WHITE, COLOR_BLUE); + init_pair(PAIR_BLACK, COLOR_BLACK, COLOR_WHITE); + init_pair(PAIR_RED, COLOR_RED, COLOR_WHITE); +} + +static const char rank[] = "\0A23456789TJQK"; +static const char *suit[] = { + [SUIT_HEART] = "♥", + [SUIT_CLUB] = "♣", + [SUIT_DIAMOND] = "♦", + [SUIT_SPADE] = "♠", +}; + +static void renderCard(int y, int x, Card card) { + if (card & MASK_UP) { + bkgdset( + COLOR_PAIR(card & MASK_COLOR ? PAIR_RED : PAIR_BLACK) + | (card & MASK_SELECT ? A_REVERSE : A_NORMAL) + ); + + move(y, x); + addch(rank[card & MASK_RANK]); + addstr(suit[card & MASK_SUIT]); + addch(' '); + + move(y + 1, x); + addstr(suit[card & MASK_SUIT]); + addch(' '); + addstr(suit[card & MASK_SUIT]); + + move(y + 2, x); + addch(' '); + addstr(suit[card & MASK_SUIT]); + addch(rank[card & MASK_RANK]); + + } else { + bkgdset(COLOR_PAIR(card ? PAIR_BACK : PAIR_EMPTY)); + mvaddstr(y, x, " "); + mvaddstr(y + 1, x, " "); + mvaddstr(y + 2, x, " "); + } +} + +static void render(void) { + bkgdset(COLOR_PAIR(0)); + erase(); + + int x = 2; + int y = 1; + + renderCard(y, x, get(&g.stock, 0)); + + x += 5; + renderCard(y, x++, get(&g.waste, 2)); + renderCard(y, x++, get(&g.waste, 1)); + renderCard(y, x, get(&g.waste, 0)); + + x += 5; + for (int i = 0; i < 4; ++i) { + renderCard(y, x, get(&g.found[i], 0)); + x += 4; + } + + x = 2; + for (int i = 0; i < 7; ++i) { + y = 5; + renderCard(y, x, 0); + for (int j = len(&g.table[i]); j > 0; --j) { + renderCard(y, x, get(&g.table[i], j - 1)); + y++; + } + x += 4; + } +} + +static struct { + struct Stack *stack; + uint8_t depth; +} input; + +static void deepen(void) { + assert(input.stack); + if (input.depth == len(input.stack)) return; + if (!(get(input.stack, input.depth) & MASK_UP)) return; + input.stack->data[input.stack->index + input.depth] |= MASK_SELECT; + input.depth++; +} + +static void select(struct Stack *stack) { + if (!get(stack, 0)) return; + input.stack = stack; + input.depth = 0; + deepen(); +} + +static void commit(struct Stack *dest) { + assert(input.stack); + for (int i = 0; i < input.depth; ++i) { + input.stack->data[input.stack->index + i] &= ~MASK_SELECT; + } + if (dest) { + checkpoint(); + transfer(dest, input.stack, input.depth); + } + input.stack = NULL; + input.depth = 0; +} + +int main() { + curse(); + + shuffle(); + deal(); + checkpoint(); + + for (;;) { + reveal(); + render(); + + int c = getch(); + if (!input.stack) { + if (c == 'q') { + break; + } else if (c == 'u') { + undo(); + } else if (c == 's' || c == ' ') { + if (get(&g.stock, 0)) { + checkpoint(); + draw(); + } else { + wasted(); + } + } else if (c == 'w') { + select(&g.waste); + } else if (c >= 'a' && c <= 'd') { + select(&g.found[c - 'a']); + } else if (c >= '1' && c <= '7') { + select(&g.table[c - '1']); + } + + } else { + if (c >= '1' && c <= '7') { + struct Stack *table = &g.table[c - '1']; + if (input.stack == table) { + deepen(); + } else if (canTable(table, get(input.stack, input.depth - 1))) { + commit(table); + } else { + commit(NULL); + } + } else if (input.depth == 1 && c >= 'a' && c <= 'd') { + struct Stack *found = &g.found[c - 'a']; + if (canFound(found, get(input.stack, 0))) { + commit(found); + } else { + commit(NULL); + } + } else if (input.depth == 1 && (c == 'f' || c == '\n')) { + struct Stack *found; + for (int i = 0; i < 4; ++i) { + found = &g.found[i]; + if (canFound(found, get(input.stack, 0))) break; + found = NULL; + } + commit(found); + } else { + commit(NULL); + } + } + } + + endwin(); + return 0; +} diff --git a/bin/pbd.c b/bin/pbd.c new file mode 100644 index 00000000..80ab036f --- /dev/null +++ b/bin/pbd.c @@ -0,0 +1,136 @@ +/* Copyright (c) 2017, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <arpa/inet.h> +#include <err.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sysexits.h> +#include <unistd.h> + +#define UNUSED __attribute__((unused)) + +static void spawn(const char *cmd, int dest, int src) { + pid_t pid = fork(); + if (pid < 0) err(EX_OSERR, "fork"); + + if (pid) { + int status; + pid_t dead = waitpid(pid, &status, 0); + if (dead < 0) err(EX_OSERR, "waitpid(%d)", pid); + if (status) warnx("%s: status %d", cmd, status); + + } else { + int fd = dup2(src, dest); + if (fd < 0) err(EX_OSERR, "dup2"); + + execlp(cmd, cmd, NULL); + err(EX_UNAVAILABLE, "%s", cmd); + } +} + +static int pbd(void) { + int error; + + int server = socket(PF_INET, SOCK_STREAM, 0); + if (server < 0) err(EX_OSERR, "socket"); + + error = fcntl(server, F_SETFD, FD_CLOEXEC); + if (error) err(EX_IOERR, "fcntl"); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(7062), + .sin_addr = { .s_addr = htonl(0x7f000001) }, + }; + error = bind(server, (struct sockaddr *)&addr, sizeof(addr)); + if (error) err(EX_UNAVAILABLE, "bind"); + + error = listen(server, 0); + if (error) err(EX_UNAVAILABLE, "listen"); + + for (;;) { + int client = accept(server, NULL, NULL); + if (client < 0) err(EX_IOERR, "accept"); + + error = fcntl(client, F_SETFD, FD_CLOEXEC); + if (error) err(EX_IOERR, "fcntl"); + + spawn("pbpaste", STDOUT_FILENO, client); + + char p; + ssize_t peek = recv(client, &p, 1, MSG_PEEK); + if (peek < 0) err(EX_IOERR, "recv"); + + if (peek) spawn("pbcopy", STDIN_FILENO, client); + + close(client); + } +} + +static int pbdClient(void) { + int client = socket(PF_INET, SOCK_STREAM, 0); + if (client < 0) err(EX_OSERR, "socket"); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(7062), + .sin_addr = { .s_addr = htonl(0x7f000001) }, + }; + int error = connect(client, (struct sockaddr *)&addr, sizeof(addr)); + if (error) err(EX_UNAVAILABLE, "connect"); + + return client; +} + +static void copy(int out, int in) { + char buf[4096]; + ssize_t readSize; + while (0 < (readSize = read(in, buf, sizeof(buf)))) { + ssize_t writeSize = write(out, buf, readSize); + if (writeSize < 0) err(EX_IOERR, "write(%d)", out); + if (writeSize < readSize) errx(EX_IOERR, "short write(%d)", out); + } + if (readSize < 0) err(EX_IOERR, "read(%d)", in); +} + +static int pbcopy(void) { + int client = pbdClient(); + copy(client, STDIN_FILENO); + return EX_OK; +} + +static int pbpaste(void) { + int client = pbdClient(); + shutdown(client, SHUT_WR); + copy(STDOUT_FILENO, client); + return EX_OK; +} + +int main(int argc UNUSED, char *argv[]) { + if (!argv[0][0] || !argv[0][1]) return EX_USAGE; + switch (argv[0][2]) { + case 'd': return pbd(); + case 'c': return pbcopy(); + case 'p': return pbpaste(); + default: return EX_USAGE; + } +} diff --git a/bin/pngo.c b/bin/pngo.c new file mode 100644 index 00000000..c34ec7d1 --- /dev/null +++ b/bin/pngo.c @@ -0,0 +1,717 @@ +/* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <arpa/inet.h> +#include <assert.h> +#include <err.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> +#include <zlib.h> + +#define PACKED __attribute__((packed)) +#define PAIR(a, b) ((uint16_t)(a) << 8 | (uint16_t)(b)) + +#define CRC_INIT (crc32(0, Z_NULL, 0)) + +static bool verbose; +static const char *path; +static FILE *file; +static uint32_t crc; + +static void readExpect(void *ptr, size_t size, const char *expect) { + fread(ptr, size, 1, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + if (feof(file)) errx(EX_DATAERR, "%s: missing %s", path, expect); + crc = crc32(crc, ptr, size); +} + +static void writeExpect(const void *ptr, size_t size) { + fwrite(ptr, size, 1, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + crc = crc32(crc, ptr, size); +} + +static const uint8_t SIGNATURE[8] = { + 0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n' +}; + +static void readSignature(void) { + uint8_t signature[8]; + readExpect(signature, 8, "signature"); + if (0 != memcmp(signature, SIGNATURE, 8)) { + errx(EX_DATAERR, "%s: invalid signature", path); + } +} + +static void writeSignature(void) { + writeExpect(SIGNATURE, sizeof(SIGNATURE)); +} + +struct PACKED Chunk { + uint32_t size; + char type[4]; +}; + +static const char *typeStr(struct Chunk chunk) { + static char buf[5]; + memcpy(buf, chunk.type, 4); + return buf; +} + +static struct Chunk readChunk(void) { + struct Chunk chunk; + readExpect(&chunk, sizeof(chunk), "chunk"); + chunk.size = ntohl(chunk.size); + crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type)); + return chunk; +} + +static void writeChunk(struct Chunk chunk) { + chunk.size = htonl(chunk.size); + writeExpect(&chunk, sizeof(chunk)); + crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type)); +} + +static void readCrc(void) { + uint32_t expected = crc; + uint32_t found; + readExpect(&found, sizeof(found), "CRC32"); + found = ntohl(found); + if (found != expected) { + errx( + EX_DATAERR, "%s: expected CRC32 %08X, found %08X", + path, expected, found + ); + } +} + +static void writeCrc(void) { + uint32_t net = htonl(crc); + writeExpect(&net, sizeof(net)); +} + +static void skipChunk(struct Chunk chunk) { + if (!(chunk.type[0] & 0x20)) { + errx(EX_CONFIG, "%s: unsupported critical chunk %s", path, typeStr(chunk)); + } + uint8_t discard[chunk.size]; + readExpect(discard, sizeof(discard), "chunk data"); + readCrc(); +} + +static struct PACKED { + uint32_t width; + uint32_t height; + uint8_t depth; + enum PACKED { + GRAYSCALE = 0, + TRUECOLOR = 2, + INDEXED = 3, + GRAYSCALE_ALPHA = 4, + TRUECOLOR_ALPHA = 6, + } color; + enum PACKED { DEFLATE } compression; + enum PACKED { ADAPTIVE } filter; + enum PACKED { PROGRESSIVE, ADAM7 } interlace; +} header; + +static size_t lineSize(void) { + switch (header.color) { + case GRAYSCALE: return (header.width * 1 * header.depth + 7) / 8; + case TRUECOLOR: return (header.width * 3 * header.depth + 7) / 8; + case INDEXED: return (header.width * 1 * header.depth + 7) / 8; + case GRAYSCALE_ALPHA: return (header.width * 2 * header.depth + 7) / 8; + case TRUECOLOR_ALPHA: return (header.width * 4 * header.depth + 7) / 8; + default: abort(); + } +} + +static size_t dataSize(void) { + return (1 + lineSize()) * header.height; +} + +static const char *COLOR_STR[] = { + [GRAYSCALE] = "grayscale", + [TRUECOLOR] = "truecolor", + [INDEXED] = "indexed", + [GRAYSCALE_ALPHA] = "grayscale alpha", + [TRUECOLOR_ALPHA] = "truecolor alpha", +}; +static void printHeader(void) { + fprintf( + stderr, + "%s: %ux%u %hhu-bit %s\n", + path, + header.width, header.height, + header.depth, COLOR_STR[header.color] + ); +} + +static void readHeader(void) { + struct Chunk ihdr = readChunk(); + if (0 != memcmp(ihdr.type, "IHDR", 4)) { + errx(EX_DATAERR, "%s: expected IHDR, found %s", path, typeStr(ihdr)); + } + if (ihdr.size != sizeof(header)) { + errx( + EX_DATAERR, "%s: expected IHDR size %zu, found %u", + path, sizeof(header), ihdr.size + ); + } + readExpect(&header, sizeof(header), "header"); + readCrc(); + + header.width = ntohl(header.width); + header.height = ntohl(header.height); + + if (!header.width) errx(EX_DATAERR, "%s: invalid width 0", path); + if (!header.height) errx(EX_DATAERR, "%s: invalid height 0", path); + switch (PAIR(header.color, header.depth)) { + case PAIR(GRAYSCALE, 1): + case PAIR(GRAYSCALE, 2): + case PAIR(GRAYSCALE, 4): + case PAIR(GRAYSCALE, 8): + case PAIR(GRAYSCALE, 16): + case PAIR(TRUECOLOR, 8): + case PAIR(TRUECOLOR, 16): + case PAIR(INDEXED, 1): + case PAIR(INDEXED, 2): + case PAIR(INDEXED, 4): + case PAIR(INDEXED, 8): + case PAIR(GRAYSCALE_ALPHA, 8): + case PAIR(GRAYSCALE_ALPHA, 16): + case PAIR(TRUECOLOR_ALPHA, 8): + case PAIR(TRUECOLOR_ALPHA, 16): + break; + default: + errx( + EX_DATAERR, "%s: invalid color type %hhu and bit depth %hhu", + path, header.color, header.depth + ); + } + if (header.compression != DEFLATE) { + errx( + EX_DATAERR, "%s: invalid compression method %hhu", + path, header.compression + ); + } + if (header.filter != ADAPTIVE) { + errx(EX_DATAERR, "%s: invalid filter method %hhu", path, header.filter); + } + if (header.interlace > ADAM7) { + errx(EX_DATAERR, "%s: invalid interlace method %hhu", path, header.interlace); + } + + if (verbose) printHeader(); +} + +static void writeHeader(void) { + if (verbose) printHeader(); + + struct Chunk ihdr = { .size = sizeof(header), .type = "IHDR" }; + writeChunk(ihdr); + header.width = htonl(header.width); + header.height = htonl(header.height); + writeExpect(&header, sizeof(header)); + writeCrc(); + + header.width = ntohl(header.width); + header.height = ntohl(header.height); +} + +static struct { + uint32_t len; + uint8_t entries[256][3]; +} palette; + +static uint16_t paletteIndex(const uint8_t *rgb) { + uint16_t i; + for (i = 0; i < palette.len; ++i) { + if (0 == memcmp(palette.entries[i], rgb, 3)) break; + } + return i; +} + +static bool paletteAdd(const uint8_t *rgb) { + uint16_t i = paletteIndex(rgb); + if (i < palette.len) return true; + if (i == 256) return false; + memcpy(palette.entries[palette.len++], rgb, 3); + return true; +} + +static void readPalette(void) { + struct Chunk chunk; + for (;;) { + chunk = readChunk(); + if (0 == memcmp(chunk.type, "PLTE", 4)) break; + skipChunk(chunk); + } + if (chunk.size % 3) { + errx(EX_DATAERR, "%s: PLTE size %u not divisible by 3", path, chunk.size); + } + + palette.len = chunk.size / 3; + readExpect(palette.entries, chunk.size, "palette data"); + readCrc(); + + if (verbose) fprintf(stderr, "%s: palette length %u\n", path, palette.len); +} + +static void writePalette(void) { + if (verbose) fprintf(stderr, "%s: palette length %u\n", path, palette.len); + struct Chunk plte = { .size = 3 * palette.len, .type = "PLTE" }; + writeChunk(plte); + writeExpect(palette.entries, plte.size); + writeCrc(); +} + +static uint8_t *data; + +static void allocData(void) { + data = malloc(dataSize()); + if (!data) err(EX_OSERR, "malloc(%zu)", dataSize()); +} + +static void readData(void) { + if (verbose) fprintf(stderr, "%s: data size %zu\n", path, dataSize()); + + struct z_stream_s stream = { .next_out = data, .avail_out = dataSize() }; + int error = inflateInit(&stream); + if (error != Z_OK) errx(EX_SOFTWARE, "%s: inflateInit: %s", path, stream.msg); + + for (;;) { + struct Chunk chunk = readChunk(); + if (0 == memcmp(chunk.type, "IDAT", 4)) { + uint8_t idat[chunk.size]; + readExpect(idat, sizeof(idat), "image data"); + readCrc(); + + stream.next_in = idat; + stream.avail_in = sizeof(idat); + int error = inflate(&stream, Z_SYNC_FLUSH); + if (error == Z_STREAM_END) break; + if (error != Z_OK) errx(EX_DATAERR, "%s: inflate: %s", path, stream.msg); + + } else if (0 == memcmp(chunk.type, "IEND", 4)) { + errx(EX_DATAERR, "%s: missing IDAT chunk", path); + } else { + skipChunk(chunk); + } + } + + inflateEnd(&stream); + if (stream.total_out != dataSize()) { + errx( + EX_DATAERR, "%s: expected data size %zu, found %lu", + path, dataSize(), stream.total_out + ); + } + + if (verbose) fprintf(stderr, "%s: deflate size %lu\n", path, stream.total_in); +} + +static void writeData(void) { + if (verbose) fprintf(stderr, "%s: data size %zu\n", path, dataSize()); + + uLong size = compressBound(dataSize()); + uint8_t deflate[size]; + int error = compress2(deflate, &size, data, dataSize(), Z_BEST_COMPRESSION); + if (error != Z_OK) errx(EX_SOFTWARE, "%s: compress2: %d", path, error); + + struct Chunk idat = { .size = size, .type = "IDAT" }; + writeChunk(idat); + writeExpect(deflate, size); + writeCrc(); + + if (verbose) fprintf(stderr, "%s: deflate size %lu\n", path, size); +} + +static void writeEnd(void) { + struct Chunk iend = { .size = 0, .type = "IEND" }; + writeChunk(iend); + writeCrc(); +} + +enum PACKED Filter { + NONE, + SUB, + UP, + AVERAGE, + PAETH, +}; +#define FILTER_COUNT (PAETH + 1) + +struct Bytes { + uint8_t x; + uint8_t a; + uint8_t b; + uint8_t c; +}; + +static uint8_t paethPredictor(struct Bytes f) { + int32_t p = (int32_t)f.a + (int32_t)f.b - (int32_t)f.c; + int32_t pa = abs(p - (int32_t)f.a); + int32_t pb = abs(p - (int32_t)f.b); + int32_t pc = abs(p - (int32_t)f.c); + if (pa <= pb && pa <= pc) return f.a; + if (pb <= pc) return f.b; + return f.c; +} + +static uint8_t recon(enum Filter type, struct Bytes f) { + switch (type) { + case NONE: return f.x; + case SUB: return f.x + f.a; + case UP: return f.x + f.b; + case AVERAGE: return f.x + ((uint32_t)f.a + (uint32_t)f.b) / 2; + case PAETH: return f.x + paethPredictor(f); + default: abort(); + } +} + +static uint8_t filt(enum Filter type, struct Bytes f) { + switch (type) { + case NONE: return f.x; + case SUB: return f.x - f.a; + case UP: return f.x - f.b; + case AVERAGE: return f.x - ((uint32_t)f.a + (uint32_t)f.b) / 2; + case PAETH: return f.x - paethPredictor(f); + default: abort(); + } +} + +static struct Line { + enum Filter type; + uint8_t data[]; +} **lines; + +static void allocLines(void) { + lines = calloc(header.height, sizeof(*lines)); + if (!lines) err(EX_OSERR, "calloc(%u, %zu)", header.height, sizeof(*lines)); +} + +static void scanlines(void) { + size_t stride = 1 + lineSize(); + for (uint32_t y = 0; y < header.height; ++y) { + lines[y] = (struct Line *)&data[y * stride]; + if (lines[y]->type >= FILTER_COUNT) { + errx(EX_DATAERR, "%s: invalid filter type %hhu", path, lines[y]->type); + } + } +} + +static struct Bytes origBytes(uint32_t y, size_t i) { + size_t pixelSize = lineSize() / header.width; + if (!pixelSize) pixelSize = 1; + bool a = (i >= pixelSize), b = (y > 0), c = (a && b); + return (struct Bytes) { + .x = lines[y]->data[i], + .a = a ? lines[y]->data[i - pixelSize] : 0, + .b = b ? lines[y - 1]->data[i] : 0, + .c = c ? lines[y - 1]->data[i - pixelSize] : 0, + }; +} + +static void reconData(void) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0; i < lineSize(); ++i) { + lines[y]->data[i] = + recon(lines[y]->type, origBytes(y, i)); + } + lines[y]->type = NONE; + } +} + +static void filterData(void) { + if (header.color == INDEXED || header.depth < 8) return; + for (uint32_t y = header.height - 1; y < header.height; --y) { + uint8_t filter[FILTER_COUNT][lineSize()]; + uint32_t heuristic[FILTER_COUNT] = {0}; + enum Filter minType = NONE; + for (enum Filter type = NONE; type < FILTER_COUNT; ++type) { + for (size_t i = 0; i < lineSize(); ++i) { + filter[type][i] = filt(type, origBytes(y, i)); + heuristic[type] += abs((int8_t)filter[type][i]); + } + if (heuristic[type] < heuristic[minType]) minType = type; + } + lines[y]->type = minType; + memcpy(lines[y]->data, filter[minType], lineSize()); + } +} + +static void discardAlpha(void) { + if (header.color != GRAYSCALE_ALPHA && header.color != TRUECOLOR_ALPHA) return; + size_t sampleSize = header.depth / 8; + size_t pixelSize = sampleSize * (header.color == GRAYSCALE_ALPHA ? 2 : 4); + size_t colorSize = pixelSize - sampleSize; + for (uint32_t y = 0; y < header.height; ++y) { + for (uint32_t x = 0; x < header.width; ++x) { + for (size_t i = 0; i < sampleSize; ++i) { + if (lines[y]->data[x * pixelSize + colorSize + i] != 0xFF) return; + } + } + } + + uint8_t *ptr = data; + for (uint32_t y = 0; y < header.height; ++y) { + *ptr++ = lines[y]->type; + for (uint32_t x = 0; x < header.width; ++x) { + memmove(ptr, &lines[y]->data[x * pixelSize], colorSize); + ptr += colorSize; + } + } + header.color = (header.color == GRAYSCALE_ALPHA) ? GRAYSCALE : TRUECOLOR; + scanlines(); +} + +static void discardColor(void) { + if (header.color != TRUECOLOR && header.color != TRUECOLOR_ALPHA) return; + size_t sampleSize = header.depth / 8; + size_t pixelSize = sampleSize * (header.color == TRUECOLOR ? 3 : 4); + for (uint32_t y = 0; y < header.height; ++y) { + for (uint32_t x = 0; x < header.width; ++x) { + uint8_t *r = &lines[y]->data[x * pixelSize]; + uint8_t *g = r + sampleSize; + uint8_t *b = g + sampleSize; + if (0 != memcmp(r, g, sampleSize)) return; + if (0 != memcmp(g, b, sampleSize)) return; + } + } + + uint8_t *ptr = data; + for (uint32_t y = 0; y < header.height; ++y) { + *ptr++ = lines[y]->type; + for (uint32_t x = 0; x < header.width; ++x) { + uint8_t *pixel = &lines[y]->data[x * pixelSize]; + memmove(ptr, pixel, sampleSize); + ptr += sampleSize; + if (header.color == TRUECOLOR_ALPHA) { + memmove(ptr, pixel + 3 * sampleSize, sampleSize); + ptr += sampleSize; + } + } + } + header.color = (header.color == TRUECOLOR) ? GRAYSCALE : GRAYSCALE_ALPHA; + scanlines(); +} + +static void indexColor(void) { + if (header.color != TRUECOLOR || header.depth != 8) return; + for (uint32_t y = 0; y < header.height; ++y) { + for (uint32_t x = 0; x < header.width; ++x) { + if (!paletteAdd(&lines[y]->data[x * 3])) return; + } + } + + uint8_t *ptr = data; + for (uint32_t y = 0; y < header.height; ++y) { + *ptr++ = lines[y]->type; + for (uint32_t x = 0; x < header.width; ++x) { + *ptr++ = paletteIndex(&lines[y]->data[x * 3]); + } + } + header.color = INDEXED; + scanlines(); +} + +static void reduceDepth8(void) { + if (header.color != GRAYSCALE && header.color != INDEXED) return; + if (header.depth != 8) return; + if (header.color == GRAYSCALE) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0; i < lineSize(); ++i) { + uint8_t a = lines[y]->data[i]; + if ((a >> 4) != (a & 0x0F)) return; + } + } + } else if (palette.len > 16) { + return; + } + + uint8_t *ptr = data; + for (uint32_t y = 0; y < header.height; ++y) { + *ptr++ = lines[y]->type; + for (size_t i = 0; i < lineSize(); i += 2) { + uint8_t iByte = lines[y]->data[i]; + uint8_t jByte = (i + 1 < lineSize()) ? lines[y]->data[i + 1] : 0; + uint8_t a = iByte & 0x0F; + uint8_t b = jByte & 0x0F; + *ptr++ = a << 4 | b; + } + } + header.depth = 4; + scanlines(); +} + +static void reduceDepth4(void) { + if (header.depth != 4) return; + if (header.color == GRAYSCALE) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0; i < lineSize(); ++i) { + uint8_t a = lines[y]->data[i] >> 4; + uint8_t b = lines[y]->data[i] & 0x0F; + if ((a >> 2) != (a & 0x03)) return; + if ((b >> 2) != (b & 0x03)) return; + } + } + } else if (palette.len > 4) { + return; + } + + uint8_t *ptr = data; + for (uint32_t y = 0; y < header.height; ++y) { + *ptr++ = lines[y]->type; + for (size_t i = 0; i < lineSize(); i += 2) { + uint8_t iByte = lines[y]->data[i]; + uint8_t jByte = (i + 1 < lineSize()) ? lines[y]->data[i + 1] : 0; + uint8_t a = iByte >> 4 & 0x03, b = iByte & 0x03; + uint8_t c = jByte >> 4 & 0x03, d = jByte & 0x03; + *ptr++ = a << 6 | b << 4 | c << 2 | d; + } + } + header.depth = 2; + scanlines(); +} + +static void reduceDepth2(void) { + if (header.depth != 2) return; + if (header.color == GRAYSCALE) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0; i < lineSize(); ++i) { + uint8_t a = lines[y]->data[i] >> 6; + uint8_t b = lines[y]->data[i] >> 4 & 0x03; + uint8_t c = lines[y]->data[i] >> 2 & 0x03; + uint8_t d = lines[y]->data[i] & 0x03; + if ((a >> 1) != (a & 0x01)) return; + if ((b >> 1) != (b & 0x01)) return; + if ((c >> 1) != (c & 0x01)) return; + if ((d >> 1) != (d & 0x01)) return; + } + } + } else if (palette.len > 2) { + return; + } + + uint8_t *ptr = data; + for (uint32_t y = 0; y < header.height; ++y) { + *ptr++ = lines[y]->type; + for (size_t i = 0; i < lineSize(); i += 2) { + uint8_t iByte = lines[y]->data[i]; + uint8_t jByte = (i + 1 < lineSize()) ? lines[y]->data[i + 1] : 0; + uint8_t a = iByte >> 6 & 0x01, b = iByte >> 4 & 0x01; + uint8_t c = iByte >> 2 & 0x01, d = iByte & 0x01; + uint8_t e = jByte >> 6 & 0x01, f = jByte >> 4 & 0x01; + uint8_t g = jByte >> 2 & 0x01, h = jByte & 0x01; + *ptr++ = a << 7 | b << 6 | c << 5 | d << 4 | e << 3 | f << 2 | g << 1 | h; + } + } + header.depth = 1; + scanlines(); +} + +static void reduceDepth(void) { + reduceDepth8(); + reduceDepth4(); + reduceDepth2(); +} + +static void optimize(const char *inPath, const char *outPath) { + if (inPath) { + path = inPath; + file = fopen(path, "r"); + if (!file) err(EX_NOINPUT, "%s", path); + } else { + path = "(stdin)"; + file = stdin; + } + + readSignature(); + readHeader(); + if (header.interlace != PROGRESSIVE) { + errx( + EX_CONFIG, "%s: unsupported interlace method %hhu", + path, header.interlace + ); + } + if (header.color == INDEXED) readPalette(); + allocData(); + readData(); + fclose(file); + + allocLines(); + scanlines(); + reconData(); + + discardAlpha(); + discardColor(); + indexColor(); + reduceDepth(); + filterData(); + free(lines); + + if (outPath) { + path = outPath; + file = fopen(path, "w"); + if (!file) err(EX_CANTCREAT, "%s", path); + } else { + path = "(stdout)"; + file = stdout; + } + + writeSignature(); + writeHeader(); + if (header.color == INDEXED) writePalette(); + writeData(); + writeEnd(); + free(data); + + int error = fclose(file); + if (error) err(EX_IOERR, "%s", path); +} + +int main(int argc, char *argv[]) { + bool stdio = false; + char *output = NULL; + + int opt; + while (0 < (opt = getopt(argc, argv, "co:v"))) { + switch (opt) { + case 'c': stdio = true; break; + case 'o': output = optarg; break; + case 'v': verbose = true; break; + default: return EX_USAGE; + } + } + + if (argc - optind == 1 && (output || stdio)) { + optimize(argv[optind], output); + } else if (optind < argc) { + for (int i = optind; i < argc; ++i) { + optimize(argv[i], argv[i]); + } + } else { + optimize(NULL, output); + } + + return EX_OK; +} diff --git a/bin/scheme.c b/bin/scheme.c new file mode 100644 index 00000000..162192fc --- /dev/null +++ b/bin/scheme.c @@ -0,0 +1,219 @@ +/* Copyright (C) 2018 Curtis McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <arpa/inet.h> +#include <err.h> +#include <math.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> +#include <zlib.h> + +static const struct Hsv { double h, s, v; } + R = { 0.0, 1.0, 1.0 }, + Y = { 60.0, 1.0, 1.0 }, + G = { 120.0, 1.0, 1.0 }, + C = { 180.0, 1.0, 1.0 }, + B = { 240.0, 1.0, 1.0 }, + M = { 300.0, 1.0, 1.0 }; + +static struct Hsv x(struct Hsv o, double hd, double sf, double vf) { + return (struct Hsv) { + fmod(o.h + hd, 360.0), + fmin(o.s * sf, 1.0), + fmin(o.v * vf, 1.0), + }; +} + +struct Ansi { + enum { BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE }; + struct Hsv dark[8]; + struct Hsv light[8]; +}; + +struct Terminal { + struct Ansi ansi; + struct Hsv background, text, bold, selection, cursor; +}; + +#define HSV_LEN(x) (sizeof(x) / sizeof(struct Hsv)) +struct Scheme { + size_t len; + union { + struct Hsv hsv; + struct Ansi ansi; + struct Terminal terminal; + }; +}; + +static struct Scheme ansi(void) { + struct Ansi a = { + .light = { + [BLACK] = x(R, +45.0, 0.3, 0.3), + [RED] = x(R, +10.0, 0.9, 0.8), + [GREEN] = x(G, -55.0, 0.8, 0.6), + [YELLOW] = x(Y, -20.0, 0.8, 0.8), + [BLUE] = x(B, -55.0, 0.4, 0.5), + [MAGENTA] = x(M, +45.0, 0.4, 0.6), + [CYAN] = x(C, -60.0, 0.3, 0.6), + [WHITE] = x(R, +45.0, 0.3, 0.8), + }, + }; + a.dark[BLACK] = x(a.light[BLACK], 0.0, 1.0, 0.3); + a.dark[WHITE] = x(a.light[WHITE], 0.0, 1.0, 0.6); + for (int i = RED; i < WHITE; ++i) { + a.dark[i] = x(a.light[i], 0.0, 1.0, 0.8); + } + return (struct Scheme) { .len = HSV_LEN(a), .ansi = a }; +} + +static struct Scheme terminal(void) { + struct Ansi a = ansi().ansi; + struct Terminal t = { + .ansi = a, + .background = x(a.dark[BLACK], 0.0, 1.0, 0.9), + .text = x(a.light[WHITE], 0.0, 1.0, 0.9), + .bold = x(a.light[WHITE], 0.0, 1.0, 1.0), + .selection = x(a.light[RED], +10.0, 1.0, 0.8), + .cursor = x(a.dark[WHITE], 0.0, 1.0, 0.8), + }; + return (struct Scheme) { .len = HSV_LEN(t), .terminal = t }; +} + +static void hsv(const struct Hsv *hsv, size_t len) { + for (size_t i = 0; i < len; ++i) { + printf("%g,%g,%g\n", hsv[i].h, hsv[i].s, hsv[i].v); + } +} + +struct Rgb { uint8_t r, g, b; }; +static struct Rgb toRgb(struct Hsv hsv) { + double c = hsv.v * hsv.s; + double h = hsv.h / 60.0; + double x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0)); + double m = hsv.v - c; + double r = m, g = m, b = m; + if (h <= 1.0) { r += c; g += x; } + else if (h <= 2.0) { r += x; g += c; } + else if (h <= 3.0) { g += c; b += x; } + else if (h <= 4.0) { g += x; b += c; } + else if (h <= 5.0) { r += x; b += c; } + else if (h <= 6.0) { r += c; b += x; } + return (struct Rgb) { r * 255.0, g * 255.0, b * 255.0 }; +} + +static void hex(const struct Hsv *hsv, size_t len) { + for (size_t i = 0; i < len; ++i) { + struct Rgb rgb = toRgb(hsv[i]); + printf("%02x%02x%02x\n", rgb.r, rgb.g, rgb.b); + } +} + +static void linux(const struct Hsv *hsv, size_t len) { + if (len > 16) len = 16; + for (size_t i = 0; i < len; ++i) { + struct Rgb rgb = toRgb(hsv[i]); + printf("\x1B]P%zx%02x%02x%02x", i, rgb.r, rgb.g, rgb.b); + } +} + +static uint32_t crc; +static void pngWrite(const void *ptr, size_t size) { + fwrite(ptr, size, 1, stdout); + if (ferror(stdout)) err(EX_IOERR, "(stdout)"); + crc = crc32(crc, ptr, size); +} +static void pngInt(uint32_t host) { + uint32_t net = htonl(host); + pngWrite(&net, 4); +} +static void pngChunk(const char *type, uint32_t size) { + pngInt(size); + crc = crc32(0, Z_NULL, 0); + pngWrite(type, 4); +} + +static void png(const struct Hsv *hsv, size_t len) { + if (len > 256) len = 256; + uint32_t swatchWidth = 64; + uint32_t swatchHeight = 64; + uint32_t columns = 8; + uint32_t rows = (len + columns - 1) / columns; + uint32_t width = swatchWidth * columns; + uint32_t height = swatchHeight * rows; + + pngWrite("\x89PNG\r\n\x1A\n", 8); + + pngChunk("IHDR", 13); + pngInt(width); + pngInt(height); + pngWrite("\x08\x03\0\0\0", 5); + pngInt(crc); + + pngChunk("PLTE", 3 * len); + for (size_t i = 0; i < len; ++i) { + struct Rgb rgb = toRgb(hsv[i]); + pngWrite(&rgb, 3); + } + pngInt(crc); + + uint8_t data[height][1 + width]; + memset(data, 0, sizeof(data)); + for (uint32_t y = 0; y < height; ++y) { + enum { NONE, SUB, UP, AVERAGE, PAETH }; + data[y][0] = (y % swatchHeight) ? UP : SUB; + } + for (size_t i = 0; i < len; ++i) { + uint32_t y = swatchHeight * (i / columns); + uint32_t x = swatchWidth * (i % columns); + data[y][1 + x] = x ? 1 : i; + } + + uLong size = compressBound(sizeof(data)); + uint8_t deflate[size]; + int error = compress(deflate, &size, (Byte *)data, sizeof(data)); + if (error != Z_OK) errx(EX_SOFTWARE, "compress: %d", error); + + pngChunk("IDAT", size); + pngWrite(deflate, size); + pngInt(crc); + + pngChunk("IEND", 0); + pngInt(crc); +} + +int main(int argc, char *argv[]) { + struct Scheme (*gen)(void) = ansi; + void (*out)(const struct Hsv *, size_t len) = hex; + int opt; + while (0 < (opt = getopt(argc, argv, "aghltx"))) { + switch (opt) { + case 'a': gen = ansi; break; + case 'g': out = png; break; + case 'h': out = hsv; break; + case 'l': out = linux; break; + case 't': gen = terminal; break; + case 'x': out = hex; break; + default: return EX_USAGE; + } + } + struct Scheme scheme = gen(); + out(&scheme.hsv, scheme.len); + return EX_OK; +} diff --git a/bin/wake.c b/bin/wake.c new file mode 100644 index 00000000..c81c5ede --- /dev/null +++ b/bin/wake.c @@ -0,0 +1,53 @@ +/* Copyright (c) 2017, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sys/types.h> + +#include <err.h> +#include <netinet/in.h> +#include <stdint.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sysexits.h> + +#define MAC 0x04, 0x7D, 0x7B, 0xD5, 0x6A, 0x53 +static const uint8_t payload[102] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + MAC, MAC, MAC, MAC, MAC, MAC, MAC, MAC, + MAC, MAC, MAC, MAC, MAC, MAC, MAC, MAC, +}; + +int main() { + int sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock < 0) err(EX_OSERR, "socket"); + + int on = 1; + int error = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); + if (error) err(EX_OSERR, "setsockopt"); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = 9, + .sin_addr.s_addr = INADDR_BROADCAST, + }; + ssize_t size = sendto( + sock, payload, sizeof(payload), 0, + (struct sockaddr *)&addr, sizeof(addr) + ); + if (size < 0) err(EX_IOERR, "sendto"); + + return EX_OK; +} diff --git a/bin/watch.c b/bin/watch.c new file mode 100644 index 00000000..ed499652 --- /dev/null +++ b/bin/watch.c @@ -0,0 +1,93 @@ +/* Copyright (c) 2017, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sys/types.h> + +#include <err.h> +#include <fcntl.h> +#include <stdlib.h> +#include <sys/event.h> +#include <sys/wait.h> +#include <sysexits.h> +#include <unistd.h> + +static void watch(int kq, char *path) { + int fd = open(path, O_CLOEXEC); + if (fd < 0) err(EX_NOINPUT, "%s", path); + + struct kevent event = { + .ident = fd, + .filter = EVFILT_VNODE, + .flags = EV_ADD | EV_CLEAR, + .fflags = NOTE_WRITE | NOTE_DELETE, + .udata = path, + }; + int nevents = kevent(kq, &event, 1, NULL, 0, NULL); + if (nevents < 0) err(EX_OSERR, "kevent"); +} + +static void exec(char *const argv[]) { + pid_t pid = fork(); + if (pid < 0) err(EX_OSERR, "fork"); + + if (!pid) { + execvp(*argv, argv); + err(EX_NOINPUT, "%s", *argv); + } + + int status; + pid = wait(&status); + if (pid < 0) err(EX_OSERR, "wait"); + + if (WIFEXITED(status)) { + warnx("exit %d\n", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + warnx("signal %d\n", WTERMSIG(status)); + } else { + warnx("status %d\n", status); + } +} + +int main(int argc, char *argv[]) { + if (argc < 3) return EX_USAGE; + + int kq = kqueue(); + if (kq < 0) err(EX_OSERR, "kqueue"); + + int i; + for (i = 1; i < argc - 1; ++i) { + if (argv[i][0] == '-') { + i++; + break; + } + watch(kq, argv[i]); + } + + exec(&argv[i]); + + for (;;) { + struct kevent event; + int nevents = kevent(kq, NULL, 0, &event, 1, NULL); + if (nevents < 0) err(EX_OSERR, "kevent"); + + if (event.fflags & NOTE_DELETE) { + close(event.ident); + watch(kq, event.udata); + } + + exec(&argv[i]); + } +} diff --git a/bin/xx.c b/bin/xx.c new file mode 100644 index 00000000..688db8bc --- /dev/null +++ b/bin/xx.c @@ -0,0 +1,133 @@ +/* Copyright (c) 2017, Curtis McEnroe <programble@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <ctype.h> +#include <err.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sysexits.h> +#include <unistd.h> + +static bool zero(const uint8_t *ptr, size_t size) { + for (size_t i = 0; i < size; ++i) { + if (ptr[i]) return false; + } + return true; +} + +static struct { + size_t cols; + size_t group; + bool ascii; + bool offset; + bool skip; +} options = { 16, 8, true, true, false }; + +static void dump(FILE *file) { + bool skip = false; + + uint8_t buf[options.cols]; + size_t offset = 0; + for ( + size_t size; + (size = fread(buf, 1, sizeof(buf), file)); + offset += size + ) { + if (options.skip) { + if (zero(buf, size)) { + if (!skip) printf("*\n"); + skip = true; + continue; + } else { + skip = false; + } + } + + if (options.offset) { + printf("%08zx: ", offset); + } + + for (size_t i = 0; i < sizeof(buf); ++i) { + if (options.group) { + if (i && !(i % options.group)) { + printf(" "); + } + } + if (i < size) { + printf("%02hhx ", buf[i]); + } else { + printf(" "); + } + } + + if (options.ascii) { + printf(" "); + for (size_t i = 0; i < size; ++i) { + if (options.group) { + if (i && !(i % options.group)) { + printf(" "); + } + } + printf("%c", isprint(buf[i]) ? buf[i] : '.'); + } + } + + printf("\n"); + } +} + +static void undump(FILE *file) { + uint8_t byte; + int match; + while (0 < (match = fscanf(file, " %hhx", &byte))) { + printf("%c", byte); + } + if (!match) errx(EX_DATAERR, "invalid input"); +} + +int main(int argc, char *argv[]) { + bool reverse = false; + const char *path = NULL; + + int opt; + while (0 < (opt = getopt(argc, argv, "ac:g:rsz"))) { + switch (opt) { + case 'a': options.ascii ^= true; break; + case 'c': options.cols = strtoul(optarg, NULL, 0); break; + case 'g': options.group = strtoul(optarg, NULL, 0); break; + case 'r': reverse = true; break; + case 's': options.offset ^= true; break; + case 'z': options.skip ^= true; break; + default: return EX_USAGE; + } + } + if (argc > optind) path = argv[optind]; + if (!options.cols) return EX_USAGE; + + FILE *file = path ? fopen(path, "r") : stdin; + if (!file) err(EX_NOINPUT, "%s", path); + + if (reverse) { + undump(file); + } else { + dump(file); + } + if (ferror(file)) err(EX_IOERR, "%s", path); + + return EX_OK; +} |