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.
- 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.
-
-
- Copyright (C)
-
- 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 .
-
-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
-.
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
-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
-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
-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
- *
- * 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 .
- */
-
-#define _WITH_GETLINE
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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
- *
- * 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 .
- */
-
-#define SOURCE_URL "https://git.causal.agency/catgirl"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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
- *
- * 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 .
- */
-
-#include
-
-#include "chat.h"
-
-// Adapted from .
-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
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-
-#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
-
-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
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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 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
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-#include
-
-#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 :
-//
-// 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
-#include
-
-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
- *
- * 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 .
- */
-
-#import
-#import
-#import
-#import
-#import
-#import
-
-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
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-
-#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
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-
-#include "chat.h"
-
-static struct {
- char *name[TagsLen];
- size_t len;
-} tags = {
- .name = { "", "", "" },
- .len = 3,
-};
-
-const struct Tag TagNone = { 0, "" };
-const struct Tag TagStatus = { 1, "" };
-const struct Tag TagRaw = { 2, "" };
-
-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
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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
-
-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
- *
- * 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 .
- */
-
-#define _XOPEN_SOURCE_EXTENDED
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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);
-}