diff options
-rw-r--r-- | .gitignore | 8 | ||||
-rw-r--r-- | Darwin.mk | 4 | ||||
-rw-r--r-- | LICENSE | 661 | ||||
-rw-r--r-- | Linux.mk | 6 | ||||
-rw-r--r-- | Makefile | 99 | ||||
-rw-r--r-- | NetBSD.mk | 3 | ||||
-rw-r--r-- | README.7 | 111 | ||||
-rw-r--r-- | catgirl.1 | 413 | ||||
-rw-r--r-- | chat.c | 91 | ||||
-rw-r--r-- | chat.h | 221 | ||||
-rw-r--r-- | color.c | 51 | ||||
-rw-r--r-- | edit.c | 186 | ||||
-rw-r--r-- | event.c | 168 | ||||
-rw-r--r-- | format.c | 162 | ||||
-rw-r--r-- | handle.c | 568 | ||||
-rw-r--r-- | input.c | 276 | ||||
-rw-r--r-- | irc.c | 147 | ||||
-rw-r--r-- | log.c | 157 | ||||
-rw-r--r-- | man.sh | 2 | ||||
-rw-r--r-- | pls.c | 186 | ||||
-rw-r--r-- | sandman.1 | 30 | ||||
-rw-r--r-- | sandman.m | 88 | ||||
-rw-r--r-- | sshd_config | 13 | ||||
-rw-r--r-- | tab.c | 148 | ||||
-rw-r--r-- | tag.c | 53 | ||||
-rw-r--r-- | term.c | 100 | ||||
-rw-r--r-- | ui.c | 615 | ||||
-rw-r--r-- | url.c | 111 |
28 files changed, 0 insertions, 4678 deletions
diff --git a/.gitignore b/.gitignore deleted file mode 100644 index fb17842..0000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -*.o -*.t -catgirl -chroot.tar -config.mk -root -sandman -tags diff --git a/Darwin.mk b/Darwin.mk deleted file mode 100644 index d1f26cc..0000000 --- a/Darwin.mk +++ /dev/null @@ -1,4 +0,0 @@ -LIBRESSL_PREFIX = /usr/local/opt/libressl -LDLIBS = -lcurses -ltls -framework Cocoa -BINS += sandman -MANS += sandman.1 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index dba13ed..0000000 --- a/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - 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/Linux.mk b/Linux.mk deleted file mode 100644 index f6f5535..0000000 --- a/Linux.mk +++ /dev/null @@ -1,6 +0,0 @@ -LIBRESSL_PREFIX = /usr/local -CFLAGS += -D_GNU_SOURCE -LDLIBS = -lncursesw -lpthread -LDLIBS += ${LIBRESSL_PREFIX}/lib/libtls.a -LDLIBS += ${LIBRESSL_PREFIX}/lib/libssl.a -LDLIBS += ${LIBRESSL_PREFIX}/lib/libcrypto.a diff --git a/Makefile b/Makefile deleted file mode 100644 index 8519d59..0000000 --- a/Makefile +++ /dev/null @@ -1,99 +0,0 @@ -PREFIX = ~/.local -MANDIR = ${PREFIX}/share/man -CHROOT_USER = chat -CHROOT_GROUP = ${CHROOT_USER} -LIBRESSL_PREFIX = /usr/local - -CFLAGS += -std=c11 -Wall -Wextra -Wpedantic -CFLAGS += -I${LIBRESSL_PREFIX}/include -LDFLAGS += -L${LIBRESSL_PREFIX}/lib -LDLIBS = -lcursesw -ltls - -BINS = catgirl -MANS = catgirl.1 - --include config.mk - -OBJS += chat.o -OBJS += color.o -OBJS += edit.o -OBJS += event.o -OBJS += format.o -OBJS += handle.o -OBJS += input.o -OBJS += irc.o -OBJS += log.o -OBJS += pls.o -OBJS += tab.o -OBJS += tag.o -OBJS += term.o -OBJS += ui.o -OBJS += url.o - -TESTS += format.t -TESTS += pls.t -TESTS += term.t - -all: tags ${BINS} test - -catgirl: ${OBJS} - ${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@ - -${OBJS}: chat.h - -test: ${TESTS} - set -e; ${TESTS:%=./%;} - -.SUFFIXES: .t - -.c.t: - ${CC} ${CFLAGS} -DTEST ${LDFLAGS} $< ${LDLIBS} -o $@ - -tags: *.c *.h - ctags -w *.c *.h - -install: ${BINS} ${MANS} - install -d ${PREFIX}/bin ${MANDIR}/man1 - install ${BINS} ${PREFIX}/bin - install -m 644 ${MANS} ${MANDIR}/man1 - -uninstall: - rm -f ${BINS:%=${PREFIX}/bin/%} - rm -f ${MANS:%=${MANDIR}/man1/%} - -chroot.tar: catgirl catgirl.1 man.sh - install -d -o root -g wheel \ - root \ - root/bin \ - root/etc/ssl \ - root/home \ - root/lib \ - root/libexec \ - root/usr/bin \ - root/usr/share/man \ - root/usr/share/misc - install -d -o ${CHROOT_USER} -g ${CHROOT_GROUP} root/home/${CHROOT_USER} - cp -fp /libexec/ld-elf.so.1 root/libexec - cp -fp \ - /lib/libc.so.7 \ - /lib/libncursesw.so.8 \ - /lib/libthr.so.3 \ - /lib/libz.so.6 \ - /usr/local/lib/libcrypto.so.45 \ - /usr/local/lib/libssl.so.47 \ - /usr/local/lib/libtls.so.19 \ - root/lib - cp -fp /etc/hosts /etc/resolv.conf root/etc - cp -fp /etc/ssl/cert.pem root/etc/ssl - cp -af /usr/share/locale root/usr/share - cp -fp /usr/share/misc/termcap.db root/usr/share/misc - cp -fp /rescue/sh /usr/bin/mandoc /usr/bin/less root/bin - ${MAKE} install PREFIX=root/usr - install man.sh root/usr/bin/man - tar -c -f chroot.tar -C root bin etc home lib libexec usr - -install-chroot: chroot.tar - tar -x -f chroot.tar -C /home/${CHROOT_USER} - -clean: - rm -fr ${BINS} ${OBJS} ${TESTS} tags root chroot.tar diff --git a/NetBSD.mk b/NetBSD.mk deleted file mode 100644 index 7c47665..0000000 --- a/NetBSD.mk +++ /dev/null @@ -1,3 +0,0 @@ -LIBRESSL_PREFIX = /usr/pkg/libressl -LDFLAGS += -rpath=${LIBRESSL_PREFIX}/lib -LDLIBS = -lcurses -ltls diff --git a/README.7 b/README.7 deleted file mode 100644 index cae56bb..0000000 --- a/README.7 +++ /dev/null @@ -1,111 +0,0 @@ -.Dd February 25, 2019 -.Dt CATGIRL 7 -.Os "Causal Agency" -. -.Sh NAME -.Nm catgirl -.Nd IRC client -. -.Sh DESCRIPTION -.Nm -is a curses IRC client -originally intended for -use over anonymous SSH. -. -.Pp -It requires LibreSSL -.Pq Fl ltls -and targets -.Fx , -Darwin, -.Nx -and -GNU/Linux. -. -.Sh INSTALL -On platforms other than -.Fx , -copy the appropriate file to -.Pa config.mk -and modify as needed. -The default install -.Va PREFIX -is -.Pa ~/.local . -. -.Pp -.Bd -literal -offset indent -cp $(uname).mk config.mk -make -make install -.Ed -. -.Ss Darwin -LibreSSL is assumed to be installed with -.Xr brew 1 . -The -.Xr sandman 1 -wrapper is also installed. -. -.Ss NetBSD -LibreSSL is assumed to be installed with -.Xr pkgsrc 7 . -Due to bugs in -.Nx Ap s -.Xr curses 3 -implementation, -some of the UI is currently broken. -. -.Ss GNU/Linux -LibreSSL is assumed to be manually installed in -.Pa /usr/local -and is statically linked. -. -.Sh FILES -.Bl -tag -width sandman.m -compact -.It Pa chat.h -shared state and function prototypes -.It Pa chat.c -command line parsing -.It Pa event.c -event loop and process spawning -.It Pa tag.c -tag (channel, query) ID assignment -.It Pa handle.c -incoming command handling -.It Pa input.c -input command handling -.It Pa irc.c -TLS client connection -.It Pa format.c -IRC formatting -.It Pa color.c -nick and channel coloring -.It Pa ui.c -cursed UI -.It Pa term.c -terminal features unsupported by curses -.It Pa edit.c -line editing -.It Pa tab.c -tab-complete -.It Pa url.c -URL detection -.It Pa pls.c -functions which should not have to be written -.It Pa sandman.m -utility for Darwin to signal sleep -.El -. -.Pp -.Bl -tag -width sshd_config -compact -.It Pa sshd_config -anonymous SSH configuration -.It Pa man.sh -.Xr man 1 -implementation for chroot -.El -. -.Sh SEE ALSO -.Xr catgirl 1 , -.Xr sandman 1 diff --git a/catgirl.1 b/catgirl.1 deleted file mode 100644 index 5511ed4..0000000 --- a/catgirl.1 +++ /dev/null @@ -1,413 +0,0 @@ -.Dd October 3, 2019 -.Dt CATGIRL 1 -.Os -. -.Sh NAME -.Nm catgirl -.Nd IRC client -. -.Sh SYNOPSIS -.Nm -.Op Fl NPRv -.Op Fl a Ar auth -.Op Fl h Ar host -.Op Fl j Ar chan -.Op Fl k Ar keys -.Op Fl l Ar path -.Op Fl n Ar nick -.Op Fl p Ar port -.Op Fl r Ar real -.Op Fl u Ar user -.Op Fl w Ar pass -. -.Sh DESCRIPTION -.Nm -is a curses, TLS-only IRC client. -. -.Pp -The arguments are as follows: -. -.Bl -tag -width "-w pass" -.It Fl N -Send notifications with -.Xr notify-send 1 . -. -.It Fl P -Prompt for nickname. -. -.It Fl R -Restrict the use of the -.Ic /join , -.Ic /query , -.Ic /quote , -.Ic /raw -commands. -. -.It Fl a Ar auth -Authenticate with SASL PLAIN. -.Ar auth -is a colon-separated -username and password pair. -. -.It Fl h Ar host -Connect to -.Ar host . -. -.It Fl j Ar chan -Join -.Ar chan -after connecting. -.Ar chan -may be a comma-separated list. -. -.It Fl k Ar keys -Set keys for channels in -.Fl j . -.Ar keys -may be a comma-separated list. -. -.It Fl l Ar path -Log messages to -subdirectories of -.Ar path -named by channel or nick -in files named by date. -. -.It Fl n Ar nick -Set nickname to -.Ar nick . -The default nickname -is the user's name. -. -.It Fl p Ar port -Connect to -.Ar port . -The default port is 6697. -. -.It Fl r Ar real -Set realname to -.Ar real . -The default realname is -the same as the nickname. -. -.It Fl u Ar user -Set username to -.Ar user . -The default username is -the same as the nickname. -. -.It Fl v -Show raw IRC protocol in the -.Sy <raw> -window. -If standard error is not a terminal, -output raw IRC protocol -to standard error. -. -.It Fl w Ar pass -Log in with -.Ar pass . -.El -. -.Sh COMMANDS -Any unique prefix -may be used to abbreviate a command. -. -.Ss Chat Commands -.Bl -tag -width Ds -.It Ic /join Ar chan Op Ar key -Join a channel. -. -.It Ic /list Op Ar chan -List channels. -. -.It Ic /me Op Ar action -Send an action message. -. -.It Ic /names , /who -List users in the current channel. -. -.It Ic /nick Ar nick -Change nicknames. -. -.It Ic /part Op Ar message -Leave the current channel. -. -.It Ic /query Ar nick -Open a private message view. -. -.It Ic /quit Op Ar message -Quit IRC. -. -.It Ic /quote Ar command -Send a raw IRC command. -. -.It Ic /topic Op Ar topic -Show or set the topic of the current channel. -. -.It Ic /whois Ar nick -Query information about a user. -. -.It Ic /znc Ar command -Send -.Xr znc 1 -command. -.El -. -.Pp -Any messages entered in the -.Sy <raw> -window will be sent as raw IRC commands. -. -.Ss UI Commands -.Bl -tag -width Ds -.It Ic /close -Close the current window. -. -.It Ic /help , /man -View this manual. -. -.It Ic /move Ar num -Move window to number. -If -.Ar num -starts with -.Cm + -or -.Cm - , -the number is relative to the current window. -. -.It Ic /open Op Ar range -Open a -.Ar range -of recent URLs -in the current window with -.Xr open 1 . -URLs are numbered -from the most recent -starting at 1. -The -.Ar range -may be a single number, -or a hyphen- or comma-separated range. -. -.It Ic /open Ar substring -Open the most recent URL -in the current window -matching the -.Ar substring . -. -.It Ic /raw -Toggle the -.Sy <raw> -window. -. -.It Ic /url -Hide the UI -and list the most recent URLs -in the current window. -Press -.Ic Enter -to resume the UI. -. -.It Ic /window Ar name -Switch to window by name. -. -.It Ic /window Ar num , Ic / Ns Ar num -Switch to window by number. -If -.Ar num -starts with -.Cm + -or -.Cm - , -the number is relative to the current window. -.El -. -.Sh KEY BINDINGS -.Nm -provides -.Xr emacs 1 Ns -like -line editing keys -as well as keys for applying IRC formatting. -The prefixes -.Ic C- , M- , S- -represent the control, meta (alt) and shift modifiers, -respectively. -.Ic M- Ns Ar x -sequences can also be typed as -.Ic Esc -followed by -.Ar x . -. -.Ss Line Editing -.Bl -tag -width Ds -compact -.It Ic C-a -Move cursor to beginning of line. -.It Ic C-b -Move cursor left. -.It Ic C-d -Delete character under cursor. -.It Ic C-e -Move cursor to end of line. -.It Ic C-f -Move cursor right. -.It Ic C-k -Delete line after cursor. -.It Ic C-u -Delete line. -.It Ic C-w -Delete word before cursor. -.It Ic M-b -Move cursor to beginning of word. -.It Ic M-d -Delete word after cursor. -.It Ic M-f -Move cursor to end of word. -.It Ic Tab -Cycle through completions for -commands, nicks and channels. -.El -. -.Ss IRC Formatting -.Bl -tag -width Ds -compact -.It Ic C-_ -Toggle underline. -.It Ic C-o -Toggle bold. -.It Ic C-r -Set or reset color. -.It Ic C-s -Reset formatting. -.It Ic C-t -Toggle italics. -.It Ic C-v -Toggle reverse video. -This must usually be typed as -.Ic C-v C-v . -.El -. -.Pp -To reset color, follow -.Ic C-r -by a non-digit. -To set colors, follow -.Ic C-r -by one or two digits -to set the foreground color, -optionally followed by a comma -and one or two digits -to set the background color. -. -.Pp -The color numbers are as follows: -.Pp -.Bl -column "7" "orange (dark yellow)" "15" "pink (light magenta)" -.It 0 Ta white Ta \ 8 Ta yellow -.It 1 Ta black Ta \ 9 Ta light green -.It 2 Ta blue Ta 10 Ta cyan -.It 3 Ta green Ta 11 Ta light cyan -.It 4 Ta red Ta 12 Ta light blue -.It 5 Ta brown (dark red) Ta 13 Ta pink (light magenta) -.It 6 Ta magenta Ta 14 Ta gray -.It 7 Ta orange (dark yellow) Ta 15 Ta light gray -.El -. -.Ss Window Keys -.Bl -tag -width "PageDown" -compact -.It Ic C-l -Redraw the UI. -.It Ic C-n -Switch to the next window. -.It Ic C-p -Switch to the previous window. -.It Ic M-/ -Switch to the previously active window. -.It Ic M-a -Switch to next hot or unread window. -.It Ic M-l -Hide the UI and list the log for the current window. -.It Ic M-m -Insert a blank line in the window. -.It Ic M- Ns Ar n -Switch to window by number 0\(en9. -.It Ic Down -Scroll window down by one line. -.It Ic PageDown -Scroll window down by one page. -.It Ic PageUp -Scroll window up by one page. -.It Ic Up -Scroll window up by one line. -.El -. -.Sh ENVIRONMENT -.Bl -tag -width Ds -.It Ev USER -The default nickname. -.El -. -.Sh EXAMPLES -.Dl catgirl -h chat.freenode.net -j '#ascii.town' -. -.Sh STANDARDS -.Nm -is a partial implementation of the following: -. -.Bl -item -.It -.Rs -.%A C. Kalt -.%T Internet Relay Chat: Client Protocol -.%I IETF -.%N RFC 2812 -.%D April 2000 -.%U https://tools.ietf.org/html/rfc2812 -.Re -. -.It -.Rs -.%A Kevin L. Mitchell -.%A Perry Lorier -.%A Lee Hardy -.%A William Pitcock -.%T IRCv3.1 Client Capability Negotiation -.%I IRCv3 Working Group -.%U https://ircv3.net/specs/core/capability-negotiation-3.1.html -.Re -. -.It -.Rs -.%A Jilles Tjoelker -.%A William Pitcock -.%T IRCv3.1 SASL Authentication -.%I IRCv3 Working Group -.%U https://ircv3.net/specs/extensions/sasl-3.1.html -.Re -. -.It -.Rs -.%A K. Zeilenga, Ed. -.%Q OpenLDAP Foundation -.%T The PLAIN Simple Authentication and Security Layer (SASL) Mechanism -.%I IETF -.%N RFC 4616 -.%D August 2006 -.%U https://tools.ietf.org/html/rfc4616 -.Re -. -.It -.Rs -.%A S. Josefsson -.%Q SJD -.%T The Base16, Base32, and Base64 Data Encodings -.%I IETF -.%N RFC 4648 -.%D October 2006 -.%U https://tools.ietf.org/html/rfc4648 -.Re -.El -. -.Sh CAVEATS -.Nm -does not support unencrypted connections. diff --git a/chat.c b/chat.c deleted file mode 100644 index 3a1cfe9..0000000 --- a/chat.c +++ /dev/null @@ -1,91 +0,0 @@ -/* Copyright (C) 2018 C. 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/>. - */ - -#define _WITH_GETLINE - -#include <err.h> -#include <locale.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sysexits.h> -#include <unistd.h> - -#include "chat.h" - -static char *dupe(const char *str) { - char *dup = strdup(str); - if (!dup) err(EX_OSERR, "strdup"); - return dup; -} - -static char *prompt(const char *prompt) { - char *line = NULL; - size_t cap; - for (;;) { - printf("%s", prompt); - fflush(stdout); - - ssize_t len = getline(&line, &cap, stdin); - if (ferror(stdin)) err(EX_IOERR, "getline"); - if (feof(stdin)) exit(EX_OK); - if (len < 2) continue; - - line[len - 1] = '\0'; - return line; - } -} - -int main(int argc, char *argv[]) { - setlocale(LC_CTYPE, ""); - - int opt; - while (0 < (opt = getopt(argc, argv, "!NPRa:h:j:k:l:n:p:r:u:vw:"))) { - switch (opt) { - break; case '!': self.insecure = true; - break; case 'N': self.notify = true; - break; case 'P': self.nick = prompt("Name: "); - break; case 'R': self.limit = true; - break; case 'a': self.auth = dupe(optarg); - break; case 'h': self.host = dupe(optarg); - break; case 'j': self.join = dupe(optarg); - break; case 'k': self.keys = dupe(optarg); - break; case 'l': logOpen(optarg); - break; case 'n': self.nick = dupe(optarg); - break; case 'p': self.port = dupe(optarg); - break; case 'r': self.real = dupe(optarg); - break; case 'u': self.user = dupe(optarg); - break; case 'v': self.raw = true; - break; case 'w': self.pass = dupe(optarg); - break; default: return EX_USAGE; - } - } - - if (!self.nick) { - const char *user = getenv("USER"); - if (!user) errx(EX_USAGE, "USER unset"); - self.nick = dupe(user); - } - - if (!self.host) self.host = prompt("Host: "); - if (!self.port) self.port = dupe("6697"); - if (!self.user) self.user = dupe(self.nick); - if (!self.real) self.real = dupe(self.nick); - - inputTab(); - uiInit(); - eventLoop(); -} diff --git a/chat.h b/chat.h deleted file mode 100644 index 01bab21..0000000 --- a/chat.h +++ /dev/null @@ -1,221 +0,0 @@ -/* Copyright (C) 2018 C. 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/>. - */ - -#define SOURCE_URL "https://git.causal.agency/catgirl" - -#include <stdarg.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <stdnoreturn.h> -#include <time.h> -#include <wchar.h> - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) - -#define err(...) do { uiHide(); err(__VA_ARGS__); } while (0) -#define errx(...) do { uiHide(); errx(__VA_ARGS__); } while (0) - -typedef unsigned uint; -typedef unsigned char byte; - -struct { - bool insecure; - char *host; - char *port; - char *auth; - char *pass; - char *nick; - char *user; - char *real; - char *join; - char *keys; - bool limit; - bool raw; - bool notify; - bool quit; -} self; - -void eventWait(const char *argv[static 2]); -void eventPipe(const char *argv[static 2]); -noreturn void eventLoop(void); - -struct Tag { - size_t id; - const char *name; -}; - -enum { TagsLen = 256 }; -const struct Tag TagNone; -const struct Tag TagStatus; -const struct Tag TagRaw; -struct Tag tagFind(const char *name); -struct Tag tagFor(const char *name); - -enum IRCColor { - IRCWhite, - IRCBlack, - IRCBlue, - IRCGreen, - IRCRed, - IRCBrown, - IRCMagenta, - IRCOrange, - IRCYellow, - IRCLightGreen, - IRCCyan, - IRCLightCyan, - IRCLightBlue, - IRCPink, - IRCGray, - IRCLightGray, - IRCDefault = 99, -}; -enum { - IRCBold = 002, - IRCColor = 003, - IRCReverse = 026, - IRCReset = 017, - IRCItalic = 035, - IRCUnderline = 037, -}; - -struct Format { - const wchar_t *str; - size_t len; - bool split; - bool bold, italic, underline, reverse; - enum IRCColor fg, bg; -}; -void formatReset(struct Format *format); -bool formatParse(struct Format *format, const wchar_t *split); - -enum IRCColor colorGen(const char *str); -struct Tag colorTag(struct Tag tag, const char *gen); -enum IRCColor colorFor(struct Tag tag); - -void handle(char *line); -void input(struct Tag tag, char *line); -void inputTab(void); - -int ircConnect(void); -void ircRead(void); -void ircWrite(const char *ptr, size_t len); -void ircFmt(const char *format, ...) __attribute__((format(printf, 1, 2))); -void ircQuit(const char *mesg); - -void uiInit(void); -void uiShow(void); -void uiHide(void); -void uiDraw(void); -void uiRead(void); -void uiExit(int status); - -void uiPrompt(bool nickChanged); -void uiShowTag(struct Tag tag); -void uiShowNum(int num, bool relative); -void uiMoveTag(struct Tag tag, int num, bool relative); -void uiCloseTag(struct Tag tag); - -enum UIHeat { - UICold, - UIWarm, - UIHot, -}; -void uiLog(struct Tag tag, enum UIHeat heat, const wchar_t *str); -void uiFmt(struct Tag tag, enum UIHeat heat, const wchar_t *format, ...); - -enum TermMode { - TermFocus, - TermPaste, -}; -enum TermEvent { - TermNone, - TermFocusIn, - TermFocusOut, - TermPasteStart, - TermPasteEnd, -}; -void termInit(void); -void termNoFlow(void); -void termTitle(const char *title); -void termMode(enum TermMode mode, bool set); -enum TermEvent termEvent(char ch); - -enum Edit { - EditLeft, - EditRight, - EditHome, - EditEnd, - EditBackWord, - EditForeWord, - EditInsert, - EditBackspace, - EditDelete, - EditKill, - EditKillBackWord, - EditKillForeWord, - EditKillEnd, - EditComplete, - EditEnter, -}; -void edit(struct Tag tag, enum Edit op, wchar_t ch); -const wchar_t *editHead(void); -const wchar_t *editTail(void); - -void tabTouch(struct Tag tag, const char *word); -void tabAdd(struct Tag tag, const char *word); -void tabRemove(struct Tag tag, const char *word); -void tabReplace(struct Tag tag, const char *prev, const char *next); -void tabClear(struct Tag tag); -struct Tag tabTag(const char *word); -const char *tabNext(struct Tag tag, const char *prefix); -void tabAccept(void); -void tabReject(void); - -void urlScan(struct Tag tag, const char *str); -void urlList(struct Tag tag); -void urlOpenMatch(struct Tag tag, const char *substr); -void urlOpenRange(struct Tag tag, size_t at, size_t to); - -void logOpen(const char *path); -void logFmt( - struct Tag tag, const time_t *ts, const char *format, ... -) __attribute__((format(printf, 3, 4))); -void logList(struct Tag tag); -void logReplay(struct Tag tag); - -wchar_t *wcsnchr(const wchar_t *wcs, size_t len, wchar_t chr); -wchar_t *wcsnrchr(const wchar_t *wcs, size_t len, wchar_t chr); -wchar_t *ambstowcs(const char *src); -char *awcstombs(const wchar_t *src); -char *awcsntombs(const wchar_t *src, size_t nwc); -int vaswprintf(wchar_t **ret, const wchar_t *format, va_list ap); -int aswprintf(wchar_t **ret, const wchar_t *format, ...); - -size_t base64Size(size_t len); -void base64(char *dst, const byte *src, size_t len); - -// HACK: clang won't check wchar_t *format strings. -#ifdef NDEBUG -#define uiFmt(tag, heat, format, ...) uiFmt(tag, heat, L##format, __VA_ARGS__) -#else -#define uiFmt(tag, heat, format, ...) do { \ - snprintf(NULL, 0, format, __VA_ARGS__); \ - uiFmt(tag, heat, L##format, __VA_ARGS__); \ -} while(0) -#endif diff --git a/color.c b/color.c deleted file mode 100644 index dad5647..0000000 --- a/color.c +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright (C) 2019 C. 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 <stdint.h> - -#include "chat.h" - -// Adapted from <https://github.com/cbreeden/fxhash/blob/master/lib.rs>. -static uint32_t hashChar(uint32_t hash, char ch) { - hash = (hash << 5) | (hash >> 27); - hash ^= ch; - hash *= 0x27220A95; - return hash; -} - -enum IRCColor colorGen(const char *str) { - if (!str) return IRCDefault; - uint32_t hash = 0; - if (*str == '~') str++; - for (; *str; ++str) { - hash = hashChar(hash, *str); - } - while (IRCBlack == (hash & IRCLightGray)) { - hash = hashChar(hash, '\0'); - } - return (hash & IRCLightGray); -} - -static enum IRCColor colors[TagsLen]; - -struct Tag colorTag(struct Tag tag, const char *gen) { - if (!colors[tag.id]) colors[tag.id] = 1 + colorGen(gen); - return tag; -} - -enum IRCColor colorFor(struct Tag tag) { - return colors[tag.id] ? colors[tag.id] - 1 : IRCDefault; -} diff --git a/edit.c b/edit.c deleted file mode 100644 index c63e4a2..0000000 --- a/edit.c +++ /dev/null @@ -1,186 +0,0 @@ -/* Copyright (C) 2018 C. 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 <err.h> -#include <stdbool.h> -#include <stdlib.h> -#include <sysexits.h> -#include <wchar.h> -#include <wctype.h> - -#include "chat.h" - -enum { BufLen = 512 }; -static struct { - wchar_t buf[BufLen]; - wchar_t *ptr; - wchar_t *end; - wchar_t *tab; -} line = { - .ptr = line.buf, - .end = line.buf, -}; - -const wchar_t *editHead(void) { - return line.buf; -} -const wchar_t *editTail(void) { - return line.ptr; -} - -static void left(void) { - if (line.ptr > line.buf) line.ptr--; -} -static void right(void) { - if (line.ptr < line.end) line.ptr++; -} - -static void backWord(void) { - left(); - wchar_t *word = wcsnrchr(line.buf, line.ptr - line.buf, L' '); - line.ptr = (word ? &word[1] : line.buf); -} -static void foreWord(void) { - right(); - wchar_t *word = wcsnchr(line.ptr, line.end - line.ptr, L' '); - line.ptr = (word ? word : line.end); -} - -static void insert(wchar_t ch) { - if (line.end == &line.buf[BufLen - 1]) return; - if (line.ptr != line.end) { - wmemmove(line.ptr + 1, line.ptr, line.end - line.ptr); - } - *line.ptr++ = ch; - line.end++; -} -static void backspace(void) { - if (line.ptr == line.buf) return; - if (line.ptr != line.end) { - wmemmove(line.ptr - 1, line.ptr, line.end - line.ptr); - } - line.ptr--; - line.end--; -} -static void delete(void) { - if (line.ptr == line.end) return; - right(); - backspace(); -} - -static void killBackWord(void) { - wchar_t *from = line.ptr; - backWord(); - wmemmove(line.ptr, from, line.end - from); - line.end -= from - line.ptr; -} -static void killForeWord(void) { - wchar_t *from = line.ptr; - foreWord(); - wmemmove(from, line.ptr, line.end - line.ptr); - line.end -= line.ptr - from; - line.ptr = from; -} - -static char *prefix; -static void complete(struct Tag tag) { - if (!line.tab) { - line.tab = wcsnrchr(line.buf, line.ptr - line.buf, L' '); - line.tab = (line.tab ? &line.tab[1] : line.buf); - prefix = awcsntombs(line.tab, line.ptr - line.tab); - if (!prefix) err(EX_DATAERR, "awcstombs"); - } - - const char *next = tabNext(tag, prefix); - if (!next) return; - - wchar_t *wcs = ambstowcs(next); - if (!wcs) err(EX_DATAERR, "ambstowcs"); - - size_t i = 0; - for (; wcs[i] && line.ptr > &line.tab[i]; ++i) { - line.tab[i] = wcs[i]; - } - while (line.ptr > &line.tab[i]) { - backspace(); - } - for (; wcs[i]; ++i) { - insert(wcs[i]); - } - free(wcs); - - size_t pos = line.tab - line.buf; - if (!pos && line.tab[0] != L'/') { - insert(L':'); - } else if (pos >= 2) { - if (line.buf[pos - 2] == L':') { - line.buf[pos - 2] = L','; - insert(L':'); - } - } - insert(L' '); -} - -static void accept(void) { - if (!line.tab) return; - line.tab = NULL; - free(prefix); - tabAccept(); -} -static void reject(void) { - if (!line.tab) return; - line.tab = NULL; - free(prefix); - tabReject(); -} - -static void enter(struct Tag tag) { - if (line.end == line.buf) return; - *line.end = L'\0'; - char *str = awcstombs(line.buf); - if (!str) err(EX_DATAERR, "awcstombs"); - input(tag, str); - free(str); - line.ptr = line.buf; - line.end = line.buf; -} - -void edit(struct Tag tag, enum Edit op, wchar_t ch) { - switch (op) { - break; case EditLeft: reject(); left(); - break; case EditRight: reject(); right(); - break; case EditHome: reject(); line.ptr = line.buf; - break; case EditEnd: reject(); line.ptr = line.end; - - break; case EditBackWord: reject(); backWord(); - break; case EditForeWord: reject(); foreWord(); - - break; case EditInsert: accept(); insert(ch); - break; case EditBackspace: reject(); backspace(); - break; case EditDelete: reject(); delete(); - - break; case EditKill: reject(); line.ptr = line.end = line.buf; - break; case EditKillBackWord: reject(); killBackWord(); - break; case EditKillForeWord: reject(); killForeWord(); - break; case EditKillEnd: reject(); line.end = line.ptr; - - break; case EditComplete: complete(tag); - - break; case EditEnter: accept(); enter(tag); - } - - *line.end = L'\0'; -} diff --git a/event.c b/event.c deleted file mode 100644 index c6e0987..0000000 --- a/event.c +++ /dev/null @@ -1,168 +0,0 @@ -/* Copyright (C) 2018 C. 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 <assert.h> -#include <err.h> -#include <errno.h> -#include <poll.h> -#include <signal.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <stdnoreturn.h> -#include <string.h> -#include <sys/wait.h> -#include <sysexits.h> -#include <unistd.h> - -#include "chat.h" - -static struct { - bool wait; - int pipe; -} child = { - .pipe = -1, -}; - -void eventWait(const char *argv[static 2]) { - uiHide(); - pid_t pid = fork(); - if (pid < 0) err(EX_OSERR, "fork"); - if (!pid) { - execvp(argv[0], (char *const *)argv); - err(EX_CONFIG, "%s", argv[0]); - } - child.wait = true; -} - -static void childWait(void) { - uiShow(); - int status; - pid_t pid = wait(&status); - if (pid < 0) err(EX_OSERR, "wait"); - if (WIFEXITED(status) && WEXITSTATUS(status)) { - uiFmt(TagStatus, UIHot, "event: exit %d", WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - uiFmt( - TagStatus, UIHot, - "event: signal %s", strsignal(WTERMSIG(status)) - ); - } - child.wait = false; -} - -void eventPipe(const char *argv[static 2]) { - if (child.pipe > 0) { - uiLog(TagStatus, UIHot, L"event: existing pipe"); - return; - } - - int rw[2]; - int error = pipe(rw); - if (error) err(EX_OSERR, "pipe"); - - pid_t pid = fork(); - if (pid < 0) err(EX_OSERR, "fork"); - if (!pid) { - close(rw[0]); - close(STDIN_FILENO); - dup2(rw[1], STDOUT_FILENO); - dup2(rw[1], STDERR_FILENO); - close(rw[1]); - execvp(argv[0], (char *const *)argv); - perror(argv[0]); - exit(EX_CONFIG); - } - - close(rw[1]); - child.pipe = rw[0]; -} - -static void childRead(void) { - char buf[256]; - ssize_t len = read(child.pipe, buf, sizeof(buf) - 1); - if (len < 0) err(EX_IOERR, "read"); - if (len) { - buf[len] = '\0'; - buf[strcspn(buf, "\n")] = '\0'; - uiFmt(TagStatus, UIHot, "event: %s", buf); - } else { - close(child.pipe); - child.pipe = -1; - } -} - -static volatile sig_atomic_t sig[NSIG]; -static void handler(int n) { - sig[n] = 1; -} - -noreturn void eventLoop(void) { - sigset_t mask; - sigemptyset(&mask); - struct sigaction action = { - .sa_handler = handler, - .sa_mask = mask, - .sa_flags = SA_RESTART | SA_NOCLDSTOP, - }; - sigaction(SIGCHLD, &action, NULL); - sigaction(SIGINT, &action, NULL); - sigaction(SIGHUP, &action, NULL); - - struct sigaction curses; - sigaction(SIGWINCH, &action, &curses); - assert(!(curses.sa_flags & SA_SIGINFO)); - - uiShowTag(TagStatus); - uiFmt(TagStatus, UICold, "Traveling to %s...", self.host); - uiDraw(); - int irc = ircConnect(); - - for (;;) { - if (sig[SIGCHLD]) childWait(); - if (sig[SIGHUP]) ircQuit("zzz"); - if (sig[SIGINT]) { - signal(SIGINT, SIG_DFL); - ircQuit("Goodbye"); - } - if (sig[SIGWINCH]) { - curses.sa_handler(SIGWINCH); - uiRead(); - uiDraw(); - } - sig[SIGCHLD] = sig[SIGHUP] = sig[SIGINT] = sig[SIGWINCH] = 0; - - struct pollfd fds[3] = { - { .events = POLLIN, .fd = irc }, - { .events = POLLIN, .fd = STDIN_FILENO }, - { .events = POLLIN, .fd = child.pipe }, - }; - if (child.wait) fds[1].events = 0; - if (child.pipe < 0) fds[2].events = 0; - - int nfds = poll(fds, 3, -1); - if (nfds < 0) { - if (errno == EINTR) continue; - err(EX_IOERR, "poll"); - } - - if (fds[0].revents) ircRead(); - if (fds[1].revents) uiRead(); - if (fds[2].revents) childRead(); - - uiDraw(); - } -} diff --git a/format.c b/format.c deleted file mode 100644 index 71d1d93..0000000 --- a/format.c +++ /dev/null @@ -1,162 +0,0 @@ -/* Copyright (C) 2018 C. 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 <stdbool.h> -#include <stdint.h> -#include <stdlib.h> -#include <wchar.h> - -#include "chat.h" - -void formatReset(struct Format *format) { - format->bold = false; - format->italic = false; - format->underline = false; - format->reverse = false; - format->fg = IRCDefault; - format->bg = IRCDefault; -} - -static void parseColor(struct Format *format) { - size_t len = MIN(wcsspn(format->str, L"0123456789"), 2); - if (!len) { - format->fg = IRCDefault; - format->bg = IRCDefault; - return; - } - format->fg = 0; - for (size_t i = 0; i < len; ++i) { - format->fg *= 10; - format->fg += format->str[i] - L'0'; - } - if (format->fg > IRCLightGray) format->fg = IRCDefault; - format->str = &format->str[len]; - - len = 0; - if (format->str[0] == L',') { - len = MIN(wcsspn(&format->str[1], L"0123456789"), 2); - } - if (!len) return; - format->bg = 0; - for (size_t i = 0; i < len; ++i) { - format->bg *= 10; - format->bg += format->str[1 + i] - L'0'; - } - if (format->bg > IRCLightGray) format->bg = IRCDefault; - format->str = &format->str[1 + len]; -} - -static const wchar_t Codes[] = { - IRCBold, IRCColor, IRCReverse, IRCReset, IRCItalic, IRCUnderline, L'\0', -}; - -bool formatParse(struct Format *format, const wchar_t *split) { - format->str += format->len; - if (!format->str[0]) { - if (split == format->str && !format->split) { - format->len = 0; - format->split = true; - return true; - } - return false; - } - - const wchar_t *init = format->str; - for (bool done = false; !done;) { - switch (format->str[0]) { - break; case IRCBold: format->str++; format->bold ^= true; - break; case IRCItalic: format->str++; format->italic ^= true; - break; case IRCUnderline: format->str++; format->underline ^= true; - break; case IRCReverse: format->str++; format->reverse ^= true; - break; case IRCColor: format->str++; parseColor(format); - break; case IRCReset: format->str++; formatReset(format); - break; default: done = true; - } - } - format->split = (split >= init && split <= format->str); - - format->len = wcscspn(format->str, Codes); - if (split > format->str && split < &format->str[format->len]) { - format->len = split - format->str; - } - return true; -} - -#ifdef TEST -#include <assert.h> - -static bool testColor( - const wchar_t *str, enum IRCColor fg, enum IRCColor bg, size_t index -) { - struct Format format = { .str = str }; - formatReset(&format); - if (!formatParse(&format, NULL)) return false; - if (format.fg != fg) return false; - if (format.bg != bg) return false; - return (format.str == &str[index]); -} - -static bool testSplit(const wchar_t *str, size_t index) { - struct Format format = { .str = str }; - formatReset(&format); - bool split = false; - while (formatParse(&format, &str[index])) { - if (format.split && split) return false; - if (format.split) split = true; - } - return split; -} - -static bool testSplits(const wchar_t *str) { - for (size_t i = 0; i <= wcslen(str); ++i) { - if (!testSplit(str, i)) return false; - } - return true; -} - -int main() { - assert(testColor(L"\003a", IRCDefault, IRCDefault, 1)); - assert(testColor(L"\003,a", IRCDefault, IRCDefault, 1)); - assert(testColor(L"\003,1", IRCDefault, IRCDefault, 1)); - assert(testColor(L"\0031a", IRCBlack, IRCDefault, 2)); - assert(testColor(L"\0031,a", IRCBlack, IRCDefault, 2)); - assert(testColor(L"\00312a", IRCLightBlue, IRCDefault, 3)); - assert(testColor(L"\00312,a", IRCLightBlue, IRCDefault, 3)); - assert(testColor(L"\003123", IRCLightBlue, IRCDefault, 3)); - assert(testColor(L"\0031,1a", IRCBlack, IRCBlack, 4)); - assert(testColor(L"\0031,12a", IRCBlack, IRCLightBlue, 5)); - assert(testColor(L"\0031,123", IRCBlack, IRCLightBlue, 5)); - assert(testColor(L"\00312,1a", IRCLightBlue, IRCBlack, 5)); - assert(testColor(L"\00312,12a", IRCLightBlue, IRCLightBlue, 6)); - assert(testColor(L"\00312,123", IRCLightBlue, IRCLightBlue, 6)); - - assert(testColor(L"\00316,16a", IRCDefault, IRCDefault, 6)); - assert(testColor(L"\00399,99a", IRCDefault, IRCDefault, 6)); - - assert(testSplits(L"")); - assert(testSplits(L"ab")); - assert(testSplits(L"\002")); - assert(testSplits(L"\002ab")); - assert(testSplits(L"a\002b")); - assert(testSplits(L"\002\003")); - assert(testSplits(L"a\002\003b")); - assert(testSplits(L"a\0031b")); - assert(testSplits(L"a\00312b")); - assert(testSplits(L"a\00312,1b")); - assert(testSplits(L"a\00312,12b")); -} - -#endif diff --git a/handle.c b/handle.c deleted file mode 100644 index fe15d9a..0000000 --- a/handle.c +++ /dev/null @@ -1,568 +0,0 @@ -/* Copyright (C) 2018, 2019 C. 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 <ctype.h> -#include <err.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sysexits.h> -#include <time.h> - -#include "chat.h" - -static char *paramField(char **params) { - char *rest = *params; - if (rest[0] == ':') { - *params = NULL; - return &rest[1]; - } - return strsep(params, " "); -} - -static void parse( - char *prefix, char **nick, char **user, char **host, - char *params, size_t req, size_t opt, /* (char **) */ ... -) { - char *field; - if (prefix) { - field = strsep(&prefix, "!"); - if (nick) *nick = field; - field = strsep(&prefix, "@"); - if (user) *user = field; - if (host) *host = prefix; - } - - va_list ap; - va_start(ap, opt); - for (size_t i = 0; i < req; ++i) { - if (!params) errx(EX_PROTOCOL, "%zu params required, found %zu", req, i); - field = paramField(¶ms); - char **param = va_arg(ap, char **); - if (param) *param = field; - } - for (size_t i = 0; i < opt; ++i) { - char **param = va_arg(ap, char **); - if (params) { - *param = paramField(¶ms); - } else { - *param = NULL; - } - } - va_end(ap); -} - -static bool isPing(const char *mesg) { - size_t len = strlen(self.nick); - const char *match = mesg; - while (NULL != (match = strcasestr(match, self.nick))) { - char b = (match > mesg ? *(match - 1) : ' '); - char a = (match[len] ? match[len] : ' '); - match = &match[len]; - if (!isspace(b) && !ispunct(b)) continue; - if (!isspace(a) && !ispunct(a)) continue; - return true; - } - return false; -} - -static char *dequote(char *mesg) { - if (mesg[0] == '"') mesg = &mesg[1]; - size_t len = strlen(mesg); - if (mesg[len - 1] == '"') mesg[len - 1] = '\0'; - return mesg; -} - -typedef void Handler(char *prefix, char *params); - -static void handlePing(char *prefix, char *params) { - (void)prefix; - ircFmt("PONG %s\r\n", params); -} - -static void handleError(char *prefix, char *params) { - char *mesg; - parse(prefix, NULL, NULL, NULL, params, 1, 0, &mesg); - if (self.quit) { - uiExit(EX_OK); - } else { - errx(EX_PROTOCOL, "%s", mesg); - } -} - -static void handleCap(char *prefix, char *params) { - char *subc, *list; - parse(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &subc, &list); - if (!strcmp(subc, "ACK") && self.auth) { - size_t len = strlen(self.auth); - byte plain[1 + len]; - plain[0] = 0; - for (size_t i = 0; i < len; ++i) { - plain[1 + i] = (self.auth[i] == ':' ? 0 : self.auth[i]); - } - char b64[base64Size(sizeof(plain))]; - base64(b64, plain, sizeof(plain)); - ircFmt("AUTHENTICATE PLAIN\r\n"); - ircFmt("AUTHENTICATE %s\r\n", b64); - } - ircFmt("CAP END\r\n"); -} - -static void handleErrorErroneousNickname(char *prefix, char *params) { - char *mesg; - parse(prefix, NULL, NULL, NULL, params, 3, 0, NULL, NULL, &mesg); - uiFmt(TagStatus, UIHot, "You can't use that name here: \"%s\"", mesg); - uiLog(TagStatus, UICold, L"Type /nick <name> to choose a new one"); -} - -static void handleReplyWelcome(char *prefix, char *params) { - char *nick; - parse(prefix, NULL, NULL, NULL, params, 1, 0, &nick); - - if (strcmp(nick, self.nick)) { - free(self.nick); - self.nick = strdup(nick); - if (!self.nick) err(EX_OSERR, "strdup"); - uiPrompt(true); - } - if (self.join && self.keys) { - ircFmt("JOIN %s %s\r\n", self.join, self.keys); - } else if (self.join) { - ircFmt("JOIN %s\r\n", self.join); - } - tabTouch(TagStatus, self.nick); - - uiLog(TagStatus, UICold, L"You have arrived"); -} - -static void handleReplyMOTD(char *prefix, char *params) { - char *mesg; - parse(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &mesg); - if (mesg[0] == '-' && mesg[1] == ' ') mesg = &mesg[2]; - - urlScan(TagStatus, mesg); - uiFmt(TagStatus, UICold, "%s", mesg); -} - -static void handleReplyList(char *prefix, char *params) { - char *chan, *count, *topic; - parse(prefix, NULL, NULL, NULL, params, 4, 0, NULL, &chan, &count, &topic); - if (topic[0] == '[') { - char *skip = strstr(topic, "] "); - if (skip) topic = &skip[2]; - } - const char *people = (strcmp(count, "1") ? "people" : "person"); - if (topic[0]) { - uiFmt( - TagStatus, UIWarm, - "You see %s %s in \3%d%s\3 under the banner, \"%s\"", - count, people, colorGen(chan), chan, topic - ); - } else { - uiFmt( - TagStatus, UIWarm, - "You see %s %s in \3%d%s\3", - count, people, colorGen(chan), chan - ); - } -} - -static void handleReplyListEnd(char *prefix, char *params) { - (void)prefix; - (void)params; - uiLog(TagStatus, UICold, L"You don't see anyone else"); -} - -static enum IRCColor whoisColor; -static void handleReplyWhoisUser(char *prefix, char *params) { - char *nick, *user, *host, *real; - parse( - prefix, NULL, NULL, NULL, - params, 6, 0, NULL, &nick, &user, &host, NULL, &real - ); - whoisColor = colorGen(user); - uiFmt( - TagStatus, UIWarm, - "\3%d%s\3 is %s@%s, \"%s\"", - whoisColor, nick, user, host, real - ); -} - -static void handleReplyWhoisServer(char *prefix, char *params) { - char *nick, *serv, *info; - parse(prefix, NULL, NULL, NULL, params, 4, 0, NULL, &nick, &serv, &info); - uiFmt( - TagStatus, UIWarm, - "\3%d%s\3 is connected to %s, \"%s\"", - whoisColor, nick, serv, info - ); -} - -static void handleReplyWhoisOperator(char *prefix, char *params) { - char *nick, *oper; - parse(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &nick, &oper); - uiFmt(TagStatus, UIWarm, "\3%d%s\3 %s", whoisColor, nick, oper); -} - -static void handleReplyWhoisIdle(char *prefix, char *params) { - char *nick, *idle, *sign; - parse(prefix, NULL, NULL, NULL, params, 4, 0, NULL, &nick, &idle, &sign); - time_t time = strtoul(sign, NULL, 10); - const char *at = ctime(&time); - unsigned long secs = strtoul(idle, NULL, 10); - unsigned long mins = secs / 60; secs %= 60; - unsigned long hours = mins / 60; mins %= 60; - uiFmt( - TagStatus, UIWarm, - "\3%d%s\3 signed on at %.24s and has been idle for %02lu:%02lu:%02lu", - whoisColor, nick, at, hours, mins, secs - ); -} - -static void handleReplyWhoisChannels(char *prefix, char *params) { - char *nick, *chans; - parse(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &nick, &chans); - uiFmt(TagStatus, UIWarm, "\3%d%s\3 is in %s", whoisColor, nick, chans); -} - -static void handleErrorNoSuchNick(char *prefix, char *params) { - char *nick, *mesg; - parse(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &nick, &mesg); - uiFmt(TagStatus, UIWarm, "%s, \"%s\"", mesg, nick); -} - -static void handleJoin(char *prefix, char *params) { - char *nick, *user, *chan; - parse(prefix, &nick, &user, NULL, params, 1, 0, &chan); - struct Tag tag = colorTag(tagFor(chan), chan); - - if (!strcmp(nick, self.nick)) { - tabTouch(TagNone, chan); - uiShowTag(tag); - logReplay(tag); - } - tabTouch(tag, nick); - - uiFmt( - tag, UICold, - "\3%d%s\3 arrives in \3%d%s\3", - colorGen(user), nick, colorGen(chan), chan - ); - logFmt(tag, NULL, "%s arrives in %s", nick, chan); -} - -static void handlePart(char *prefix, char *params) { - char *nick, *user, *chan, *mesg; - parse(prefix, &nick, &user, NULL, params, 1, 1, &chan, &mesg); - struct Tag tag = colorTag(tagFor(chan), chan); - - if (!strcmp(nick, self.nick)) { - tabClear(tag); - } else { - tabRemove(tag, nick); - } - - if (mesg) { - urlScan(tag, mesg); - uiFmt( - tag, UICold, - "\3%d%s\3 leaves \3%d%s\3, \"%s\"", - colorGen(user), nick, colorGen(chan), chan, dequote(mesg) - ); - logFmt(tag, NULL, "%s leaves %s, \"%s\"", nick, chan, dequote(mesg)); - } else { - uiFmt( - tag, UICold, - "\3%d%s\3 leaves \3%d%s\3", - colorGen(user), nick, colorGen(chan), chan - ); - logFmt(tag, NULL, "%s leaves %s", nick, chan); - } -} - -static void handleKick(char *prefix, char *params) { - char *nick, *user, *chan, *kick, *mesg; - parse(prefix, &nick, &user, NULL, params, 2, 1, &chan, &kick, &mesg); - struct Tag tag = colorTag(tagFor(chan), chan); - bool kicked = !strcmp(kick, self.nick); - - if (kicked) { - tabClear(tag); - } else { - tabRemove(tag, kick); - } - - if (mesg) { - urlScan(tag, mesg); - uiFmt( - tag, (kicked ? UIHot : UICold), - "\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3, \"%s\"", - colorGen(user), nick, - colorGen(kick), kick, - colorGen(chan), chan, - dequote(mesg) - ); - logFmt( - tag, NULL, - "%s kicks %s out of %s, \"%s\"", nick, kick, chan, dequote(mesg) - ); - } else { - uiFmt( - tag, (kicked ? UIHot : UICold), - "\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3", - colorGen(user), nick, - colorGen(kick), kick, - colorGen(chan), chan - ); - logFmt(tag, NULL, "%s kicks %s out of %s", nick, kick, chan); - } -} - -static void handleQuit(char *prefix, char *params) { - char *nick, *user, *mesg; - parse(prefix, &nick, &user, NULL, params, 0, 1, &mesg); - - struct Tag tag; - while (TagNone.id != (tag = tabTag(nick)).id) { - tabRemove(tag, nick); - - if (mesg) { - urlScan(tag, mesg); - uiFmt( - tag, UICold, - "\3%d%s\3 leaves, \"%s\"", - colorGen(user), nick, dequote(mesg) - ); - logFmt(tag, NULL, "%s leaves, \"%s\"", nick, dequote(mesg)); - } else { - uiFmt(tag, UICold, "\3%d%s\3 leaves", colorGen(user), nick); - logFmt(tag, NULL, "%s leaves", nick); - } - } -} - -static void handleReplyTopic(char *prefix, char *params) { - char *chan, *topic; - parse(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &chan, &topic); - struct Tag tag = colorTag(tagFor(chan), chan); - - urlScan(tag, topic); - uiFmt( - tag, UICold, - "The sign in \3%d%s\3 reads, \"%s\"", - colorGen(chan), chan, topic - ); - logFmt(tag, NULL, "The sign in %s reads, \"%s\"", chan, topic); -} - -static void handleTopic(char *prefix, char *params) { - char *nick, *user, *chan, *topic; - parse(prefix, &nick, &user, NULL, params, 2, 0, &chan, &topic); - struct Tag tag = colorTag(tagFor(chan), chan); - - if (strcmp(nick, self.nick)) tabTouch(tag, nick); - - urlScan(tag, topic); - uiFmt( - tag, UICold, - "\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"", - colorGen(user), nick, colorGen(chan), chan, topic - ); - logFmt(tag, NULL, "%s places a new sign in %s, \"%s\"", nick, chan, topic); -} - -static void handleReplyEndOfNames(char *prefix, char *params) { - char *chan; - parse(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan); - ircFmt("WHO %s\r\n", chan); -} - -static struct { - char buf[4096]; - size_t len; -} who; - -static void handleReplyWho(char *prefix, char *params) { - char *chan, *user, *nick; - parse( - prefix, NULL, NULL, NULL, - params, 6, 0, NULL, &chan, &user, NULL, NULL, &nick - ); - struct Tag tag = colorTag(tagFor(chan), chan); - - tabAdd(tag, nick); - - size_t cap = sizeof(who.buf) - who.len; - int len = snprintf( - &who.buf[who.len], cap, - "%s\3%d%s\3", - (who.len ? ", " : ""), colorGen(user), nick - ); - if ((size_t)len < cap) who.len += len; -} - -static void handleReplyEndOfWho(char *prefix, char *params) { - char *chan; - parse(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan); - struct Tag tag = colorTag(tagFor(chan), chan); - - uiFmt( - tag, UICold, - "In \3%d%s\3 are %s", - colorGen(chan), chan, who.buf - ); - who.len = 0; -} - -static void handleNick(char *prefix, char *params) { - char *prev, *user, *next; - parse(prefix, &prev, &user, NULL, params, 1, 0, &next); - - if (!strcmp(prev, self.nick)) { - free(self.nick); - self.nick = strdup(next); - if (!self.nick) err(EX_OSERR, "strdup"); - uiPrompt(true); - } - - struct Tag tag; - while (TagNone.id != (tag = tabTag(prev)).id) { - tabReplace(tag, prev, next); - - uiFmt( - tag, UICold, - "\3%d%s\3 is now known as \3%d%s\3", - colorGen(user), prev, colorGen(user), next - ); - logFmt(tag, NULL, "%s is now known as %s", prev, next); - } -} - -static void handleCTCP(struct Tag tag, char *nick, char *user, char *mesg) { - mesg = &mesg[1]; - char *ctcp = strsep(&mesg, " "); - char *params = strsep(&mesg, "\1"); - if (strcmp(ctcp, "ACTION")) return; - - if (strcmp(nick, self.nick)) tabTouch(tag, nick); - - urlScan(tag, params); - bool ping = strcmp(nick, self.nick) && isPing(params); - uiFmt( - tag, (ping ? UIHot : UIWarm), - "%c\3%d* %s\17 %s", - ping["\17\26"], colorGen(user), nick, params - ); - logFmt(tag, NULL, "* %s %s", nick, params); -} - -static void handlePrivmsg(char *prefix, char *params) { - char *nick, *user, *chan, *mesg; - parse(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg); - bool direct = !strcmp(chan, self.nick); - struct Tag tag = tagFor(direct ? nick : chan); - colorTag(tag, direct ? user : chan); - if (mesg[0] == '\1') { - handleCTCP(tag, nick, user, mesg); - return; - } - - bool me = !strcmp(nick, self.nick); - if (!me) tabTouch(tag, nick); - - urlScan(tag, mesg); - bool hot = !me && (direct || isPing(mesg)); - bool ping = !me && isPing(mesg); - uiFmt( - tag, (hot ? UIHot : UIWarm), - "%c%c\3%d<%s>%c %s", - (me ? IRCUnderline : IRCColor), (ping ? IRCReverse : IRCColor), - colorGen(user), nick, IRCReset, mesg - ); - logFmt(tag, NULL, "<%s> %s", nick, mesg); -} - -static void handleNotice(char *prefix, char *params) { - char *nick, *user, *chan, *mesg; - parse(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg); - bool direct = !strcmp(chan, self.nick); - struct Tag tag = TagStatus; - if (user) { - tag = tagFor(direct ? nick : chan); - colorTag(tag, direct ? user : chan); - } - - if (strcmp(nick, self.nick)) tabTouch(tag, nick); - - urlScan(tag, mesg); - bool ping = strcmp(nick, self.nick) && isPing(mesg); - uiFmt( - tag, (ping ? UIHot : UIWarm), - "%c\3%d-%s-\17 %s", - ping["\17\26"], colorGen(user), nick, mesg - ); - logFmt(tag, NULL, "-%s- %s", nick, mesg); -} - -static const struct { - const char *command; - Handler *handler; -} Handlers[] = { - { "001", handleReplyWelcome }, - { "311", handleReplyWhoisUser }, - { "312", handleReplyWhoisServer }, - { "313", handleReplyWhoisOperator }, - { "315", handleReplyEndOfWho }, - { "317", handleReplyWhoisIdle }, - { "319", handleReplyWhoisChannels }, - { "322", handleReplyList }, - { "323", handleReplyListEnd }, - { "332", handleReplyTopic }, - { "352", handleReplyWho }, - { "366", handleReplyEndOfNames }, - { "372", handleReplyMOTD }, - { "375", handleReplyMOTD }, - { "401", handleErrorNoSuchNick }, - { "432", handleErrorErroneousNickname }, - { "433", handleErrorErroneousNickname }, - { "CAP", handleCap }, - { "ERROR", handleError }, - { "JOIN", handleJoin }, - { "KICK", handleKick }, - { "NICK", handleNick }, - { "NOTICE", handleNotice }, - { "PART", handlePart }, - { "PING", handlePing }, - { "PRIVMSG", handlePrivmsg }, - { "QUIT", handleQuit }, - { "TOPIC", handleTopic }, -}; -static const size_t HandlersLen = sizeof(Handlers) / sizeof(Handlers[0]); - -void handle(char *line) { - char *prefix = NULL; - if (line[0] == ':') { - prefix = strsep(&line, " ") + 1; - if (!line) errx(EX_PROTOCOL, "unexpected eol"); - } - char *command = strsep(&line, " "); - for (size_t i = 0; i < HandlersLen; ++i) { - if (strcmp(command, Handlers[i].command)) continue; - Handlers[i].handler(prefix, line); - break; - } -} diff --git a/input.c b/input.c deleted file mode 100644 index 8be8eaf..0000000 --- a/input.c +++ /dev/null @@ -1,276 +0,0 @@ -/* Copyright (C) 2018 C. 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 <ctype.h> -#include <err.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sysexits.h> - -#include "chat.h" - -static void privmsg(struct Tag tag, bool action, const char *mesg) { - char *line; - int send; - asprintf( - &line, ":%s!%s %nPRIVMSG %s :%s%s%s", - self.nick, self.user, &send, tag.name, - (action ? "\1ACTION " : ""), mesg, (action ? "\1" : "") - ); - if (!line) err(EX_OSERR, "asprintf"); - ircFmt("%s\r\n", &line[send]); - handle(line); - free(line); -} - -typedef void Handler(struct Tag tag, char *params); - -static void inputJoin(struct Tag tag, char *params) { - char *chan = strsep(¶ms, " "); - char *key = strsep(¶ms, " "); - if (key) { - ircFmt("JOIN %s %s\r\n", chan, key); - } else { - ircFmt("JOIN %s\r\n", chan ? chan : tag.name); - } -} - -static void inputList(struct Tag tag, char *params) { - (void)tag; - char *chan = strsep(¶ms, " "); - if (chan) { - ircFmt("LIST %s\r\n", chan); - } else { - ircFmt("LIST\r\n"); - } -} - -static void inputMe(struct Tag tag, char *params) { - privmsg(tag, true, params ? params : ""); -} - -static void inputNick(struct Tag tag, char *params) { - char *nick = strsep(¶ms, " "); - if (!nick) { - uiLog(tag, UIHot, L"/nick requires a name"); - return; - } - ircFmt("NICK %s\r\n", nick); -} - -static void inputPart(struct Tag tag, char *params) { - ircFmt("PART %s :%s\r\n", tag.name, params ? params : "Goodbye"); -} - -static void inputQuery(struct Tag tag, char *params) { - char *nick = strsep(¶ms, " "); - if (!nick) { - uiLog(tag, UIHot, L"/query requires a nick"); - return; - } - tabTouch(TagNone, nick); - uiShowTag(tagFor(nick)); - logReplay(tagFor(nick)); -} - -static void inputQuit(struct Tag tag, char *params) { - (void)tag; - ircQuit(params ? params : "Goodbye"); -} - -static void inputQuote(struct Tag tag, char *params) { - (void)tag; - if (params) ircFmt("%s\r\n", params); -} - -static void inputTopic(struct Tag tag, char *params) { - if (params) { - ircFmt("TOPIC %s :%s\r\n", tag.name, params); - } else { - ircFmt("TOPIC %s\r\n", tag.name); - } -} - -static void inputWho(struct Tag tag, char *params) { - (void)params; - ircFmt("WHO :%s\r\n", tag.name); -} - -static void inputWhois(struct Tag tag, char *params) { - char *nick = strsep(¶ms, " "); - if (!nick) { - uiLog(tag, UIHot, L"/whois requires a nick"); - return; - } - ircFmt("WHOIS %s\r\n", nick); -} - -static void inputZNC(struct Tag tag, char *params) { - (void)tag; - ircFmt("ZNC %s\r\n", params ? params : ""); -} - -static void inputClose(struct Tag tag, char *params) { - (void)params; - uiCloseTag(tag); - tabRemove(TagNone, tag.name); -} - -static void inputMan(struct Tag tag, char *params) { - (void)tag; - (void)params; - eventWait((const char *[]) { "man", "1", "catgirl", NULL }); -} - -static void inputMove(struct Tag tag, char *params) { - char *num = strsep(¶ms, " "); - if (!num) { - uiLog(tag, UIHot, L"/move requires a number"); - return; - } - uiMoveTag(tag, strtol(num, NULL, 0), num[0] == '+' || num[0] == '-'); -} - -static void inputOpen(struct Tag tag, char *params) { - if (params && !isdigit(params[0])) { - urlOpenMatch(tag, params); - } else { - size_t at = (params ? strtoul(strsep(¶ms, "-,"), NULL, 0) : 1); - size_t to = (params ? strtoul(params, NULL, 0) : at); - urlOpenRange(tag, at - 1, to); - } -} - -static void inputRaw(struct Tag tag, char *params) { - (void)tag; - (void)params; - self.raw ^= true; - uiFmt( - TagRaw, UIWarm, "\3%d%s\3 %s raw mode!", - colorGen(self.user), self.nick, (self.raw ? "engages" : "disengages") - ); -} - -static void inputURL(struct Tag tag, char *params) { - (void)params; - urlList(tag); -} - -static void inputWindow(struct Tag tag, char *params) { - char *word = strsep(¶ms, " "); - if (!word) { - uiLog(tag, UIHot, L"/window requires a name or number"); - return; - } - bool relative = (word[0] == '+' || word[0] == '-'); - char *trail; - int num = strtol(word, &trail, 0); - if (!trail[0]) { - uiShowNum(num, relative); - } else { - struct Tag name = tagFind(word); - if (name.id != TagNone.id) { - uiShowTag(name); - } else { - uiFmt(tag, UIHot, "No window for %s", word); - } - } -} - -static const struct { - const char *command; - Handler *handler; - bool limit; -} Commands[] = { - { "/close", .handler = inputClose }, - { "/help", .handler = inputMan }, - { "/join", .handler = inputJoin, .limit = true }, - { "/list", .handler = inputList }, - { "/man", .handler = inputMan }, - { "/me", .handler = inputMe }, - { "/move", .handler = inputMove }, - { "/names", .handler = inputWho }, - { "/nick", .handler = inputNick }, - { "/open", .handler = inputOpen }, - { "/part", .handler = inputPart }, - { "/query", .handler = inputQuery, .limit = true }, - { "/quit", .handler = inputQuit }, - { "/quote", .handler = inputQuote, .limit = true }, - { "/raw", .handler = inputRaw, .limit = true }, - { "/topic", .handler = inputTopic }, - { "/url", .handler = inputURL }, - { "/who", .handler = inputWho }, - { "/whois", .handler = inputWhois }, - { "/window", .handler = inputWindow }, - { "/znc", .handler = inputZNC }, -}; -static const size_t CommandsLen = sizeof(Commands) / sizeof(Commands[0]); - -void inputTab(void) { - for (size_t i = 0; i < CommandsLen; ++i) { - tabTouch(TagNone, Commands[i].command); - } -} - -void input(struct Tag tag, char *input) { - bool slash = (input[0] == '/'); - if (slash) { - char *space = strchr(&input[1], ' '); - char *extra = strchr(&input[1], '/'); - if (extra && (!space || extra < space)) slash = false; - } - - if (!slash) { - if (tag.id == TagRaw.id) { - ircFmt("%s\r\n", input); - } else if (tag.id != TagStatus.id) { - privmsg(tag, false, input); - } - return; - } - - char *word = strsep(&input, " "); - if (input && !input[0]) input = NULL; - - char *trail; - strtol(&word[1], &trail, 0); - if (!trail[0]) { - inputWindow(tag, &word[1]); - return; - } - - const char *command = word; - const char *uniq = tabNext(TagNone, command); - if (uniq && tabNext(TagNone, command) == uniq) { - command = uniq; - tabAccept(); - } else { - tabReject(); - } - - for (size_t i = 0; i < CommandsLen; ++i) { - if (strcasecmp(command, Commands[i].command)) continue; - if (self.limit && Commands[i].limit) { - uiFmt(tag, UIHot, "%s isn't available in restricted mode", command); - return; - } - Commands[i].handler(tag, input); - return; - } - uiFmt(tag, UIHot, "%s isn't a recognized command", command); -} diff --git a/irc.c b/irc.c deleted file mode 100644 index 56a5dc0..0000000 --- a/irc.c +++ /dev/null @@ -1,147 +0,0 @@ -/* Copyright (C) 2018 C. 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 <err.h> -#include <fcntl.h> -#include <netdb.h> -#include <netinet/in.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/socket.h> -#include <sysexits.h> -#include <tls.h> -#include <unistd.h> - -#include "chat.h" - -static struct tls *client; - -int ircConnect(void) { - int error; - - struct tls_config *config = tls_config_new(); - error = tls_config_set_ciphers(config, "compat"); - if (error) errx(EX_SOFTWARE, "tls_config"); - - client = tls_client(); - if (!client) errx(EX_SOFTWARE, "tls_client"); - - error = tls_configure(client, config); - if (self.insecure) { - tls_config_insecure_noverifycert(config); - tls_config_insecure_noverifyname(config); - } - if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(client)); - tls_config_free(config); - - struct addrinfo *head; - struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, - .ai_protocol = IPPROTO_TCP, - }; - error = getaddrinfo(self.host, self.port, &hints, &head); - if (error) errx(EX_NOHOST, "getaddrinfo: %s", gai_strerror(error)); - - int sock = -1; - for (struct addrinfo *ai = head; ai; ai = ai->ai_next) { - sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (sock < 0) err(EX_OSERR, "socket"); - error = connect(sock, ai->ai_addr, ai->ai_addrlen); - if (!error) break; - - close(sock); - sock = -1; - } - if (sock < 0) err(EX_UNAVAILABLE, "connect"); - freeaddrinfo(head); - - error = fcntl(sock, F_SETFD, FD_CLOEXEC); - if (error) err(EX_IOERR, "fcntl"); - - error = tls_connect_socket(client, sock, self.host); - if (error) errx(EX_PROTOCOL, "tls_connect: %s", tls_error(client)); - - if (self.auth) ircFmt("CAP REQ :sasl\r\n"); - if (self.pass) ircFmt("PASS :%s\r\n", self.pass); - ircFmt("NICK %s\r\n", self.nick); - ircFmt("USER %s 0 * :%s\r\n", self.user, self.real); - - return sock; -} - -void ircWrite(const char *ptr, size_t len) { - while (len) { - ssize_t ret = tls_write(client, ptr, len); - if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) continue; - if (ret < 0) errx(EX_IOERR, "tls_write: %s", tls_error(client)); - ptr += ret; - len -= ret; - } -} - -void ircFmt(const char *format, ...) { - char *buf; - va_list ap; - va_start(ap, format); - int len = vasprintf(&buf, format, ap); - va_end(ap); - if (!buf) err(EX_OSERR, "vasprintf"); - if (self.raw) { - if (!isatty(STDERR_FILENO)) fprintf(stderr, "<<< %.*s\n", len - 2, buf); - uiFmt(TagRaw, UICold, "\3%d<<<\3 %.*s", IRCWhite, len - 2, buf); - } - ircWrite(buf, len); - free(buf); -} - -void ircQuit(const char *mesg) { - ircFmt("QUIT :%s\r\n", mesg); - self.quit = true; -} - -void ircRead(void) { - static char buf[4096]; - static size_t len; - - ssize_t read; -retry: - read = tls_read(client, &buf[len], sizeof(buf) - len); - if (read == TLS_WANT_POLLIN || read == TLS_WANT_POLLOUT) goto retry; - if (read < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client)); - if (!read) { - if (!self.quit) errx(EX_PROTOCOL, "unexpected eof"); - uiExit(EX_OK); - } - len += read; - - char *crlf; - char *line = buf; - while (NULL != (crlf = memmem(line, &buf[len] - line, "\r\n", 2))) { - crlf[0] = '\0'; - if (self.raw) { - if (!isatty(STDERR_FILENO)) fprintf(stderr, ">>> %s\n", line); - uiFmt(TagRaw, UICold, "\3%d>>>\3 %s", IRCGray, line); - } - handle(line); - line = &crlf[2]; - } - - len -= line - buf; - memmove(buf, line, len); -} diff --git a/log.c b/log.c deleted file mode 100644 index 2681bac..0000000 --- a/log.c +++ /dev/null @@ -1,157 +0,0 @@ -/* Copyright (C) 2018 C. 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 <err.h> -#include <errno.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <sysexits.h> -#include <time.h> - -#include "chat.h" - -static int logRoot = -1; - -static struct Log { - int dir; - int year; - int month; - int day; - FILE *file; -} logs[TagsLen]; - -void logOpen(const char *path) { - logRoot = open(path, O_RDONLY | O_CLOEXEC); - if (logRoot < 0) err(EX_CANTCREAT, "%s", path); -} - -static void sanitize(char *name) { - for (; name[0]; ++name) { - if (name[0] == '/') name[0] = '_'; - } -} - -static FILE *logFile(struct Tag tag, const struct tm *time) { - struct Log *log = &logs[tag.id]; - if ( - log->file - && log->year == time->tm_year - && log->month == time->tm_mon - && log->day == time->tm_mday - ) return log->file; - - if (log->file) { - fclose(log->file); - - } else { - char *name = strdup(tag.name); - if (!name) err(EX_OSERR, "strdup"); - sanitize(name); - - int error = mkdirat(logRoot, name, 0700); - if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", name); - - log->dir = openat(logRoot, name, O_RDONLY | O_CLOEXEC); - if (log->dir < 0) err(EX_CANTCREAT, "%s", name); - - free(name); - } - - log->year = time->tm_year; - log->month = time->tm_mon; - log->day = time->tm_mday; - - char path[sizeof("YYYY-MM-DD.log")]; - strftime(path, sizeof(path), "%F.log", time); - int fd = openat( - log->dir, path, O_RDWR | O_APPEND | O_CREAT | O_CLOEXEC, 0600 - ); - if (fd < 0) err(EX_CANTCREAT, "%s/%s", tag.name, path); - - log->file = fdopen(fd, "a+"); - if (!log->file) err(EX_CANTCREAT, "%s/%s", tag.name, path); - setlinebuf(log->file); - - return log->file; -} - -enum { StampLen = sizeof("YYYY-MM-DDThh:mm:ss+hhmm") - 1 }; - -void logFmt(struct Tag tag, const time_t *ts, const char *format, ...) { - if (logRoot < 0) return; - - time_t t; - if (!ts) { - t = time(NULL); - ts = &t; - } - - struct tm *time = localtime(ts); - if (!time) err(EX_SOFTWARE, "localtime"); - - FILE *file = logFile(tag, time); - - char stamp[StampLen + 1]; - strftime(stamp, sizeof(stamp), "%FT%T%z", time); - fprintf(file, "[%s] ", stamp); - if (ferror(file)) err(EX_IOERR, "%s", tag.name); - - va_list ap; - va_start(ap, format); - vfprintf(file, format, ap); - va_end(ap); - if (ferror(file)) err(EX_IOERR, "%s", tag.name); - - fprintf(file, "\n"); - if (ferror(file)) err(EX_IOERR, "%s", tag.name); -} - -static void logRead(struct Tag tag, bool replay) { - if (logRoot < 0) return; - - time_t t = time(NULL); - struct tm *time = localtime(&t); - if (!time) err(EX_SOFTWARE, "localtime"); - - FILE *file = logFile(tag, time); - rewind(file); - - char *line = NULL; - size_t cap = 0; - ssize_t len; - while (0 < (len = getline(&line, &cap, file))) { - if (replay) { - if (len < 1 + StampLen + 2 + 1) continue; - line[len - 1] = '\0'; - uiFmt(tag, UICold, "\3%d%s", IRCGray, &line[1 + StampLen + 2]); - } else { - printf("%s", line); - } - } - if (ferror(file)) err(EX_IOERR, "%s", tag.name); - free(line); -} - -void logList(struct Tag tag) { - logRead(tag, false); -} - -void logReplay(struct Tag tag) { - logRead(tag, true); -} diff --git a/man.sh b/man.sh deleted file mode 100644 index 9d686f9..0000000 --- a/man.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -exec mandoc /usr/share/man/man1/catgirl.1 | LESSSECURE=1 less diff --git a/pls.c b/pls.c deleted file mode 100644 index b724033..0000000 --- a/pls.c +++ /dev/null @@ -1,186 +0,0 @@ -/* Copyright (C) 2018 C. 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 <errno.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <wchar.h> - -#include "chat.h" - -wchar_t *wcsnchr(const wchar_t *wcs, size_t len, wchar_t chr) { - len = wcsnlen(wcs, len); - for (size_t i = 0; i < len; ++i) { - if (wcs[i] == chr) return (wchar_t *)&wcs[i]; - } - return NULL; -} - -wchar_t *wcsnrchr(const wchar_t *wcs, size_t len, wchar_t chr) { - len = wcsnlen(wcs, len); - for (size_t i = len - 1; i < len; --i) { - if (wcs[i] == chr) return (wchar_t *)&wcs[i]; - } - return NULL; -} - -wchar_t *ambstowcs(const char *src) { - size_t len = mbsrtowcs(NULL, &src, 0, NULL); - if (len == (size_t)-1) return NULL; - - wchar_t *dst = malloc(sizeof(*dst) * (1 + len)); - if (!dst) return NULL; - - len = mbsrtowcs(dst, &src, len, NULL); - if (len == (size_t)-1) { - free(dst); - return NULL; - } - - dst[len] = L'\0'; - return dst; -} - -char *awcstombs(const wchar_t *src) { - size_t len = wcsrtombs(NULL, &src, 0, NULL); - if (len == (size_t)-1) return NULL; - - char *dst = malloc(sizeof(*dst) * (1 + len)); - if (!dst) return NULL; - - len = wcsrtombs(dst, &src, len, NULL); - if (len == (size_t)-1) { - free(dst); - return NULL; - } - - dst[len] = '\0'; - return dst; -} - -char *awcsntombs(const wchar_t *src, size_t nwc) { - size_t len = wcsnrtombs(NULL, &src, nwc, 0, NULL); - if (len == (size_t)-1) return NULL; - - char *dst = malloc(sizeof(*dst) * (1 + len)); - if (!dst) return NULL; - - len = wcsnrtombs(dst, &src, nwc, len, NULL); - if (len == (size_t)-1) { - free(dst); - return NULL; - } - - dst[len] = '\0'; - return dst; -} - -// From <https://en.cppreference.com/w/c/io/fwprintf#Notes>: -// -// While narrow strings provide snprintf, which makes it possible to determine -// the required output buffer size, there is no equivalent for wide strings -// (until C11's snwprintf_s), and in order to determine the buffer size, the -// program may need to call swprintf, check the result value, and reallocate a -// larger buffer, trying again until successful. -// -// snwprintf_s, unlike swprintf_s, will truncate the result to fit within the -// array pointed to by buffer, even though truncation is treated as an error by -// most bounds-checked functions. -int vaswprintf(wchar_t **ret, const wchar_t *format, va_list ap) { - *ret = NULL; - - for (size_t cap = 2 * wcslen(format);; cap *= 2) { - wchar_t *buf = realloc(*ret, sizeof(*buf) * (1 + cap)); - if (!buf) goto fail; - *ret = buf; - - va_list _ap; - va_copy(_ap, ap); - errno = EOVERFLOW; // vswprintf may not set errno. - int len = vswprintf(*ret, 1 + cap, format, _ap); - va_end(_ap); - - if (len >= 0) return len; - if (errno != EOVERFLOW) goto fail; - } - -fail: - free(*ret); - *ret = NULL; - return -1; -} - -int aswprintf(wchar_t **ret, const wchar_t *format, ...) { - va_list ap; - va_start(ap, format); - int n = vaswprintf(ret, format, ap); - va_end(ap); - return n; -} - -static const char Base64[64] = { - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -}; - -size_t base64Size(size_t len) { - return 1 + (len + 2) / 3 * 4; -} - -void base64(char *dst, const byte *src, size_t len) { - size_t i = 0; - while (len > 2) { - dst[i++] = Base64[0x3F & (src[0] >> 2)]; - dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)]; - dst[i++] = Base64[0x3F & (src[1] << 2 | src[2] >> 6)]; - dst[i++] = Base64[0x3F & src[2]]; - src += 3; - len -= 3; - } - if (len) { - dst[i++] = Base64[0x3F & (src[0] >> 2)]; - if (len > 1) { - dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)]; - dst[i++] = Base64[0x3F & (src[1] << 2)]; - } else { - dst[i++] = Base64[0x3F & (src[0] << 4)]; - dst[i++] = '='; - } - dst[i++] = '='; - } - dst[i] = '\0'; -} - -#ifdef TEST -#include <assert.h> -#include <string.h> - -int main() { - assert(5 == base64Size(1)); - assert(5 == base64Size(2)); - assert(5 == base64Size(3)); - assert(9 == base64Size(4)); - - char b64[base64Size(3)]; - assert((base64(b64, (byte *)"cat", 3), !strcmp("Y2F0", b64))); - assert((base64(b64, (byte *)"ca", 2), !strcmp("Y2E=", b64))); - assert((base64(b64, (byte *)"c", 1), !strcmp("Yw==", b64))); - - assert((base64(b64, (byte *)"\xFF\x00\xFF", 3), !strcmp("/wD/", b64))); - assert((base64(b64, (byte *)"\x00\xFF\x00", 3), !strcmp("AP8A", b64))); -} - -#endif diff --git a/sandman.1 b/sandman.1 deleted file mode 100644 index bd68874..0000000 --- a/sandman.1 +++ /dev/null @@ -1,30 +0,0 @@ -.Dd July 2, 2019 -.Dt SANDMAN 1 -.Os -. -.Sh NAME -.Nm sandman -.Nd signal sleep -. -.Sh SYNOPSIS -.Nm -.Ar command ... -. -.Sh DESCRIPTION -.Nm -is a utility for Darwin systems. -It runs the -.Ar command -as a child process. -When the system goes to sleep, -the process is sent -.Dv SIGHUP . -When the system wakes up, -the process is restarted. -. -.Sh EXIT STATUS -.Nm -exits with the exit status of the child process. -. -.Sh SEE ALSO -.Xr signal 3 diff --git a/sandman.m b/sandman.m deleted file mode 100644 index 94c7d1a..0000000 --- a/sandman.m +++ /dev/null @@ -1,88 +0,0 @@ -/* Copyright (C) 2019 C. 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/>. - */ - -#import <Cocoa/Cocoa.h> -#import <err.h> -#import <signal.h> -#import <stdlib.h> -#import <sysexits.h> -#import <unistd.h> - -static volatile sig_atomic_t sleeping; - -static void sigchld(int sig) { - (void)sig; - int status; - pid_t pid = wait(&status); - if (pid < 0) _exit(EX_OSERR); - if (WIFSIGNALED(status) && WTERMSIG(status) != SIGHUP) { - _exit(128 + WTERMSIG(status)); - } else if (!sleeping) { - _exit(WEXITSTATUS(status)); - } -} - -static pid_t spawn(char *argv[]) { - pid_t pid = fork(); - if (pid < 0) err(EX_OSERR, "fork"); - if (pid) return pid; - execvp(argv[0], argv); - err(EX_NOINPUT, "%s", argv[0]); -} - -static pid_t pid; - -int main(int argc, char *argv[]) { - if (argc < 2) return EX_USAGE; - - sigset_t mask; - sigemptyset(&mask); - struct sigaction action = { - .sa_handler = sigchld, - .sa_mask = mask, - .sa_flags = SA_NOCLDSTOP | SA_RESTART, - }; - sigaction(SIGCHLD, &action, NULL); - - pid = spawn(&argv[1]); - - [ - [[NSWorkspace sharedWorkspace] notificationCenter] - addObserverForName: NSWorkspaceWillSleepNotification - object: nil - queue: nil - usingBlock: ^(NSNotification *note) { - (void)note; - sleeping = 1; - int error = kill(pid, SIGHUP); - if (error) err(EX_UNAVAILABLE, "kill %d", pid); - } - ]; - - [ - [[NSWorkspace sharedWorkspace] notificationCenter] - addObserverForName: NSWorkspaceDidWakeNotification - object: nil - queue: nil - usingBlock: ^(NSNotification *note) { - (void)note; - sleeping = 0; - pid = spawn(&argv[1]); - } - ]; - - [[NSApplication sharedApplication] run]; -} diff --git a/sshd_config b/sshd_config deleted file mode 100644 index 47b5a33..0000000 --- a/sshd_config +++ /dev/null @@ -1,13 +0,0 @@ -UsePAM no - -Match User chat - PasswordAuthentication yes - PermitEmptyPasswords yes - ChrootDirectory /home/chat - ForceCommand catgirl - - AllowAgentForwarding no - AllowTcpForwarding no - AllowStreamLocalForwarding no - MaxSessions 1 - X11Forwarding no diff --git a/tab.c b/tab.c deleted file mode 100644 index a17218d..0000000 --- a/tab.c +++ /dev/null @@ -1,148 +0,0 @@ -/* Copyright (C) 2018 C. 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 <err.h> -#include <stdlib.h> -#include <string.h> -#include <sysexits.h> - -#include "chat.h" - -static struct Entry { - struct Tag tag; - char *word; - struct Entry *prev; - struct Entry *next; -} *head; - -static void prepend(struct Entry *entry) { - entry->prev = NULL; - entry->next = head; - if (head) head->prev = entry; - head = entry; -} - -static void unlink(struct Entry *entry) { - if (entry->prev) entry->prev->next = entry->next; - if (entry->next) entry->next->prev = entry->prev; - if (head == entry) head = entry->next; -} - -static void touch(struct Entry *entry) { - if (head == entry) return; - unlink(entry); - prepend(entry); -} - -static struct Entry *find(struct Tag tag, const char *word) { - for (struct Entry *entry = head; entry; entry = entry->next) { - if (entry->tag.id != tag.id) continue; - if (strcmp(entry->word, word)) continue; - return entry; - } - return NULL; -} - -static void add(struct Tag tag, const char *word) { - struct Entry *entry = malloc(sizeof(*entry)); - if (!entry) err(EX_OSERR, "malloc"); - - entry->tag = tag; - entry->word = strdup(word); - if (!entry->word) err(EX_OSERR, "strdup"); - - prepend(entry); -} - -void tabTouch(struct Tag tag, const char *word) { - struct Entry *entry = find(tag, word); - if (entry) { - touch(entry); - } else { - add(tag, word); - } -} - -void tabAdd(struct Tag tag, const char *word) { - if (!find(tag, word)) add(tag, word); -} - -void tabReplace(struct Tag tag, const char *prev, const char *next) { - struct Entry *entry = find(tag, prev); - if (!entry) return; - touch(entry); - free(entry->word); - entry->word = strdup(next); - if (!entry->word) err(EX_OSERR, "strdup"); -} - -static struct Entry *iter; - -void tabRemove(struct Tag tag, const char *word) { - for (struct Entry *entry = head; entry; entry = entry->next) { - if (entry->tag.id != tag.id) continue; - if (strcmp(entry->word, word)) continue; - if (iter == entry) iter = entry->prev; - unlink(entry); - free(entry->word); - free(entry); - return; - } -} - -void tabClear(struct Tag tag) { - for (struct Entry *entry = head; entry; entry = entry->next) { - if (entry->tag.id != tag.id) continue; - if (iter == entry) iter = entry->prev; - unlink(entry); - free(entry->word); - free(entry); - } -} - -struct Tag tabTag(const char *word) { - struct Entry *start = (iter ? iter->next : head); - for (struct Entry *entry = start; entry; entry = entry->next) { - if (strcmp(entry->word, word)) continue; - iter = entry; - return entry->tag; - } - iter = NULL; - return TagNone; -} - -const char *tabNext(struct Tag tag, const char *prefix) { - size_t len = strlen(prefix); - struct Entry *start = (iter ? iter->next : head); - for (struct Entry *entry = start; entry; entry = entry->next) { - if (entry->tag.id != TagNone.id && entry->tag.id != tag.id) continue; - if (strncasecmp(entry->word, prefix, len)) continue; - iter = entry; - return entry->word; - } - if (!iter) return NULL; - iter = NULL; - return tabNext(tag, prefix); -} - -void tabAccept(void) { - if (iter) touch(iter); - iter = NULL; -} - -void tabReject(void) { - iter = NULL; -} diff --git a/tag.c b/tag.c deleted file mode 100644 index 5b4232e..0000000 --- a/tag.c +++ /dev/null @@ -1,53 +0,0 @@ -/* Copyright (C) 2018 C. 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 <err.h> -#include <stdlib.h> -#include <string.h> -#include <sysexits.h> - -#include "chat.h" - -static struct { - char *name[TagsLen]; - size_t len; -} tags = { - .name = { "<none>", "<status>", "<raw>" }, - .len = 3, -}; - -const struct Tag TagNone = { 0, "<none>" }; -const struct Tag TagStatus = { 1, "<status>" }; -const struct Tag TagRaw = { 2, "<raw>" }; - -struct Tag tagFind(const char *name) { - for (size_t id = 0; id < tags.len; ++id) { - if (strcmp(tags.name[id], name)) continue; - return (struct Tag) { id, tags.name[id] }; - } - return TagNone; -} - -struct Tag tagFor(const char *name) { - struct Tag tag = tagFind(name); - if (tag.id != TagNone.id) return tag; - if (tags.len == TagsLen) return TagStatus; - - size_t id = tags.len++; - tags.name[id] = strdup(name); - if (!tags.name[id]) err(EX_OSERR, "strdup"); - return (struct Tag) { id, tags.name[id] }; -} diff --git a/term.c b/term.c deleted file mode 100644 index 4b583ae..0000000 --- a/term.c +++ /dev/null @@ -1,100 +0,0 @@ -/* Copyright (C) 2018 C. 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 <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <termios.h> -#include <unistd.h> - -#include "chat.h" - -static bool xterm; - -void termInit(void) { - char *term = getenv("TERM"); - xterm = term && !strncmp(term, "xterm", 5); -} - -void termNoFlow(void) { - struct termios attr; - int error = tcgetattr(STDIN_FILENO, &attr); - if (error) return; - attr.c_iflag &= ~IXON; - attr.c_cc[VDISCARD] = _POSIX_VDISABLE; - tcsetattr(STDIN_FILENO, TCSANOW, &attr); -} - -void termTitle(const char *title) { - if (!xterm) return; - printf("\33]0;%s\33\\", title); - fflush(stdout); -} - -static void privateMode(const char *mode, bool set) { - printf("\33[?%s%c", mode, (set ? 'h' : 'l')); - fflush(stdout); -} - -void termMode(enum TermMode mode, bool set) { - switch (mode) { - break; case TermFocus: privateMode("1004", set); - break; case TermPaste: privateMode("2004", set); - } -} - -#define T(s, i) ((s) << 8 | (i)) - -enum { Esc = '\33' }; - -enum TermEvent termEvent(char ch) { - static uint state = 0; - switch (T(state, ch)) { - case T(0, Esc): state = 1; return 0; - case T(1, '['): state = 2; return 0; - case T(2, 'I'): state = 0; return TermFocusIn; - case T(2, 'O'): state = 0; return TermFocusOut; - case T(2, '2'): state = 3; return 0; - case T(3, '0'): state = 4; return 0; - case T(4, '0'): state = 5; return 0; - case T(5, '~'): state = 0; return TermPasteStart; - case T(4, '1'): state = 6; return 0; - case T(6, '~'): state = 0; return TermPasteEnd; - default: state = 0; return 0; - } -} - -#ifdef TEST -#include <assert.h> - -static bool testEvent(const char *str, enum TermEvent event) { - enum TermEvent e = TermNone; - for (size_t i = 0; i < strlen(str); ++i) { - if (e) return false; - e = termEvent(str[i]); - } - return (e == event); -} - -int main() { - assert(testEvent("\33[I", TermFocusIn)); - assert(testEvent("\33[O", TermFocusOut)); - assert(testEvent("\33[200~", TermPasteStart)); - assert(testEvent("\33[201~", TermPasteEnd)); -} - -#endif diff --git a/ui.c b/ui.c deleted file mode 100644 index 9cf21d3..0000000 --- a/ui.c +++ /dev/null @@ -1,615 +0,0 @@ -/* Copyright (C) 2018, 2019 C. 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/>. - */ - -#define _XOPEN_SOURCE_EXTENDED - -#include <curses.h> -#include <err.h> -#include <stdarg.h> -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <sysexits.h> -#include <wchar.h> -#include <wctype.h> - -#ifndef A_ITALIC -#define A_ITALIC A_UNDERLINE -#endif - -#include "chat.h" -#undef uiFmt - -#define CTRL(ch) ((ch) & 037) -enum { Esc = L'\33', Del = L'\177' }; - -static const int LogLines = 512; - -static int lastLine(void) { - return LINES - 1; -} -static int lastCol(void) { - return COLS - 1; -} -static int logHeight(void) { - return LINES - 2; -} - -struct Window { - struct Tag tag; - WINDOW *log; - bool hot; - bool mark; - int scroll; - uint unread; - struct Window *prev; - struct Window *next; -}; - -static struct { - struct Window *active; - struct Window *other; - struct Window *head; - struct Window *tail; - struct Window *tag[TagsLen]; -} windows; - -static void windowAppend(struct Window *win) { - if (windows.tail) windows.tail->next = win; - win->prev = windows.tail; - win->next = NULL; - windows.tail = win; - if (!windows.head) windows.head = win; - windows.tag[win->tag.id] = win; -} - -static void windowInsert(struct Window *win, struct Window *next) { - win->prev = next->prev; - win->next = next; - if (win->prev) win->prev->next = win; - win->next->prev = win; - if (!win->prev) windows.head = win; - windows.tag[win->tag.id] = win; -} - -static void windowRemove(struct Window *win) { - windows.tag[win->tag.id] = NULL; - if (win->prev) win->prev->next = win->next; - if (win->next) win->next->prev = win->prev; - if (windows.head == win) windows.head = win->next; - if (windows.tail == win) windows.tail = win->prev; -} - -static struct Window *windowFor(struct Tag tag) { - struct Window *win = windows.tag[tag.id]; - if (win) { - win->tag = tag; - return win; - } - - win = calloc(1, sizeof(*win)); - if (!win) err(EX_OSERR, "calloc"); - - win->tag = tag; - win->mark = true; - win->scroll = LogLines; - - win->log = newpad(LogLines, COLS); - wsetscrreg(win->log, 0, LogLines - 1); - scrollok(win->log, true); - wmove(win->log, LogLines - 1, 0); - - windowAppend(win); - return win; -} - -static void windowResize(struct Window *win) { - wresize(win->log, LogLines, COLS); - wmove(win->log, LogLines - 1, lastCol()); -} - -static void windowMark(struct Window *win) { - win->mark = true; -} -static void windowUnmark(struct Window *win) { - win->mark = false; - win->unread = 0; - win->hot = false; -} - -static void windowShow(struct Window *win) { - if (windows.active) windowMark(windows.active); - if (win) { - touchwin(win->log); - windowUnmark(win); - } - windows.other = windows.active; - windows.active = win; -} - -static void windowClose(struct Window *win) { - if (windows.active == win) windowShow(win->next ? win->next : win->prev); - if (windows.other == win) windows.other = NULL; - windowRemove(win); - delwin(win->log); - free(win); -} - -static void windowScroll(struct Window *win, int lines) { - if (lines < 0) { - if (win->scroll == logHeight()) return; - if (win->scroll == LogLines) windowMark(win); - win->scroll = MAX(win->scroll + lines, logHeight()); - } else { - if (win->scroll == LogLines) return; - win->scroll = MIN(win->scroll + lines, LogLines); - if (win->scroll == LogLines) windowUnmark(win); - } -} - -static void colorInit(void) { - start_color(); - use_default_colors(); - if (COLORS < 16) { - for (short pair = 0; pair < 0100; ++pair) { - if (pair < 010) { - init_pair(1 + pair, pair, -1); - } else { - init_pair(1 + pair, pair & 007, (pair & 070) >> 3); - } - } - } else { - for (short pair = 0; pair < 0x100; ++pair) { - if (pair < 0x10) { - init_pair(1 + pair, pair, -1); - } else { - init_pair(1 + pair, pair & 0x0F, (pair & 0xF0) >> 4); - } - } - } -} - -static attr_t colorAttr(short color) { - if (color < 0) return A_NORMAL; - if (COLORS < 16 && (color & 0x08)) return A_BOLD; - return A_NORMAL; -} -static short colorPair(short color) { - if (color < 0) return 0; - if (COLORS < 16) return 1 + ((color & 0x70) >> 1 | (color & 0x07)); - return 1 + color; -} - -static struct { - bool hide; - WINDOW *status; - WINDOW *input; -} ui; - -void uiInit(void) { - initscr(); - cbreak(); - noecho(); - termInit(); - termNoFlow(); - def_prog_mode(); - colorInit(); - ui.status = newwin(1, COLS, 0, 0); - ui.input = newpad(1, 512); - keypad(ui.input, true); - nodelay(ui.input, true); - uiShow(); -} - -static void uiResize(void) { - wresize(ui.status, 1, COLS); - for (struct Window *win = windows.head; win; win = win->next) { - windowResize(win); - } -} - -void uiShow(void) { - ui.hide = false; - termMode(TermFocus, true); - uiDraw(); -} -void uiHide(void) { - ui.hide = true; - termMode(TermFocus, false); - endwin(); -} - -void uiExit(int status) { - uiHide(); - printf( - "This program is AGPLv3 Free Software!\n" - "Code is available from <" SOURCE_URL ">.\n" - ); - exit(status); -} - -static int _; -void uiDraw(void) { - if (ui.hide) return; - wnoutrefresh(ui.status); - if (windows.active) { - pnoutrefresh( - windows.active->log, - windows.active->scroll - logHeight(), 0, - 1, 0, - lastLine() - 1, lastCol() - ); - } - int x; - getyx(ui.input, _, x); - pnoutrefresh( - ui.input, - 0, MAX(0, x - lastCol() + 3), - lastLine(), 0, - lastLine(), lastCol() - ); - doupdate(); -} - -static const short Colors[] = { - [IRCWhite] = 8 + COLOR_WHITE, - [IRCBlack] = 0 + COLOR_BLACK, - [IRCBlue] = 0 + COLOR_BLUE, - [IRCGreen] = 0 + COLOR_GREEN, - [IRCRed] = 8 + COLOR_RED, - [IRCBrown] = 0 + COLOR_RED, - [IRCMagenta] = 0 + COLOR_MAGENTA, - [IRCOrange] = 0 + COLOR_YELLOW, - [IRCYellow] = 8 + COLOR_YELLOW, - [IRCLightGreen] = 8 + COLOR_GREEN, - [IRCCyan] = 0 + COLOR_CYAN, - [IRCLightCyan] = 8 + COLOR_CYAN, - [IRCLightBlue] = 8 + COLOR_BLUE, - [IRCPink] = 8 + COLOR_MAGENTA, - [IRCGray] = 8 + COLOR_BLACK, - [IRCLightGray] = 0 + COLOR_WHITE, -}; - -static void addFormat(WINDOW *win, const struct Format *format) { - attr_t attr = A_NORMAL; - if (format->bold) attr |= A_BOLD; - if (format->italic) attr |= A_ITALIC; - if (format->underline) attr |= A_UNDERLINE; - if (format->reverse) attr |= A_REVERSE; - - short color = -1; - if (format->fg != IRCDefault) color = Colors[format->fg]; - if (format->bg != IRCDefault) color |= Colors[format->bg] << 4; - - wattr_set(win, attr | colorAttr(color), colorPair(color), NULL); - waddnwstr(win, format->str, format->len); -} - -static int printWidth(const wchar_t *str, size_t len) { - int width = 0; - for (size_t i = 0; i < len; ++i) { - if (iswprint(str[i])) width += wcwidth(str[i]); - } - return width; -} - -static int addWrap(WINDOW *win, const wchar_t *str) { - int lines = 0; - struct Format format = { .str = str }; - formatReset(&format); - - while (formatParse(&format, NULL)) { - size_t word = 1 + wcscspn(&format.str[1], L" "); - if (word < format.len) format.len = word; - - int x, xMax; - getyx(win, _, x); - getmaxyx(win, _, xMax); - if (xMax - x - 1 < printWidth(format.str, word)) { - if (format.str[0] == L' ') { - format.str++; - format.len--; - } - waddch(win, '\n'); - lines++; - } - addFormat(win, &format); - } - return lines; -} - -static void title(const struct Window *win) { - int unread; - char *str; - int len = asprintf(&str, "%s%n (%u)", win->tag.name, &unread, win->unread); - if (len < 0) err(EX_OSERR, "asprintf"); - if (!win->unread) str[unread] = '\0'; - termTitle(str); - free(str); -} - -static void uiStatus(void) { - wmove(ui.status, 0, 0); - int num = 0; - for (const struct Window *win = windows.head; win; win = win->next, ++num) { - if (!win->unread && windows.active != win) continue; - if (windows.active == win) title(win); - int unread; - wchar_t *str; - int len = aswprintf( - &str, L"%c\3%d %d %s %n(\3%02d%u\3%d) ", - (windows.active == win ? IRCReverse : IRCReset), colorFor(win->tag), - num, win->tag.name, - &unread, (win->hot ? IRCWhite : colorFor(win->tag)), win->unread, - colorFor(win->tag) - ); - if (len < 0) err(EX_OSERR, "aswprintf"); - if (!win->unread) str[unread] = L'\0'; - addWrap(ui.status, str); - free(str); - } - wclrtoeol(ui.status); -} - -static void uiShowWindow(struct Window *win) { - windowShow(win); - uiStatus(); - uiPrompt(false); -} - -void uiShowTag(struct Tag tag) { - uiShowWindow(windowFor(tag)); -} - -static void uiShowAuto(void) { - struct Window *unread = NULL; - struct Window *hot; - for (hot = windows.head; hot; hot = hot->next) { - if (hot->hot) break; - if (!unread && hot->unread) unread = hot; - } - if (!hot && !unread) return; - uiShowWindow(hot ? hot : unread); -} - -void uiShowNum(int num, bool relative) { - struct Window *win = (relative ? windows.active : windows.head); - if (num < 0) { - for (; win; win = win->prev) if (!num++) break; - } else { - for (; win; win = win->next) if (!num--) break; - } - if (win) uiShowWindow(win); -} - -void uiMoveTag(struct Tag tag, int num, bool relative) { - struct Window *win = windowFor(tag); - windowRemove(win); - struct Window *ins = (relative ? win : windows.head); - if (num < 0) { - for (; ins; ins = ins->prev) if (!num++) break; - } else { - if (relative) ins = ins->next; - for (; ins; ins = ins->next) if (!num--) break; - } - ins ? windowInsert(win, ins) : windowAppend(win); - uiStatus(); -} - -void uiCloseTag(struct Tag tag) { - windowClose(windowFor(tag)); - uiStatus(); - uiPrompt(false); -} - -static void notify(struct Tag tag, const wchar_t *str) { - beep(); - if (!self.notify) return; - - size_t len = 0; - char buf[256]; - struct Format format = { .str = str }; - formatReset(&format); - while (formatParse(&format, NULL)) { - int n = snprintf( - &buf[len], sizeof(buf) - len, - "%.*ls", (int)format.len, format.str - ); - if (n < 0) err(EX_OSERR, "snprintf"); - len += n; - if (len >= sizeof(buf)) break; - } - eventPipe((const char *[]) { "notify-send", tag.name, buf, NULL }); -} - -void uiLog(struct Tag tag, enum UIHeat heat, const wchar_t *str) { - struct Window *win = windowFor(tag); - int lines = 1; - waddch(win->log, '\n'); - if (win->mark && heat > UICold) { - if (!win->unread++) { - lines++; - waddch(win->log, '\n'); - } - if (heat > UIWarm) { - win->hot = true; - notify(tag, str); - } - uiStatus(); - } - lines += addWrap(win->log, str); - if (win->scroll != LogLines) win->scroll -= lines; -} - -void uiFmt(struct Tag tag, enum UIHeat heat, const wchar_t *format, ...) { - wchar_t *str; - va_list ap; - va_start(ap, format); - vaswprintf(&str, format, ap); - va_end(ap); - if (!str) err(EX_OSERR, "vaswprintf"); - uiLog(tag, heat, str); - free(str); -} - -static void keyCode(wchar_t code) { - if (code == KEY_RESIZE) uiResize(); - struct Window *win = windows.active; - if (!win) return; - switch (code) { - break; case KEY_UP: windowScroll(win, -1); - break; case KEY_DOWN: windowScroll(win, +1); - break; case KEY_PPAGE: windowScroll(win, -(logHeight() - 1)); - break; case KEY_NPAGE: windowScroll(win, +(logHeight() - 1)); - break; case KEY_LEFT: edit(win->tag, EditLeft, 0); - break; case KEY_RIGHT: edit(win->tag, EditRight, 0); - break; case KEY_HOME: edit(win->tag, EditHome, 0); - break; case KEY_END: edit(win->tag, EditEnd, 0); - break; case KEY_DC: edit(win->tag, EditDelete, 0); - break; case KEY_BACKSPACE: edit(win->tag, EditBackspace, 0); - break; case KEY_ENTER: edit(win->tag, EditEnter, 0); - break; default: return; - } - uiStatus(); -} - -static void keyMeta(wchar_t ch) { - struct Window *win = windows.active; - if (ch >= L'0' && ch <= L'9') uiShowNum(ch - L'0', false); - if (ch == L'a') uiShowAuto(); - if (ch == L'/' && windows.other) uiShowWindow(windows.other); - if (!win) return; - switch (ch) { - break; case L'b': edit(win->tag, EditBackWord, 0); - break; case L'f': edit(win->tag, EditForeWord, 0); - break; case L'\b': edit(win->tag, EditKillBackWord, 0); - break; case L'd': edit(win->tag, EditKillForeWord, 0); - break; case L'l': uiHide(); logList(win->tag); - break; case L'm': uiLog(win->tag, UICold, L""); - } -} - -static void keyChar(wchar_t ch) { - struct Window *win = windows.active; - if (ch == CTRL(L'L')) clearok(curscr, true); - if (!win) return; - switch (ch) { - break; case CTRL(L'N'): uiShowNum(+1, true); - break; case CTRL(L'P'): uiShowNum(-1, true); - - break; case CTRL(L'A'): edit(win->tag, EditHome, 0); - break; case CTRL(L'B'): edit(win->tag, EditLeft, 0); - break; case CTRL(L'D'): edit(win->tag, EditDelete, 0); - break; case CTRL(L'E'): edit(win->tag, EditEnd, 0); - break; case CTRL(L'F'): edit(win->tag, EditRight, 0); - break; case CTRL(L'K'): edit(win->tag, EditKillEnd, 0); - break; case CTRL(L'U'): edit(win->tag, EditKill, 0); - break; case CTRL(L'W'): edit(win->tag, EditKillBackWord, 0); - - break; case CTRL(L'C'): edit(win->tag, EditInsert, IRCColor); - break; case CTRL(L'O'): edit(win->tag, EditInsert, IRCBold); - break; case CTRL(L'R'): edit(win->tag, EditInsert, IRCColor); - break; case CTRL(L'S'): edit(win->tag, EditInsert, IRCReset); - break; case CTRL(L'T'): edit(win->tag, EditInsert, IRCItalic); - break; case CTRL(L'V'): edit(win->tag, EditInsert, IRCReverse); - break; case CTRL(L'_'): edit(win->tag, EditInsert, IRCUnderline); - - break; case L'\b': edit(win->tag, EditBackspace, 0); - break; case L'\t': edit(win->tag, EditComplete, 0); - break; case L'\n': edit(win->tag, EditEnter, 0); - - break; default: if (iswprint(ch)) edit(win->tag, EditInsert, ch); - } -} - -void uiRead(void) { - if (ui.hide) uiShow(); - static bool meta; - int ret; - wint_t ch; - enum TermEvent event; - while (ERR != (ret = wget_wch(ui.input, &ch))) { - if (ret == KEY_CODE_YES) { - keyCode(ch); - } else if (ch < 0200 && (event = termEvent((char)ch))) { - struct Window *win = windows.active; - switch (event) { - break; case TermFocusIn: if (win) windowUnmark(win); - break; case TermFocusOut: if (win) windowMark(win); - break; default: {} - } - uiStatus(); - } else if (ch == Esc) { - meta = true; - continue; - } else if (meta) { - keyMeta(ch == Del ? '\b' : ch); - } else { - keyChar(ch == Del ? '\b' : ch); - } - meta = false; - } - uiPrompt(false); -} - -static bool isAction(struct Tag tag, const wchar_t *input) { - if (tag.id == TagStatus.id || tag.id == TagRaw.id) return false; - return !wcsncasecmp(input, L"/me ", 4); -} - -static bool isCommand(struct Tag tag, const wchar_t *input) { - if (tag.id == TagStatus.id || tag.id == TagRaw.id) return true; - if (input[0] != L'/') return false; - const wchar_t *space = wcschr(&input[1], L' '); - const wchar_t *extra = wcschr(&input[1], L'/'); - return !extra || (space && extra > space); -} - -void uiPrompt(bool nickChanged) { - static wchar_t *promptMesg; - static wchar_t *promptAction; - if (nickChanged || !promptMesg || !promptAction) { - free(promptMesg); - free(promptAction); - enum IRCColor color = colorGen(self.user); - int len = aswprintf(&promptMesg, L"\3%d<%s>\3 ", color, self.nick); - if (len < 0) err(EX_OSERR, "aswprintf"); - len = aswprintf(&promptAction, L"\3%d* %s\3 ", color, self.nick); - if (len < 0) err(EX_OSERR, "aswprintf"); - } - - const wchar_t *input = editHead(); - - wmove(ui.input, 0, 0); - if (windows.active) { - if (isAction(windows.active->tag, input) && editTail() >= &input[4]) { - input = &input[4]; - addWrap(ui.input, promptAction); - } else if (!isCommand(windows.active->tag, input)) { - addWrap(ui.input, promptMesg); - } - } - - int x = 0; - struct Format format = { .str = input }; - formatReset(&format); - while (formatParse(&format, editTail())) { - if (format.split) getyx(ui.input, _, x); - addFormat(ui.input, &format); - } - wclrtoeol(ui.input); - wmove(ui.input, 0, x); -} diff --git a/url.c b/url.c deleted file mode 100644 index 21d93e6..0000000 --- a/url.c +++ /dev/null @@ -1,111 +0,0 @@ -/* Copyright (C) 2018 C. 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 <assert.h> -#include <err.h> -#include <stdlib.h> -#include <string.h> -#include <sysexits.h> -#include <unistd.h> - -#include "chat.h" - -static const char *Schemes[] = { - "cvs:", - "ftp:", - "git:", - "http:", - "https:", - "irc:", - "ircs:", - "magnet:", - "sftp:", - "ssh:", - "svn:", - "telnet:", - "vnc:", -}; -static const size_t SchemesLen = sizeof(Schemes) / sizeof(Schemes[0]); - -struct Entry { - size_t tag; - char *url; -}; - -enum { RingLen = 32 }; -static_assert(!(RingLen & (RingLen - 1)), "power of two RingLen"); - -static struct { - struct Entry buf[RingLen]; - size_t end; -} ring; - -static void ringPush(struct Tag tag, const char *url, size_t len) { - free(ring.buf[ring.end].url); - ring.buf[ring.end].tag = tag.id; - ring.buf[ring.end].url = strndup(url, len); - if (!ring.buf[ring.end].url) err(EX_OSERR, "strndup"); - ring.end = (ring.end + 1) & (RingLen - 1); -} - -static struct Entry ringEntry(size_t i) { - return ring.buf[(ring.end + i) & (RingLen - 1)]; -} - -void urlScan(struct Tag tag, const char *str) { - while (str[0]) { - size_t len = 1; - for (size_t i = 0; i < SchemesLen; ++i) { - if (strncmp(str, Schemes[i], strlen(Schemes[i]))) continue; - len = strcspn(str, " >\""); - ringPush(tag, str, len); - } - str = &str[len]; - } -} - -void urlList(struct Tag tag) { - uiHide(); - for (size_t i = 0; i < RingLen; ++i) { - struct Entry entry = ringEntry(i); - if (!entry.url || entry.tag != tag.id) continue; - printf("%s\n", entry.url); - } -} - -void urlOpenMatch(struct Tag tag, const char *substr) { - for (size_t i = RingLen - 1; i < RingLen; --i) { - struct Entry entry = ringEntry(i); - if (!entry.url || entry.tag != tag.id) continue; - if (!strstr(entry.url, substr)) continue; - eventPipe((const char *[]) { "open", entry.url, NULL }); - break; - } -} - -void urlOpenRange(struct Tag tag, size_t at, size_t to) { - size_t argc = 1; - const char *argv[2 + RingLen] = { "open" }; - size_t tagIndex = 0; - for (size_t i = RingLen - 1; i < RingLen; --i) { - struct Entry entry = ringEntry(i); - if (!entry.url || entry.tag != tag.id) continue; - if (tagIndex >= at && tagIndex < to) argv[argc++] = entry.url; - tagIndex++; - } - argv[argc] = NULL; - if (argc > 1) eventPipe(argv); -} |