Remove legacy code
parent
34ef6ee5a7
commit
15ff2a470e
|
@ -1,8 +0,0 @@
|
|||
*.o
|
||||
*.t
|
||||
catgirl
|
||||
chroot.tar
|
||||
config.mk
|
||||
root
|
||||
sandman
|
||||
tags
|
|
@ -1,4 +0,0 @@
|
|||
LIBRESSL_PREFIX = /usr/local/opt/libressl
|
||||
LDLIBS = -lcurses -ltls -framework Cocoa
|
||||
BINS += sandman
|
||||
MANS += sandman.1
|
661
LICENSE
661
LICENSE
|
@ -1,661 +0,0 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
6
Linux.mk
6
Linux.mk
|
@ -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
|
99
Makefile
99
Makefile
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||
LIBRESSL_PREFIX = /usr/pkg/libressl
|
||||
LDFLAGS += -rpath=${LIBRESSL_PREFIX}/lib
|
||||
LDLIBS = -lcurses -ltls
|
111
README.7
111
README.7
|
@ -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
|
413
catgirl.1
413
catgirl.1
|
@ -1,413 +0,0 @@
|
|||
.Dd October 3, 2019
|
||||
.Dt CATGIRL 1
|
||||
.Os
|
||||
.
|
||||
.Sh NAME
|
||||
.Nm catgirl
|
||||
.Nd IRC client
|
||||
.
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl NPRv
|
||||
.Op Fl a Ar auth
|
||||
.Op Fl h Ar host
|
||||
.Op Fl j Ar chan
|
||||
.Op Fl k Ar keys
|
||||
.Op Fl l Ar path
|
||||
.Op Fl n Ar nick
|
||||
.Op Fl p Ar port
|
||||
.Op Fl r Ar real
|
||||
.Op Fl u Ar user
|
||||
.Op Fl w Ar pass
|
||||
.
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a curses, TLS-only IRC client.
|
||||
.
|
||||
.Pp
|
||||
The arguments are as follows:
|
||||
.
|
||||
.Bl -tag -width "-w pass"
|
||||
.It Fl N
|
||||
Send notifications with
|
||||
.Xr notify-send 1 .
|
||||
.
|
||||
.It Fl P
|
||||
Prompt for nickname.
|
||||
.
|
||||
.It Fl R
|
||||
Restrict the use of the
|
||||
.Ic /join ,
|
||||
.Ic /query ,
|
||||
.Ic /quote ,
|
||||
.Ic /raw
|
||||
commands.
|
||||
.
|
||||
.It Fl a Ar auth
|
||||
Authenticate with SASL PLAIN.
|
||||
.Ar auth
|
||||
is a colon-separated
|
||||
username and password pair.
|
||||
.
|
||||
.It Fl h Ar host
|
||||
Connect to
|
||||
.Ar host .
|
||||
.
|
||||
.It Fl j Ar chan
|
||||
Join
|
||||
.Ar chan
|
||||
after connecting.
|
||||
.Ar chan
|
||||
may be a comma-separated list.
|
||||
.
|
||||
.It Fl k Ar keys
|
||||
Set keys for channels in
|
||||
.Fl j .
|
||||
.Ar keys
|
||||
may be a comma-separated list.
|
||||
.
|
||||
.It Fl l Ar path
|
||||
Log messages to
|
||||
subdirectories of
|
||||
.Ar path
|
||||
named by channel or nick
|
||||
in files named by date.
|
||||
.
|
||||
.It Fl n Ar nick
|
||||
Set nickname to
|
||||
.Ar nick .
|
||||
The default nickname
|
||||
is the user's name.
|
||||
.
|
||||
.It Fl p Ar port
|
||||
Connect to
|
||||
.Ar port .
|
||||
The default port is 6697.
|
||||
.
|
||||
.It Fl r Ar real
|
||||
Set realname to
|
||||
.Ar real .
|
||||
The default realname is
|
||||
the same as the nickname.
|
||||
.
|
||||
.It Fl u Ar user
|
||||
Set username to
|
||||
.Ar user .
|
||||
The default username is
|
||||
the same as the nickname.
|
||||
.
|
||||
.It Fl v
|
||||
Show raw IRC protocol in the
|
||||
.Sy <raw>
|
||||
window.
|
||||
If standard error is not a terminal,
|
||||
output raw IRC protocol
|
||||
to standard error.
|
||||
.
|
||||
.It Fl w Ar pass
|
||||
Log in with
|
||||
.Ar pass .
|
||||
.El
|
||||
.
|
||||
.Sh COMMANDS
|
||||
Any unique prefix
|
||||
may be used to abbreviate a command.
|
||||
.
|
||||
.Ss Chat Commands
|
||||
.Bl -tag -width Ds
|
||||
.It Ic /join Ar chan Op Ar key
|
||||
Join a channel.
|
||||
.
|
||||
.It Ic /list Op Ar chan
|
||||
List channels.
|
||||
.
|
||||
.It Ic /me Op Ar action
|
||||
Send an action message.
|
||||
.
|
||||
.It Ic /names , /who
|
||||
List users in the current channel.
|
||||
.
|
||||
.It Ic /nick Ar nick
|
||||
Change nicknames.
|
||||
.
|
||||
.It Ic /part Op Ar message
|
||||
Leave the current channel.
|
||||
.
|
||||
.It Ic /query Ar nick
|
||||
Open a private message view.
|
||||
.
|
||||
.It Ic /quit Op Ar message
|
||||
Quit IRC.
|
||||
.
|
||||
.It Ic /quote Ar command
|
||||
Send a raw IRC command.
|
||||
.
|
||||
.It Ic /topic Op Ar topic
|
||||
Show or set the topic of the current channel.
|
||||
.
|
||||
.It Ic /whois Ar nick
|
||||
Query information about a user.
|
||||
.
|
||||
.It Ic /znc Ar command
|
||||
Send
|
||||
.Xr znc 1
|
||||
command.
|
||||
.El
|
||||
.
|
||||
.Pp
|
||||
Any messages entered in the
|
||||
.Sy <raw>
|
||||
window will be sent as raw IRC commands.
|
||||
.
|
||||
.Ss UI Commands
|
||||
.Bl -tag -width Ds
|
||||
.It Ic /close
|
||||
Close the current window.
|
||||
.
|
||||
.It Ic /help , /man
|
||||
View this manual.
|
||||
.
|
||||
.It Ic /move Ar num
|
||||
Move window to number.
|
||||
If
|
||||
.Ar num
|
||||
starts with
|
||||
.Cm +
|
||||
or
|
||||
.Cm - ,
|
||||
the number is relative to the current window.
|
||||
.
|
||||
.It Ic /open Op Ar range
|
||||
Open a
|
||||
.Ar range
|
||||
of recent URLs
|
||||
in the current window with
|
||||
.Xr open 1 .
|
||||
URLs are numbered
|
||||
from the most recent
|
||||
starting at 1.
|
||||
The
|
||||
.Ar range
|
||||
may be a single number,
|
||||
or a hyphen- or comma-separated range.
|
||||
.
|
||||
.It Ic /open Ar substring
|
||||
Open the most recent URL
|
||||
in the current window
|
||||
matching the
|
||||
.Ar substring .
|
||||
.
|
||||
.It Ic /raw
|
||||
Toggle the
|
||||
.Sy <raw>
|
||||
window.
|
||||
.
|
||||
.It Ic /url
|
||||
Hide the UI
|
||||
and list the most recent URLs
|
||||
in the current window.
|
||||
Press
|
||||
.Ic Enter
|
||||
to resume the UI.
|
||||
.
|
||||
.It Ic /window Ar name
|
||||
Switch to window by name.
|
||||
.
|
||||
.It Ic /window Ar num , Ic / Ns Ar num
|
||||
Switch to window by number.
|
||||
If
|
||||
.Ar num
|
||||
starts with
|
||||
.Cm +
|
||||
or
|
||||
.Cm - ,
|
||||
the number is relative to the current window.
|
||||
.El
|
||||
.
|
||||
.Sh KEY BINDINGS
|
||||
.Nm
|
||||
provides
|
||||
.Xr emacs 1 Ns -like
|
||||
line editing keys
|
||||
as well as keys for applying IRC formatting.
|
||||
The prefixes
|
||||
.Ic C- , M- , S-
|
||||
represent the control, meta (alt) and shift modifiers,
|
||||
respectively.
|
||||
.Ic M- Ns Ar x
|
||||
sequences can also be typed as
|
||||
.Ic Esc
|
||||
followed by
|
||||
.Ar x .
|
||||
.
|
||||
.Ss Line Editing
|
||||
.Bl -tag -width Ds -compact
|
||||
.It Ic C-a
|
||||
Move cursor to beginning of line.
|
||||
.It Ic C-b
|
||||
Move cursor left.
|
||||
.It Ic C-d
|
||||
Delete character under cursor.
|
||||
.It Ic C-e
|
||||
Move cursor to end of line.
|
||||
.It Ic C-f
|
||||
Move cursor right.
|
||||
.It Ic C-k
|
||||
Delete line after cursor.
|
||||
.It Ic C-u
|
||||
Delete line.
|
||||
.It Ic C-w
|
||||
Delete word before cursor.
|
||||
.It Ic M-b
|
||||
Move cursor to beginning of word.
|
||||
.It Ic M-d
|
||||
Delete word after cursor.
|
||||
.It Ic M-f
|
||||
Move cursor to end of word.
|
||||
.It Ic Tab
|
||||
Cycle through completions for
|
||||
commands, nicks and channels.
|
||||
.El
|
||||
.
|
||||
.Ss IRC Formatting
|
||||
.Bl -tag -width Ds -compact
|
||||
.It Ic C-_
|
||||
Toggle underline.
|
||||
.It Ic C-o
|
||||
Toggle bold.
|
||||
.It Ic C-r
|
||||
Set or reset color.
|
||||
.It Ic C-s
|
||||
Reset formatting.
|
||||
.It Ic C-t
|
||||
Toggle italics.
|
||||
.It Ic C-v
|
||||
Toggle reverse video.
|
||||
This must usually be typed as
|
||||
.Ic C-v C-v .
|
||||
.El
|
||||
.
|
||||
.Pp
|
||||
To reset color, follow
|
||||
.Ic C-r
|
||||
by a non-digit.
|
||||
To set colors, follow
|
||||
.Ic C-r
|
||||
by one or two digits
|
||||
to set the foreground color,
|
||||
optionally followed by a comma
|
||||
and one or two digits
|
||||
to set the background color.
|
||||
.
|
||||
.Pp
|
||||
The color numbers are as follows:
|
||||
.Pp
|
||||
.Bl -column "7" "orange (dark yellow)" "15" "pink (light magenta)"
|
||||
.It 0 Ta white Ta \ 8 Ta yellow
|
||||
.It 1 Ta black Ta \ 9 Ta light green
|
||||
.It 2 Ta blue Ta 10 Ta cyan
|
||||
.It 3 Ta green Ta 11 Ta light cyan
|
||||
.It 4 Ta red Ta 12 Ta light blue
|
||||
.It 5 Ta brown (dark red) Ta 13 Ta pink (light magenta)
|
||||
.It 6 Ta magenta Ta 14 Ta gray
|
||||
.It 7 Ta orange (dark yellow) Ta 15 Ta light gray
|
||||
.El
|
||||
.
|
||||
.Ss Window Keys
|
||||
.Bl -tag -width "PageDown" -compact
|
||||
.It Ic C-l
|
||||
Redraw the UI.
|
||||
.It Ic C-n
|
||||
Switch to the next window.
|
||||
.It Ic C-p
|
||||
Switch to the previous window.
|
||||
.It Ic M-/
|
||||
Switch to the previously active window.
|
||||
.It Ic M-a
|
||||
Switch to next hot or unread window.
|
||||
.It Ic M-l
|
||||
Hide the UI and list the log for the current window.
|
||||
.It Ic M-m
|
||||
Insert a blank line in the window.
|
||||
.It Ic M- Ns Ar n
|
||||
Switch to window by number 0\(en9.
|
||||
.It Ic Down
|
||||
Scroll window down by one line.
|
||||
.It Ic PageDown
|
||||
Scroll window down by one page.
|
||||
.It Ic PageUp
|
||||
Scroll window up by one page.
|
||||
.It Ic Up
|
||||
Scroll window up by one line.
|
||||
.El
|
||||
.
|
||||
.Sh ENVIRONMENT
|
||||
.Bl -tag -width Ds
|
||||
.It Ev USER
|
||||
The default nickname.
|
||||
.El
|
||||
.
|
||||
.Sh EXAMPLES
|
||||
.Dl catgirl -h chat.freenode.net -j '#ascii.town'
|
||||
.
|
||||
.Sh STANDARDS
|
||||
.Nm
|
||||
is a partial implementation of the following:
|
||||
.
|
||||
.Bl -item
|
||||
.It
|
||||
.Rs
|
||||
.%A C. Kalt
|
||||
.%T Internet Relay Chat: Client Protocol
|
||||
.%I IETF
|
||||
.%N RFC 2812
|
||||
.%D April 2000
|
||||
.%U https://tools.ietf.org/html/rfc2812
|
||||
.Re
|
||||
.
|
||||
.It
|
||||
.Rs
|
||||
.%A Kevin L. Mitchell
|
||||
.%A Perry Lorier
|
||||
.%A Lee Hardy
|
||||
.%A William Pitcock
|
||||
.%T IRCv3.1 Client Capability Negotiation
|
||||
.%I IRCv3 Working Group
|
||||
.%U https://ircv3.net/specs/core/capability-negotiation-3.1.html
|
||||
.Re
|
||||
.
|
||||
.It
|
||||
.Rs
|
||||
.%A Jilles Tjoelker
|
||||
.%A William Pitcock
|
||||
.%T IRCv3.1 SASL Authentication
|
||||
.%I IRCv3 Working Group
|
||||
.%U https://ircv3.net/specs/extensions/sasl-3.1.html
|
||||
.Re
|
||||
.
|
||||
.It
|
||||
.Rs
|
||||
.%A K. Zeilenga, Ed.
|
||||
.%Q OpenLDAP Foundation
|
||||
.%T The PLAIN Simple Authentication and Security Layer (SASL) Mechanism
|
||||
.%I IETF
|
||||
.%N RFC 4616
|
||||
.%D August 2006
|
||||
.%U https://tools.ietf.org/html/rfc4616
|
||||
.Re
|
||||
.
|
||||
.It
|
||||
.Rs
|
||||
.%A S. Josefsson
|
||||
.%Q SJD
|
||||
.%T The Base16, Base32, and Base64 Data Encodings
|
||||
.%I IETF
|
||||
.%N RFC 4648
|
||||
.%D October 2006
|
||||
.%U https://tools.ietf.org/html/rfc4648
|
||||
.Re
|
||||
.El
|
||||
.
|
||||
.Sh CAVEATS
|
||||
.Nm
|
||||
does not support unencrypted connections.
|
91
chat.c
91
chat.c
|
@ -1,91 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _WITH_GETLINE
|
||||
|
||||
#include <err.h>
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sysexits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
static char *dupe(const char *str) {
|
||||
char *dup = strdup(str);
|
||||
if (!dup) err(EX_OSERR, "strdup");
|
||||
return dup;
|
||||
}
|
||||
|
||||
static char *prompt(const char *prompt) {
|
||||
char *line = NULL;
|
||||
size_t cap;
|
||||
for (;;) {
|
||||
printf("%s", prompt);
|
||||
fflush(stdout);
|
||||
|
||||
ssize_t len = getline(&line, &cap, stdin);
|
||||
if (ferror(stdin)) err(EX_IOERR, "getline");
|
||||
if (feof(stdin)) exit(EX_OK);
|
||||
if (len < 2) continue;
|
||||
|
||||
line[len - 1] = '\0';
|
||||
return line;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
setlocale(LC_CTYPE, "");
|
||||
|
||||
int opt;
|
||||
while (0 < (opt = getopt(argc, argv, "!NPRa:h:j:k:l:n:p:r:u:vw:"))) {
|
||||
switch (opt) {
|
||||
break; case '!': self.insecure = true;
|
||||
break; case 'N': self.notify = true;
|
||||
break; case 'P': self.nick = prompt("Name: ");
|
||||
break; case 'R': self.limit = true;
|
||||
break; case 'a': self.auth = dupe(optarg);
|
||||
break; case 'h': self.host = dupe(optarg);
|
||||
break; case 'j': self.join = dupe(optarg);
|
||||
break; case 'k': self.keys = dupe(optarg);
|
||||
break; case 'l': logOpen(optarg);
|
||||
break; case 'n': self.nick = dupe(optarg);
|
||||
break; case 'p': self.port = dupe(optarg);
|
||||
break; case 'r': self.real = dupe(optarg);
|
||||
break; case 'u': self.user = dupe(optarg);
|
||||
break; case 'v': self.raw = true;
|
||||
break; case 'w': self.pass = dupe(optarg);
|
||||
break; default: return EX_USAGE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.nick) {
|
||||
const char *user = getenv("USER");
|
||||
if (!user) errx(EX_USAGE, "USER unset");
|
||||
self.nick = dupe(user);
|
||||
}
|
||||
|
||||
if (!self.host) self.host = prompt("Host: ");
|
||||
if (!self.port) self.port = dupe("6697");
|
||||
if (!self.user) self.user = dupe(self.nick);
|
||||
if (!self.real) self.real = dupe(self.nick);
|
||||
|
||||
inputTab();
|
||||
uiInit();
|
||||
eventLoop();
|
||||
}
|
221
chat.h
221
chat.h
|
@ -1,221 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define SOURCE_URL "https://git.causal.agency/catgirl"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdnoreturn.h>
|
||||
#include <time.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
|
||||
#define err(...) do { uiHide(); err(__VA_ARGS__); } while (0)
|
||||
#define errx(...) do { uiHide(); errx(__VA_ARGS__); } while (0)
|
||||
|
||||
typedef unsigned uint;
|
||||
typedef unsigned char byte;
|
||||
|
||||
struct {
|
||||
bool insecure;
|
||||
char *host;
|
||||
char *port;
|
||||
char *auth;
|
||||
char *pass;
|
||||
char *nick;
|
||||
char *user;
|
||||
char *real;
|
||||
char *join;
|
||||
char *keys;
|
||||
bool limit;
|
||||
bool raw;
|
||||
bool notify;
|
||||
bool quit;
|
||||
} self;
|
||||
|
||||
void eventWait(const char *argv[static 2]);
|
||||
void eventPipe(const char *argv[static 2]);
|
||||
noreturn void eventLoop(void);
|
||||
|
||||
struct Tag {
|
||||
size_t id;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
enum { TagsLen = 256 };
|
||||
const struct Tag TagNone;
|
||||
const struct Tag TagStatus;
|
||||
const struct Tag TagRaw;
|
||||
struct Tag tagFind(const char *name);
|
||||
struct Tag tagFor(const char *name);
|
||||
|
||||
enum IRCColor {
|
||||
IRCWhite,
|
||||
IRCBlack,
|
||||
IRCBlue,
|
||||
IRCGreen,
|
||||
IRCRed,
|
||||
IRCBrown,
|
||||
IRCMagenta,
|
||||
IRCOrange,
|
||||
IRCYellow,
|
||||
IRCLightGreen,
|
||||
IRCCyan,
|
||||
IRCLightCyan,
|
||||
IRCLightBlue,
|
||||
IRCPink,
|
||||
IRCGray,
|
||||
IRCLightGray,
|
||||
IRCDefault = 99,
|
||||
};
|
||||
enum {
|
||||
IRCBold = 002,
|
||||
IRCColor = 003,
|
||||
IRCReverse = 026,
|
||||
IRCReset = 017,
|
||||
IRCItalic = 035,
|
||||
IRCUnderline = 037,
|
||||
};
|
||||
|
||||
struct Format {
|
||||
const wchar_t *str;
|
||||
size_t len;
|
||||
bool split;
|
||||
bool bold, italic, underline, reverse;
|
||||
enum IRCColor fg, bg;
|
||||
};
|
||||
void formatReset(struct Format *format);
|
||||
bool formatParse(struct Format *format, const wchar_t *split);
|
||||
|
||||
enum IRCColor colorGen(const char *str);
|
||||
struct Tag colorTag(struct Tag tag, const char *gen);
|
||||
enum IRCColor colorFor(struct Tag tag);
|
||||
|
||||
void handle(char *line);
|
||||
void input(struct Tag tag, char *line);
|
||||
void inputTab(void);
|
||||
|
||||
int ircConnect(void);
|
||||
void ircRead(void);
|
||||
void ircWrite(const char *ptr, size_t len);
|
||||
void ircFmt(const char *format, ...) __attribute__((format(printf, 1, 2)));
|
||||
void ircQuit(const char *mesg);
|
||||
|
||||
void uiInit(void);
|
||||
void uiShow(void);
|
||||
void uiHide(void);
|
||||
void uiDraw(void);
|
||||
void uiRead(void);
|
||||
void uiExit(int status);
|
||||
|
||||
void uiPrompt(bool nickChanged);
|
||||
void uiShowTag(struct Tag tag);
|
||||
void uiShowNum(int num, bool relative);
|
||||
void uiMoveTag(struct Tag tag, int num, bool relative);
|
||||
void uiCloseTag(struct Tag tag);
|
||||
|
||||
enum UIHeat {
|
||||
UICold,
|
||||
UIWarm,
|
||||
UIHot,
|
||||
};
|
||||
void uiLog(struct Tag tag, enum UIHeat heat, const wchar_t *str);
|
||||
void uiFmt(struct Tag tag, enum UIHeat heat, const wchar_t *format, ...);
|
||||
|
||||
enum TermMode {
|
||||
TermFocus,
|
||||
TermPaste,
|
||||
};
|
||||
enum TermEvent {
|
||||
TermNone,
|
||||
TermFocusIn,
|
||||
TermFocusOut,
|
||||
TermPasteStart,
|
||||
TermPasteEnd,
|
||||
};
|
||||
void termInit(void);
|
||||
void termNoFlow(void);
|
||||
void termTitle(const char *title);
|
||||
void termMode(enum TermMode mode, bool set);
|
||||
enum TermEvent termEvent(char ch);
|
||||
|
||||
enum Edit {
|
||||
EditLeft,
|
||||
EditRight,
|
||||
EditHome,
|
||||
EditEnd,
|
||||
EditBackWord,
|
||||
EditForeWord,
|
||||
EditInsert,
|
||||
EditBackspace,
|
||||
EditDelete,
|
||||
EditKill,
|
||||
EditKillBackWord,
|
||||
EditKillForeWord,
|
||||
EditKillEnd,
|
||||
EditComplete,
|
||||
EditEnter,
|
||||
};
|
||||
void edit(struct Tag tag, enum Edit op, wchar_t ch);
|
||||
const wchar_t *editHead(void);
|
||||
const wchar_t *editTail(void);
|
||||
|
||||
void tabTouch(struct Tag tag, const char *word);
|
||||
void tabAdd(struct Tag tag, const char *word);
|
||||
void tabRemove(struct Tag tag, const char *word);
|
||||
void tabReplace(struct Tag tag, const char *prev, const char *next);
|
||||
void tabClear(struct Tag tag);
|
||||
struct Tag tabTag(const char *word);
|
||||
const char *tabNext(struct Tag tag, const char *prefix);
|
||||
void tabAccept(void);
|
||||
void tabReject(void);
|
||||
|
||||
void urlScan(struct Tag tag, const char *str);
|
||||
void urlList(struct Tag tag);
|
||||
void urlOpenMatch(struct Tag tag, const char *substr);
|
||||
void urlOpenRange(struct Tag tag, size_t at, size_t to);
|
||||
|
||||
void logOpen(const char *path);
|
||||
void logFmt(
|
||||
struct Tag tag, const time_t *ts, const char *format, ...
|
||||
) __attribute__((format(printf, 3, 4)));
|
||||
void logList(struct Tag tag);
|
||||
void logReplay(struct Tag tag);
|
||||
|
||||
wchar_t *wcsnchr(const wchar_t *wcs, size_t len, wchar_t chr);
|
||||
wchar_t *wcsnrchr(const wchar_t *wcs, size_t len, wchar_t chr);
|
||||
wchar_t *ambstowcs(const char *src);
|
||||
char *awcstombs(const wchar_t *src);
|
||||
char *awcsntombs(const wchar_t *src, size_t nwc);
|
||||
int vaswprintf(wchar_t **ret, const wchar_t *format, va_list ap);
|
||||
int aswprintf(wchar_t **ret, const wchar_t *format, ...);
|
||||
|
||||
size_t base64Size(size_t len);
|
||||
void base64(char *dst, const byte *src, size_t len);
|
||||
|
||||
// HACK: clang won't check wchar_t *format strings.
|
||||
#ifdef NDEBUG
|
||||
#define uiFmt(tag, heat, format, ...) uiFmt(tag, heat, L##format, __VA_ARGS__)
|
||||
#else
|
||||
#define uiFmt(tag, heat, format, ...) do { \
|
||||
snprintf(NULL, 0, format, __VA_ARGS__); \
|
||||
uiFmt(tag, heat, L##format, __VA_ARGS__); \
|
||||
} while(0)
|
||||
#endif
|
51
color.c
51
color.c
|
@ -1,51 +0,0 @@
|
|||
/* Copyright (C) 2019 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
// Adapted from <https://github.com/cbreeden/fxhash/blob/master/lib.rs>.
|
||||
static uint32_t hashChar(uint32_t hash, char ch) {
|
||||
hash = (hash << 5) | (hash >> 27);
|
||||
hash ^= ch;
|
||||
hash *= 0x27220A95;
|
||||
return hash;
|
||||
}
|
||||
|
||||
enum IRCColor colorGen(const char *str) {
|
||||
if (!str) return IRCDefault;
|
||||
uint32_t hash = 0;
|
||||
if (*str == '~') str++;
|
||||
for (; *str; ++str) {
|
||||
hash = hashChar(hash, *str);
|
||||
}
|
||||
while (IRCBlack == (hash & IRCLightGray)) {
|
||||
hash = hashChar(hash, '\0');
|
||||
}
|
||||
return (hash & IRCLightGray);
|
||||
}
|
||||
|
||||
static enum IRCColor colors[TagsLen];
|
||||
|
||||
struct Tag colorTag(struct Tag tag, const char *gen) {
|
||||
if (!colors[tag.id]) colors[tag.id] = 1 + colorGen(gen);
|
||||
return tag;
|
||||
}
|
||||
|
||||
enum IRCColor colorFor(struct Tag tag) {
|
||||
return colors[tag.id] ? colors[tag.id] - 1 : IRCDefault;
|
||||
}
|
186
edit.c
186
edit.c
|
@ -1,186 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <err.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <sysexits.h>
|
||||
#include <wchar.h>
|
||||
#include <wctype.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
enum { BufLen = 512 };
|
||||
static struct {
|
||||
wchar_t buf[BufLen];
|
||||
wchar_t *ptr;
|
||||
wchar_t *end;
|
||||
wchar_t *tab;
|
||||
} line = {
|
||||
.ptr = line.buf,
|
||||
.end = line.buf,
|
||||
};
|
||||
|
||||
const wchar_t *editHead(void) {
|
||||
return line.buf;
|
||||
}
|
||||
const wchar_t *editTail(void) {
|
||||
return line.ptr;
|
||||
}
|
||||
|
||||
static void left(void) {
|
||||
if (line.ptr > line.buf) line.ptr--;
|
||||
}
|
||||
static void right(void) {
|
||||
if (line.ptr < line.end) line.ptr++;
|
||||
}
|
||||
|
||||
static void backWord(void) {
|
||||
left();
|
||||
wchar_t *word = wcsnrchr(line.buf, line.ptr - line.buf, L' ');
|
||||
line.ptr = (word ? &word[1] : line.buf);
|
||||
}
|
||||
static void foreWord(void) {
|
||||
right();
|
||||
wchar_t *word = wcsnchr(line.ptr, line.end - line.ptr, L' ');
|
||||
line.ptr = (word ? word : line.end);
|
||||
}
|
||||
|
||||
static void insert(wchar_t ch) {
|
||||
if (line.end == &line.buf[BufLen - 1]) return;
|
||||
if (line.ptr != line.end) {
|
||||
wmemmove(line.ptr + 1, line.ptr, line.end - line.ptr);
|
||||
}
|
||||
*line.ptr++ = ch;
|
||||
line.end++;
|
||||
}
|
||||
static void backspace(void) {
|
||||
if (line.ptr == line.buf) return;
|
||||
if (line.ptr != line.end) {
|
||||
wmemmove(line.ptr - 1, line.ptr, line.end - line.ptr);
|
||||
}
|
||||
line.ptr--;
|
||||
line.end--;
|
||||
}
|
||||
static void delete(void) {
|
||||
if (line.ptr == line.end) return;
|
||||
right();
|
||||
backspace();
|
||||
}
|
||||
|
||||
static void killBackWord(void) {
|
||||
wchar_t *from = line.ptr;
|
||||
backWord();
|
||||
wmemmove(line.ptr, from, line.end - from);
|
||||
line.end -= from - line.ptr;
|
||||
}
|
||||
static void killForeWord(void) {
|
||||
wchar_t *from = line.ptr;
|
||||
foreWord();
|
||||
wmemmove(from, line.ptr, line.end - line.ptr);
|
||||
line.end -= line.ptr - from;
|
||||
line.ptr = from;
|
||||
}
|
||||
|
||||
static char *prefix;
|
||||
static void complete(struct Tag tag) {
|
||||
if (!line.tab) {
|
||||
line.tab = wcsnrchr(line.buf, line.ptr - line.buf, L' ');
|
||||
line.tab = (line.tab ? &line.tab[1] : line.buf);
|
||||
prefix = awcsntombs(line.tab, line.ptr - line.tab);
|
||||
if (!prefix) err(EX_DATAERR, "awcstombs");
|
||||
}
|
||||
|
||||
const char *next = tabNext(tag, prefix);
|
||||
if (!next) return;
|
||||
|
||||
wchar_t *wcs = ambstowcs(next);
|
||||
if (!wcs) err(EX_DATAERR, "ambstowcs");
|
||||
|
||||
size_t i = 0;
|
||||
for (; wcs[i] && line.ptr > &line.tab[i]; ++i) {
|
||||
line.tab[i] = wcs[i];
|
||||
}
|
||||
while (line.ptr > &line.tab[i]) {
|
||||
backspace();
|
||||
}
|
||||
for (; wcs[i]; ++i) {
|
||||
insert(wcs[i]);
|
||||
}
|
||||
free(wcs);
|
||||
|
||||
size_t pos = line.tab - line.buf;
|
||||
if (!pos && line.tab[0] != L'/') {
|
||||
insert(L':');
|
||||
} else if (pos >= 2) {
|
||||
if (line.buf[pos - 2] == L':') {
|
||||
line.buf[pos - 2] = L',';
|
||||
insert(L':');
|
||||
}
|
||||
}
|
||||
insert(L' ');
|
||||
}
|
||||
|
||||
static void accept(void) {
|
||||
if (!line.tab) return;
|
||||
line.tab = NULL;
|
||||
free(prefix);
|
||||
tabAccept();
|
||||
}
|
||||
static void reject(void) {
|
||||
if (!line.tab) return;
|
||||
line.tab = NULL;
|
||||
free(prefix);
|
||||
tabReject();
|
||||
}
|
||||
|
||||
static void enter(struct Tag tag) {
|
||||
if (line.end == line.buf) return;
|
||||
*line.end = L'\0';
|
||||
char *str = awcstombs(line.buf);
|
||||
if (!str) err(EX_DATAERR, "awcstombs");
|
||||
input(tag, str);
|
||||
free(str);
|
||||
line.ptr = line.buf;
|
||||
line.end = line.buf;
|
||||
}
|
||||
|
||||
void edit(struct Tag tag, enum Edit op, wchar_t ch) {
|
||||
switch (op) {
|
||||
break; case EditLeft: reject(); left();
|
||||
break; case EditRight: reject(); right();
|
||||
break; case EditHome: reject(); line.ptr = line.buf;
|
||||
break; case EditEnd: reject(); line.ptr = line.end;
|
||||
|
||||
break; case EditBackWord: reject(); backWord();
|
||||
break; case EditForeWord: reject(); foreWord();
|
||||
|
||||
break; case EditInsert: accept(); insert(ch);
|
||||
break; case EditBackspace: reject(); backspace();
|
||||
break; case EditDelete: reject(); delete();
|
||||
|
||||
break; case EditKill: reject(); line.ptr = line.end = line.buf;
|
||||
break; case EditKillBackWord: reject(); killBackWord();
|
||||
break; case EditKillForeWord: reject(); killForeWord();
|
||||
break; case EditKillEnd: reject(); line.end = line.ptr;
|
||||
|
||||
break; case EditComplete: complete(tag);
|
||||
|
||||
break; case EditEnter: accept(); enter(tag);
|
||||
}
|
||||
|
||||
*line.end = L'\0';
|
||||
}
|
168
event.c
168
event.c
|
@ -1,168 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdnoreturn.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sysexits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
static struct {
|
||||
bool wait;
|
||||
int pipe;
|
||||
} child = {
|
||||
.pipe = -1,
|
||||
};
|
||||
|
||||
void eventWait(const char *argv[static 2]) {
|
||||
uiHide();
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) err(EX_OSERR, "fork");
|
||||
if (!pid) {
|
||||
execvp(argv[0], (char *const *)argv);
|
||||
err(EX_CONFIG, "%s", argv[0]);
|
||||
}
|
||||
child.wait = true;
|
||||
}
|
||||
|
||||
static void childWait(void) {
|
||||
uiShow();
|
||||
int status;
|
||||
pid_t pid = wait(&status);
|
||||
if (pid < 0) err(EX_OSERR, "wait");
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status)) {
|
||||
uiFmt(TagStatus, UIHot, "event: exit %d", WEXITSTATUS(status));
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
uiFmt(
|
||||
TagStatus, UIHot,
|
||||
"event: signal %s", strsignal(WTERMSIG(status))
|
||||
);
|
||||
}
|
||||
child.wait = false;
|
||||
}
|
||||
|
||||
void eventPipe(const char *argv[static 2]) {
|
||||
if (child.pipe > 0) {
|
||||
uiLog(TagStatus, UIHot, L"event: existing pipe");
|
||||
return;
|
||||
}
|
||||
|
||||
int rw[2];
|
||||
int error = pipe(rw);
|
||||
if (error) err(EX_OSERR, "pipe");
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) err(EX_OSERR, "fork");
|
||||
if (!pid) {
|
||||
close(rw[0]);
|
||||
close(STDIN_FILENO);
|
||||
dup2(rw[1], STDOUT_FILENO);
|
||||
dup2(rw[1], STDERR_FILENO);
|
||||
close(rw[1]);
|
||||
execvp(argv[0], (char *const *)argv);
|
||||
perror(argv[0]);
|
||||
exit(EX_CONFIG);
|
||||
}
|
||||
|
||||
close(rw[1]);
|
||||
child.pipe = rw[0];
|
||||
}
|
||||
|
||||
static void childRead(void) {
|
||||
char buf[256];
|
||||
ssize_t len = read(child.pipe, buf, sizeof(buf) - 1);
|
||||
if (len < 0) err(EX_IOERR, "read");
|
||||
if (len) {
|
||||
buf[len] = '\0';
|
||||
buf[strcspn(buf, "\n")] = '\0';
|
||||
uiFmt(TagStatus, UIHot, "event: %s", buf);
|
||||
} else {
|
||||
close(child.pipe);
|
||||
child.pipe = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static volatile sig_atomic_t sig[NSIG];
|
||||
static void handler(int n) {
|
||||
sig[n] = 1;
|
||||
}
|
||||
|
||||
noreturn void eventLoop(void) {
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
struct sigaction action = {
|
||||
.sa_handler = handler,
|
||||
.sa_mask = mask,
|
||||
.sa_flags = SA_RESTART | SA_NOCLDSTOP,
|
||||
};
|
||||
sigaction(SIGCHLD, &action, NULL);
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
sigaction(SIGHUP, &action, NULL);
|
||||
|
||||
struct sigaction curses;
|
||||
sigaction(SIGWINCH, &action, &curses);
|
||||
assert(!(curses.sa_flags & SA_SIGINFO));
|
||||
|
||||
uiShowTag(TagStatus);
|
||||
uiFmt(TagStatus, UICold, "Traveling to %s...", self.host);
|
||||
uiDraw();
|
||||
int irc = ircConnect();
|
||||
|
||||
for (;;) {
|
||||
if (sig[SIGCHLD]) childWait();
|
||||
if (sig[SIGHUP]) ircQuit("zzz");
|
||||
if (sig[SIGINT]) {
|
||||
signal(SIGINT, SIG_DFL);
|
||||
ircQuit("Goodbye");
|
||||
}
|
||||
if (sig[SIGWINCH]) {
|
||||
curses.sa_handler(SIGWINCH);
|
||||
uiRead();
|
||||
uiDraw();
|
||||
}
|
||||
sig[SIGCHLD] = sig[SIGHUP] = sig[SIGINT] = sig[SIGWINCH] = 0;
|
||||
|
||||
struct pollfd fds[3] = {
|
||||
{ .events = POLLIN, .fd = irc },
|
||||
{ .events = POLLIN, .fd = STDIN_FILENO },
|
||||
{ .events = POLLIN, .fd = child.pipe },
|
||||
};
|
||||
if (child.wait) fds[1].events = 0;
|
||||
if (child.pipe < 0) fds[2].events = 0;
|
||||
|
||||
int nfds = poll(fds, 3, -1);
|
||||
if (nfds < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
err(EX_IOERR, "poll");
|
||||
}
|
||||
|
||||
if (fds[0].revents) ircRead();
|
||||
if (fds[1].revents) uiRead();
|
||||
if (fds[2].revents) childRead();
|
||||
|
||||
uiDraw();
|
||||
}
|
||||
}
|
162
format.c
162
format.c
|
@ -1,162 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
void formatReset(struct Format *format) {
|
||||
format->bold = false;
|
||||
format->italic = false;
|
||||
format->underline = false;
|
||||
format->reverse = false;
|
||||
format->fg = IRCDefault;
|
||||
format->bg = IRCDefault;
|
||||
}
|
||||
|
||||
static void parseColor(struct Format *format) {
|
||||
size_t len = MIN(wcsspn(format->str, L"0123456789"), 2);
|
||||
if (!len) {
|
||||
format->fg = IRCDefault;
|
||||
format->bg = IRCDefault;
|
||||
return;
|
||||
}
|
||||
format->fg = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
format->fg *= 10;
|
||||
format->fg += format->str[i] - L'0';
|
||||
}
|
||||
if (format->fg > IRCLightGray) format->fg = IRCDefault;
|
||||
format->str = &format->str[len];
|
||||
|
||||
len = 0;
|
||||
if (format->str[0] == L',') {
|
||||
len = MIN(wcsspn(&format->str[1], L"0123456789"), 2);
|
||||
}
|
||||
if (!len) return;
|
||||
format->bg = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
format->bg *= 10;
|
||||
format->bg += format->str[1 + i] - L'0';
|
||||
}
|
||||
if (format->bg > IRCLightGray) format->bg = IRCDefault;
|
||||
format->str = &format->str[1 + len];
|
||||
}
|
||||
|
||||
static const wchar_t Codes[] = {
|
||||
IRCBold, IRCColor, IRCReverse, IRCReset, IRCItalic, IRCUnderline, L'\0',
|
||||
};
|
||||
|
||||
bool formatParse(struct Format *format, const wchar_t *split) {
|
||||
format->str += format->len;
|
||||
if (!format->str[0]) {
|
||||
if (split == format->str && !format->split) {
|
||||
format->len = 0;
|
||||
format->split = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const wchar_t *init = format->str;
|
||||
for (bool done = false; !done;) {
|
||||
switch (format->str[0]) {
|
||||
break; case IRCBold: format->str++; format->bold ^= true;
|
||||
break; case IRCItalic: format->str++; format->italic ^= true;
|
||||
break; case IRCUnderline: format->str++; format->underline ^= true;
|
||||
break; case IRCReverse: format->str++; format->reverse ^= true;
|
||||
break; case IRCColor: format->str++; parseColor(format);
|
||||
break; case IRCReset: format->str++; formatReset(format);
|
||||
break; default: done = true;
|
||||
}
|
||||
}
|
||||
format->split = (split >= init && split <= format->str);
|
||||
|
||||
format->len = wcscspn(format->str, Codes);
|
||||
if (split > format->str && split < &format->str[format->len]) {
|
||||
format->len = split - format->str;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
|
||||
static bool testColor(
|
||||
const wchar_t *str, enum IRCColor fg, enum IRCColor bg, size_t index
|
||||
) {
|
||||
struct Format format = { .str = str };
|
||||
formatReset(&format);
|
||||
if (!formatParse(&format, NULL)) return false;
|
||||
if (format.fg != fg) return false;
|
||||
if (format.bg != bg) return false;
|
||||
return (format.str == &str[index]);
|
||||
}
|
||||
|
||||
static bool testSplit(const wchar_t *str, size_t index) {
|
||||
struct Format format = { .str = str };
|
||||
formatReset(&format);
|
||||
bool split = false;
|
||||
while (formatParse(&format, &str[index])) {
|
||||
if (format.split && split) return false;
|
||||
if (format.split) split = true;
|
||||
}
|
||||
return split;
|
||||
}
|
||||
|
||||
static bool testSplits(const wchar_t *str) {
|
||||
for (size_t i = 0; i <= wcslen(str); ++i) {
|
||||
if (!testSplit(str, i)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main() {
|
||||
assert(testColor(L"\003a", IRCDefault, IRCDefault, 1));
|
||||
assert(testColor(L"\003,a", IRCDefault, IRCDefault, 1));
|
||||
assert(testColor(L"\003,1", IRCDefault, IRCDefault, 1));
|
||||
assert(testColor(L"\0031a", IRCBlack, IRCDefault, 2));
|
||||
assert(testColor(L"\0031,a", IRCBlack, IRCDefault, 2));
|
||||
assert(testColor(L"\00312a", IRCLightBlue, IRCDefault, 3));
|
||||
assert(testColor(L"\00312,a", IRCLightBlue, IRCDefault, 3));
|
||||
assert(testColor(L"\003123", IRCLightBlue, IRCDefault, 3));
|
||||
assert(testColor(L"\0031,1a", IRCBlack, IRCBlack, 4));
|
||||
assert(testColor(L"\0031,12a", IRCBlack, IRCLightBlue, 5));
|
||||
assert(testColor(L"\0031,123", IRCBlack, IRCLightBlue, 5));
|
||||
assert(testColor(L"\00312,1a", IRCLightBlue, IRCBlack, 5));
|
||||
assert(testColor(L"\00312,12a", IRCLightBlue, IRCLightBlue, 6));
|
||||
assert(testColor(L"\00312,123", IRCLightBlue, IRCLightBlue, 6));
|
||||
|
||||
assert(testColor(L"\00316,16a", IRCDefault, IRCDefault, 6));
|
||||
assert(testColor(L"\00399,99a", IRCDefault, IRCDefault, 6));
|
||||
|
||||
assert(testSplits(L""));
|
||||
assert(testSplits(L"ab"));
|
||||
assert(testSplits(L"\002"));
|
||||
assert(testSplits(L"\002ab"));
|
||||
assert(testSplits(L"a\002b"));
|
||||
assert(testSplits(L"\002\003"));
|
||||
assert(testSplits(L"a\002\003b"));
|
||||
assert(testSplits(L"a\0031b"));
|
||||
assert(testSplits(L"a\00312b"));
|
||||
assert(testSplits(L"a\00312,1b"));
|
||||
assert(testSplits(L"a\00312,12b"));
|
||||
}
|
||||
|
||||
#endif
|
568
handle.c
568
handle.c
|
@ -1,568 +0,0 @@
|
|||
/* Copyright (C) 2018, 2019 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <err.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sysexits.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
static char *paramField(char **params) {
|
||||
char *rest = *params;
|
||||
if (rest[0] == ':') {
|
||||
*params = NULL;
|
||||
return &rest[1];
|
||||
}
|
||||
return strsep(params, " ");
|
||||
}
|
||||
|
||||
static void parse(
|
||||
char *prefix, char **nick, char **user, char **host,
|
||||
char *params, size_t req, size_t opt, /* (char **) */ ...
|
||||
) {
|
||||
char *field;
|
||||
if (prefix) {
|
||||
field = strsep(&prefix, "!");
|
||||
if (nick) *nick = field;
|
||||
field = strsep(&prefix, "@");
|
||||
if (user) *user = field;
|
||||
if (host) *host = prefix;
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, opt);
|
||||
for (size_t i = 0; i < req; ++i) {
|
||||
if (!params) errx(EX_PROTOCOL, "%zu params required, found %zu", req, i);
|
||||
field = paramField(¶ms);
|
||||
char **param = va_arg(ap, char **);
|
||||
if (param) *param = field;
|
||||
}
|
||||
for (size_t i = 0; i < opt; ++i) {
|
||||
char **param = va_arg(ap, char **);
|
||||
if (params) {
|
||||
*param = paramField(¶ms);
|
||||
} else {
|
||||
*param = NULL;
|
||||
}
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static bool isPing(const char *mesg) {
|
||||
size_t len = strlen(self.nick);
|
||||
const char *match = mesg;
|
||||
while (NULL != (match = strcasestr(match, self.nick))) {
|
||||
char b = (match > mesg ? *(match - 1) : ' ');
|
||||
char a = (match[len] ? match[len] : ' ');
|
||||
match = &match[len];
|
||||
if (!isspace(b) && !ispunct(b)) continue;
|
||||
if (!isspace(a) && !ispunct(a)) continue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static char *dequote(char *mesg) {
|
||||
if (mesg[0] == '"') mesg = &mesg[1];
|
||||
size_t len = strlen(mesg);
|
||||
if (mesg[len - 1] == '"') mesg[len - 1] = '\0';
|
||||
return mesg;
|
||||
}
|
||||
|
||||
typedef void Handler(char *prefix, char *params);
|
||||
|
||||
static void handlePing(char *prefix, char *params) {
|
||||
(void)prefix;
|
||||
ircFmt("PONG %s\r\n", params);
|
||||
}
|
||||
|
||||
static void handleError(char *prefix, char *params) {
|
||||
char *mesg;
|
||||
parse(prefix, NULL, NULL, NULL, params, 1, 0, &mesg);
|
||||
if (self.quit) {
|
||||
uiExit(EX_OK);
|
||||
} else {
|
||||
errx(EX_PROTOCOL, "%s", mesg);
|
||||
}
|
||||
}
|
||||
|
||||
static void handleCap(char *prefix, char *params) {
|
||||
char *subc, *list;
|
||||
parse(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &subc, &list);
|
||||
if (!strcmp(subc, "ACK") && self.auth) {
|
||||
size_t len = strlen(self.auth);
|
||||
byte plain[1 + len];
|
||||
plain[0] = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
plain[1 + i] = (self.auth[i] == ':' ? 0 : self.auth[i]);
|
||||
}
|
||||
char b64[base64Size(sizeof(plain))];
|
||||
base64(b64, plain, sizeof(plain));
|
||||
ircFmt("AUTHENTICATE PLAIN\r\n");
|
||||
ircFmt("AUTHENTICATE %s\r\n", b64);
|
||||
}
|
||||
ircFmt("CAP END\r\n");
|
||||
}
|
||||
|
||||
static void handleErrorErroneousNickname(char *prefix, char *params) {
|
||||
char *mesg;
|
||||
parse(prefix, NULL, NULL, NULL, params, 3, 0, NULL, NULL, &mesg);
|
||||
uiFmt(TagStatus, UIHot, "You can't use that name here: \"%s\"", mesg);
|
||||
uiLog(TagStatus, UICold, L"Type /nick <name> to choose a new one");
|
||||
}
|
||||
|
||||
static void handleReplyWelcome(char *prefix, char *params) {
|
||||
char *nick;
|
||||
parse(prefix, NULL, NULL, NULL, params, 1, 0, &nick);
|
||||
|
||||
if (strcmp(nick, self.nick)) {
|
||||
free(self.nick);
|
||||
self.nick = strdup(nick);
|
||||
if (!self.nick) err(EX_OSERR, "strdup");
|
||||
uiPrompt(true);
|
||||
}
|
||||
if (self.join && self.keys) {
|
||||
ircFmt("JOIN %s %s\r\n", self.join, self.keys);
|
||||
} else if (self.join) {
|
||||
ircFmt("JOIN %s\r\n", self.join);
|
||||
}
|
||||
tabTouch(TagStatus, self.nick);
|
||||
|
||||
uiLog(TagStatus, UICold, L"You have arrived");
|
||||
}
|
||||
|
||||
static void handleReplyMOTD(char *prefix, char *params) {
|
||||
char *mesg;
|
||||
parse(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &mesg);
|
||||
if (mesg[0] == '-' && mesg[1] == ' ') mesg = &mesg[2];
|
||||
|
||||
urlScan(TagStatus, mesg);
|
||||
uiFmt(TagStatus, UICold, "%s", mesg);
|
||||
}
|
||||
|
||||
static void handleReplyList(char *prefix, char *params) {
|
||||
char *chan, *count, *topic;
|
||||
parse(prefix, NULL, NULL, NULL, params, 4, 0, NULL, &chan, &count, &topic);
|
||||
if (topic[0] == '[') {
|
||||
char *skip = strstr(topic, "] ");
|
||||
if (skip) topic = &skip[2];
|
||||
}
|
||||
const char *people = (strcmp(count, "1") ? "people" : "person");
|
||||
if (topic[0]) {
|
||||
uiFmt(
|
||||
TagStatus, UIWarm,
|
||||
"You see %s %s in \3%d%s\3 under the banner, \"%s\"",
|
||||
count, people, colorGen(chan), chan, topic
|
||||
);
|
||||
} else {
|
||||
uiFmt(
|
||||
TagStatus, UIWarm,
|
||||
"You see %s %s in \3%d%s\3",
|
||||
count, people, colorGen(chan), chan
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void handleReplyListEnd(char *prefix, char *params) {
|
||||
(void)prefix;
|
||||
(void)params;
|
||||
uiLog(TagStatus, UICold, L"You don't see anyone else");
|
||||
}
|
||||
|
||||
static enum IRCColor whoisColor;
|
||||
static void handleReplyWhoisUser(char *prefix, char *params) {
|
||||
char *nick, *user, *host, *real;
|
||||
parse(
|
||||
prefix, NULL, NULL, NULL,
|
||||
params, 6, 0, NULL, &nick, &user, &host, NULL, &real
|
||||
);
|
||||
whoisColor = colorGen(user);
|
||||
uiFmt(
|
||||
TagStatus, UIWarm,
|
||||
"\3%d%s\3 is %s@%s, \"%s\"",
|
||||
whoisColor, nick, user, host, real
|
||||
);
|
||||
}
|
||||
|
||||
static void handleReplyWhoisServer(char *prefix, char *params) {
|
||||
char *nick, *serv, *info;
|
||||
parse(prefix, NULL, NULL, NULL, params, 4, 0, NULL, &nick, &serv, &info);
|
||||
uiFmt(
|
||||
TagStatus, UIWarm,
|
||||
"\3%d%s\3 is connected to %s, \"%s\"",
|
||||
whoisColor, nick, serv, info
|
||||
);
|
||||
}
|
||||
|
||||
static void handleReplyWhoisOperator(char *prefix, char *params) {
|
||||
char *nick, *oper;
|
||||
parse(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &nick, &oper);
|
||||
uiFmt(TagStatus, UIWarm, "\3%d%s\3 %s", whoisColor, nick, oper);
|
||||
}
|
||||
|
||||
static void handleReplyWhoisIdle(char *prefix, char *params) {
|
||||
char *nick, *idle, *sign;
|
||||
parse(prefix, NULL, NULL, NULL, params, 4, 0, NULL, &nick, &idle, &sign);
|
||||
time_t time = strtoul(sign, NULL, 10);
|
||||
const char *at = ctime(&time);
|
||||
unsigned long secs = strtoul(idle, NULL, 10);
|
||||
unsigned long mins = secs / 60; secs %= 60;
|
||||
unsigned long hours = mins / 60; mins %= 60;
|
||||
uiFmt(
|
||||
TagStatus, UIWarm,
|
||||
"\3%d%s\3 signed on at %.24s and has been idle for %02lu:%02lu:%02lu",
|
||||
whoisColor, nick, at, hours, mins, secs
|
||||
);
|
||||
}
|
||||
|
||||
static void handleReplyWhoisChannels(char *prefix, char *params) {
|
||||
char *nick, *chans;
|
||||
parse(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &nick, &chans);
|
||||
uiFmt(TagStatus, UIWarm, "\3%d%s\3 is in %s", whoisColor, nick, chans);
|
||||
}
|
||||
|
||||
static void handleErrorNoSuchNick(char *prefix, char *params) {
|
||||
char *nick, *mesg;
|
||||
parse(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &nick, &mesg);
|
||||
uiFmt(TagStatus, UIWarm, "%s, \"%s\"", mesg, nick);
|
||||
}
|
||||
|
||||
static void handleJoin(char *prefix, char *params) {
|
||||
char *nick, *user, *chan;
|
||||
parse(prefix, &nick, &user, NULL, params, 1, 0, &chan);
|
||||
struct Tag tag = colorTag(tagFor(chan), chan);
|
||||
|
||||
if (!strcmp(nick, self.nick)) {
|
||||
tabTouch(TagNone, chan);
|
||||
uiShowTag(tag);
|
||||
logReplay(tag);
|
||||
}
|
||||
tabTouch(tag, nick);
|
||||
|
||||
uiFmt(
|
||||
tag, UICold,
|
||||
"\3%d%s\3 arrives in \3%d%s\3",
|
||||
colorGen(user), nick, colorGen(chan), chan
|
||||
);
|
||||
logFmt(tag, NULL, "%s arrives in %s", nick, chan);
|
||||
}
|
||||
|
||||
static void handlePart(char *prefix, char *params) {
|
||||
char *nick, *user, *chan, *mesg;
|
||||
parse(prefix, &nick, &user, NULL, params, 1, 1, &chan, &mesg);
|
||||
struct Tag tag = colorTag(tagFor(chan), chan);
|
||||
|
||||
if (!strcmp(nick, self.nick)) {
|
||||
tabClear(tag);
|
||||
} else {
|
||||
tabRemove(tag, nick);
|
||||
}
|
||||
|
||||
if (mesg) {
|
||||
urlScan(tag, mesg);
|
||||
uiFmt(
|
||||
tag, UICold,
|
||||
"\3%d%s\3 leaves \3%d%s\3, \"%s\"",
|
||||
colorGen(user), nick, colorGen(chan), chan, dequote(mesg)
|
||||
);
|
||||
logFmt(tag, NULL, "%s leaves %s, \"%s\"", nick, chan, dequote(mesg));
|
||||
} else {
|
||||
uiFmt(
|
||||
tag, UICold,
|
||||
"\3%d%s\3 leaves \3%d%s\3",
|
||||
colorGen(user), nick, colorGen(chan), chan
|
||||
);
|
||||
logFmt(tag, NULL, "%s leaves %s", nick, chan);
|
||||
}
|
||||
}
|
||||
|
||||
static void handleKick(char *prefix, char *params) {
|
||||
char *nick, *user, *chan, *kick, *mesg;
|
||||
parse(prefix, &nick, &user, NULL, params, 2, 1, &chan, &kick, &mesg);
|
||||
struct Tag tag = colorTag(tagFor(chan), chan);
|
||||
bool kicked = !strcmp(kick, self.nick);
|
||||
|
||||
if (kicked) {
|
||||
tabClear(tag);
|
||||
} else {
|
||||
tabRemove(tag, kick);
|
||||
}
|
||||
|
||||
if (mesg) {
|
||||
urlScan(tag, mesg);
|
||||
uiFmt(
|
||||
tag, (kicked ? UIHot : UICold),
|
||||
"\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3, \"%s\"",
|
||||
colorGen(user), nick,
|
||||
colorGen(kick), kick,
|
||||
colorGen(chan), chan,
|
||||
dequote(mesg)
|
||||
);
|
||||
logFmt(
|
||||
tag, NULL,
|
||||
"%s kicks %s out of %s, \"%s\"", nick, kick, chan, dequote(mesg)
|
||||
);
|
||||
} else {
|
||||
uiFmt(
|
||||
tag, (kicked ? UIHot : UICold),
|
||||
"\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3",
|
||||
colorGen(user), nick,
|
||||
colorGen(kick), kick,
|
||||
colorGen(chan), chan
|
||||
);
|
||||
logFmt(tag, NULL, "%s kicks %s out of %s", nick, kick, chan);
|
||||
}
|
||||
}
|
||||
|
||||
static void handleQuit(char *prefix, char *params) {
|
||||
char *nick, *user, *mesg;
|
||||
parse(prefix, &nick, &user, NULL, params, 0, 1, &mesg);
|
||||
|
||||
struct Tag tag;
|
||||
while (TagNone.id != (tag = tabTag(nick)).id) {
|
||||
tabRemove(tag, nick);
|
||||
|
||||
if (mesg) {
|
||||
urlScan(tag, mesg);
|
||||
uiFmt(
|
||||
tag, UICold,
|
||||
"\3%d%s\3 leaves, \"%s\"",
|
||||
colorGen(user), nick, dequote(mesg)
|
||||
);
|
||||
logFmt(tag, NULL, "%s leaves, \"%s\"", nick, dequote(mesg));
|
||||
} else {
|
||||
uiFmt(tag, UICold, "\3%d%s\3 leaves", colorGen(user), nick);
|
||||
logFmt(tag, NULL, "%s leaves", nick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void handleReplyTopic(char *prefix, char *params) {
|
||||
char *chan, *topic;
|
||||
parse(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &chan, &topic);
|
||||
struct Tag tag = colorTag(tagFor(chan), chan);
|
||||
|
||||
urlScan(tag, topic);
|
||||
uiFmt(
|
||||
tag, UICold,
|
||||
"The sign in \3%d%s\3 reads, \"%s\"",
|
||||
colorGen(chan), chan, topic
|
||||
);
|
||||
logFmt(tag, NULL, "The sign in %s reads, \"%s\"", chan, topic);
|
||||
}
|
||||
|
||||
static void handleTopic(char *prefix, char *params) {
|
||||
char *nick, *user, *chan, *topic;
|
||||
parse(prefix, &nick, &user, NULL, params, 2, 0, &chan, &topic);
|
||||
struct Tag tag = colorTag(tagFor(chan), chan);
|
||||
|
||||
if (strcmp(nick, self.nick)) tabTouch(tag, nick);
|
||||
|
||||
urlScan(tag, topic);
|
||||
uiFmt(
|
||||
tag, UICold,
|
||||
"\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",
|
||||
colorGen(user), nick, colorGen(chan), chan, topic
|
||||
);
|
||||
logFmt(tag, NULL, "%s places a new sign in %s, \"%s\"", nick, chan, topic);
|
||||
}
|
||||
|
||||
static void handleReplyEndOfNames(char *prefix, char *params) {
|
||||
char *chan;
|
||||
parse(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan);
|
||||
ircFmt("WHO %s\r\n", chan);
|
||||
}
|
||||
|
||||
static struct {
|
||||
char buf[4096];
|
||||
size_t len;
|
||||
} who;
|
||||
|
||||
static void handleReplyWho(char *prefix, char *params) {
|
||||
char *chan, *user, *nick;
|
||||
parse(
|
||||
prefix, NULL, NULL, NULL,
|
||||
params, 6, 0, NULL, &chan, &user, NULL, NULL, &nick
|
||||
);
|
||||
struct Tag tag = colorTag(tagFor(chan), chan);
|
||||
|
||||
tabAdd(tag, nick);
|
||||
|
||||
size_t cap = sizeof(who.buf) - who.len;
|
||||
int len = snprintf(
|
||||
&who.buf[who.len], cap,
|
||||
"%s\3%d%s\3",
|
||||
(who.len ? ", " : ""), colorGen(user), nick
|
||||
);
|
||||
if ((size_t)len < cap) who.len += len;
|
||||
}
|
||||
|
||||
static void handleReplyEndOfWho(char *prefix, char *params) {
|
||||
char *chan;
|
||||
parse(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan);
|
||||
struct Tag tag = colorTag(tagFor(chan), chan);
|
||||
|
||||
uiFmt(
|
||||
tag, UICold,
|
||||
"In \3%d%s\3 are %s",
|
||||
colorGen(chan), chan, who.buf
|
||||
);
|
||||
who.len = 0;
|
||||
}
|
||||
|
||||
static void handleNick(char *prefix, char *params) {
|
||||
char *prev, *user, *next;
|
||||
parse(prefix, &prev, &user, NULL, params, 1, 0, &next);
|
||||
|
||||
if (!strcmp(prev, self.nick)) {
|
||||
free(self.nick);
|
||||
self.nick = strdup(next);
|
||||
if (!self.nick) err(EX_OSERR, "strdup");
|
||||
uiPrompt(true);
|
||||
}
|
||||
|
||||
struct Tag tag;
|
||||
while (TagNone.id != (tag = tabTag(prev)).id) {
|
||||
tabReplace(tag, prev, next);
|
||||
|
||||
uiFmt(
|
||||
tag, UICold,
|
||||
"\3%d%s\3 is now known as \3%d%s\3",
|
||||
colorGen(user), prev, colorGen(user), next
|
||||
);
|
||||
logFmt(tag, NULL, "%s is now known as %s", prev, next);
|
||||
}
|
||||
}
|
||||
|
||||
static void handleCTCP(struct Tag tag, char *nick, char *user, char *mesg) {
|
||||
mesg = &mesg[1];
|
||||
char *ctcp = strsep(&mesg, " ");
|
||||
char *params = strsep(&mesg, "\1");
|
||||
if (strcmp(ctcp, "ACTION")) return;
|
||||
|
||||
if (strcmp(nick, self.nick)) tabTouch(tag, nick);
|
||||
|
||||
urlScan(tag, params);
|
||||
bool ping = strcmp(nick, self.nick) && isPing(params);
|
||||
uiFmt(
|
||||
tag, (ping ? UIHot : UIWarm),
|
||||
"%c\3%d* %s\17 %s",
|
||||
ping["\17\26"], colorGen(user), nick, params
|
||||
);
|
||||
logFmt(tag, NULL, "* %s %s", nick, params);
|
||||
}
|
||||
|
||||
static void handlePrivmsg(char *prefix, char *params) {
|
||||
char *nick, *user, *chan, *mesg;
|
||||
parse(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg);
|
||||
bool direct = !strcmp(chan, self.nick);
|
||||
struct Tag tag = tagFor(direct ? nick : chan);
|
||||
colorTag(tag, direct ? user : chan);
|
||||
if (mesg[0] == '\1') {
|
||||
handleCTCP(tag, nick, user, mesg);
|
||||
return;
|
||||
}
|
||||
|
||||
bool me = !strcmp(nick, self.nick);
|
||||
if (!me) tabTouch(tag, nick);
|
||||
|
||||
urlScan(tag, mesg);
|
||||
bool hot = !me && (direct || isPing(mesg));
|
||||
bool ping = !me && isPing(mesg);
|
||||
uiFmt(
|
||||
tag, (hot ? UIHot : UIWarm),
|
||||
"%c%c\3%d<%s>%c %s",
|
||||
(me ? IRCUnderline : IRCColor), (ping ? IRCReverse : IRCColor),
|
||||
colorGen(user), nick, IRCReset, mesg
|
||||
);
|
||||
logFmt(tag, NULL, "<%s> %s", nick, mesg);
|
||||
}
|
||||
|
||||
static void handleNotice(char *prefix, char *params) {
|
||||
char *nick, *user, *chan, *mesg;
|
||||
parse(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg);
|
||||
bool direct = !strcmp(chan, self.nick);
|
||||
struct Tag tag = TagStatus;
|
||||
if (user) {
|
||||
tag = tagFor(direct ? nick : chan);
|
||||
colorTag(tag, direct ? user : chan);
|
||||
}
|
||||
|
||||
if (strcmp(nick, self.nick)) tabTouch(tag, nick);
|
||||
|
||||
urlScan(tag, mesg);
|
||||
bool ping = strcmp(nick, self.nick) && isPing(mesg);
|
||||
uiFmt(
|
||||
tag, (ping ? UIHot : UIWarm),
|
||||
"%c\3%d-%s-\17 %s",
|
||||
ping["\17\26"], colorGen(user), nick, mesg
|
||||
);
|
||||
logFmt(tag, NULL, "-%s- %s", nick, mesg);
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char *command;
|
||||
Handler *handler;
|
||||
} Handlers[] = {
|
||||
{ "001", handleReplyWelcome },
|
||||
{ "311", handleReplyWhoisUser },
|
||||
{ "312", handleReplyWhoisServer },
|
||||
{ "313", handleReplyWhoisOperator },
|
||||
{ "315", handleReplyEndOfWho },
|
||||
{ "317", handleReplyWhoisIdle },
|
||||
{ "319", handleReplyWhoisChannels },
|
||||
{ "322", handleReplyList },
|
||||
{ "323", handleReplyListEnd },
|
||||
{ "332", handleReplyTopic },
|
||||
{ "352", handleReplyWho },
|
||||
{ "366", handleReplyEndOfNames },
|
||||
{ "372", handleReplyMOTD },
|
||||
{ "375", handleReplyMOTD },
|
||||
{ "401", handleErrorNoSuchNick },
|
||||
{ "432", handleErrorErroneousNickname },
|
||||
{ "433", handleErrorErroneousNickname },
|
||||
{ "CAP", handleCap },
|
||||
{ "ERROR", handleError },
|
||||
{ "JOIN", handleJoin },
|
||||
{ "KICK", handleKick },
|
||||
{ "NICK", handleNick },
|
||||
{ "NOTICE", handleNotice },
|
||||
{ "PART", handlePart },
|
||||
{ "PING", handlePing },
|
||||
{ "PRIVMSG", handlePrivmsg },
|
||||
{ "QUIT", handleQuit },
|
||||
{ "TOPIC", handleTopic },
|
||||
};
|
||||
static const size_t HandlersLen = sizeof(Handlers) / sizeof(Handlers[0]);
|
||||
|
||||
void handle(char *line) {
|
||||
char *prefix = NULL;
|
||||
if (line[0] == ':') {
|
||||
prefix = strsep(&line, " ") + 1;
|
||||
if (!line) errx(EX_PROTOCOL, "unexpected eol");
|
||||
}
|
||||
char *command = strsep(&line, " ");
|
||||
for (size_t i = 0; i < HandlersLen; ++i) {
|
||||
if (strcmp(command, Handlers[i].command)) continue;
|
||||
Handlers[i].handler(prefix, line);
|
||||
break;
|
||||
}
|
||||
}
|
276
input.c
276
input.c
|
@ -1,276 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <err.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sysexits.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
static void privmsg(struct Tag tag, bool action, const char *mesg) {
|
||||
char *line;
|
||||
int send;
|
||||
asprintf(
|
||||
&line, ":%s!%s %nPRIVMSG %s :%s%s%s",
|
||||
self.nick, self.user, &send, tag.name,
|
||||
(action ? "\1ACTION " : ""), mesg, (action ? "\1" : "")
|
||||
);
|
||||
if (!line) err(EX_OSERR, "asprintf");
|
||||
ircFmt("%s\r\n", &line[send]);
|
||||
handle(line);
|
||||
free(line);
|
||||
}
|
||||
|
||||
typedef void Handler(struct Tag tag, char *params);
|
||||
|
||||
static void inputJoin(struct Tag tag, char *params) {
|
||||
char *chan = strsep(¶ms, " ");
|
||||
char *key = strsep(¶ms, " ");
|
||||
if (key) {
|
||||
ircFmt("JOIN %s %s\r\n", chan, key);
|
||||
} else {
|
||||
ircFmt("JOIN %s\r\n", chan ? chan : tag.name);
|
||||
}
|
||||
}
|
||||
|
||||
static void inputList(struct Tag tag, char *params) {
|
||||
(void)tag;
|
||||
char *chan = strsep(¶ms, " ");
|
||||
if (chan) {
|
||||
ircFmt("LIST %s\r\n", chan);
|
||||
} else {
|
||||
ircFmt("LIST\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void inputMe(struct Tag tag, char *params) {
|
||||
privmsg(tag, true, params ? params : "");
|
||||
}
|
||||
|
||||
static void inputNick(struct Tag tag, char *params) {
|
||||
char *nick = strsep(¶ms, " ");
|
||||
if (!nick) {
|
||||
uiLog(tag, UIHot, L"/nick requires a name");
|
||||
return;
|
||||
}
|
||||
ircFmt("NICK %s\r\n", nick);
|
||||
}
|
||||
|
||||
static void inputPart(struct Tag tag, char *params) {
|
||||
ircFmt("PART %s :%s\r\n", tag.name, params ? params : "Goodbye");
|
||||
}
|
||||
|
||||
static void inputQuery(struct Tag tag, char *params) {
|
||||
char *nick = strsep(¶ms, " ");
|
||||
if (!nick) {
|
||||
uiLog(tag, UIHot, L"/query requires a nick");
|
||||
return;
|
||||
}
|
||||
tabTouch(TagNone, nick);
|
||||
uiShowTag(tagFor(nick));
|
||||
logReplay(tagFor(nick));
|
||||
}
|
||||
|
||||
static void inputQuit(struct Tag tag, char *params) {
|
||||
(void)tag;
|
||||
ircQuit(params ? params : "Goodbye");
|
||||
}
|
||||
|
||||
static void inputQuote(struct Tag tag, char *params) {
|
||||
(void)tag;
|
||||
if (params) ircFmt("%s\r\n", params);
|
||||
}
|
||||
|
||||
static void inputTopic(struct Tag tag, char *params) {
|
||||
if (params) {
|
||||
ircFmt("TOPIC %s :%s\r\n", tag.name, params);
|
||||
} else {
|
||||
ircFmt("TOPIC %s\r\n", tag.name);
|
||||
}
|
||||
}
|
||||
|
||||
static void inputWho(struct Tag tag, char *params) {
|
||||
(void)params;
|
||||
ircFmt("WHO :%s\r\n", tag.name);
|
||||
}
|
||||
|
||||
static void inputWhois(struct Tag tag, char *params) {
|
||||
char *nick = strsep(¶ms, " ");
|
||||
if (!nick) {
|
||||
uiLog(tag, UIHot, L"/whois requires a nick");
|
||||
return;
|
||||
}
|
||||
ircFmt("WHOIS %s\r\n", nick);
|
||||
}
|
||||
|
||||
static void inputZNC(struct Tag tag, char *params) {
|
||||
(void)tag;
|
||||
ircFmt("ZNC %s\r\n", params ? params : "");
|
||||
}
|
||||
|
||||
static void inputClose(struct Tag tag, char *params) {
|
||||
(void)params;
|
||||
uiCloseTag(tag);
|
||||
tabRemove(TagNone, tag.name);
|
||||
}
|
||||
|
||||
static void inputMan(struct Tag tag, char *params) {
|
||||
(void)tag;
|
||||
(void)params;
|
||||
eventWait((const char *[]) { "man", "1", "catgirl", NULL });
|
||||
}
|
||||
|
||||
static void inputMove(struct Tag tag, char *params) {
|
||||
char *num = strsep(¶ms, " ");
|
||||
if (!num) {
|
||||
uiLog(tag, UIHot, L"/move requires a number");
|
||||
return;
|
||||
}
|
||||
uiMoveTag(tag, strtol(num, NULL, 0), num[0] == '+' || num[0] == '-');
|
||||
}
|
||||
|
||||
static void inputOpen(struct Tag tag, char *params) {
|
||||
if (params && !isdigit(params[0])) {
|
||||
urlOpenMatch(tag, params);
|
||||
} else {
|
||||
size_t at = (params ? strtoul(strsep(¶ms, "-,"), NULL, 0) : 1);
|
||||
size_t to = (params ? strtoul(params, NULL, 0) : at);
|
||||
urlOpenRange(tag, at - 1, to);
|
||||
}
|
||||
}
|
||||
|
||||
static void inputRaw(struct Tag tag, char *params) {
|
||||
(void)tag;
|
||||
(void)params;
|
||||
self.raw ^= true;
|
||||
uiFmt(
|
||||
TagRaw, UIWarm, "\3%d%s\3 %s raw mode!",
|
||||
colorGen(self.user), self.nick, (self.raw ? "engages" : "disengages")
|
||||
);
|
||||
}
|
||||
|
||||
static void inputURL(struct Tag tag, char *params) {
|
||||
(void)params;
|
||||
urlList(tag);
|
||||
}
|
||||
|
||||
static void inputWindow(struct Tag tag, char *params) {
|
||||
char *word = strsep(¶ms, " ");
|
||||
if (!word) {
|
||||
uiLog(tag, UIHot, L"/window requires a name or number");
|
||||
return;
|
||||
}
|
||||
bool relative = (word[0] == '+' || word[0] == '-');
|
||||
char *trail;
|
||||
int num = strtol(word, &trail, 0);
|
||||
if (!trail[0]) {
|
||||
uiShowNum(num, relative);
|
||||
} else {
|
||||
struct Tag name = tagFind(word);
|
||||
if (name.id != TagNone.id) {
|
||||
uiShowTag(name);
|
||||
} else {
|
||||
uiFmt(tag, UIHot, "No window for %s", word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char *command;
|
||||
Handler *handler;
|
||||
bool limit;
|
||||
} Commands[] = {
|
||||
{ "/close", .handler = inputClose },
|
||||
{ "/help", .handler = inputMan },
|
||||
{ "/join", .handler = inputJoin, .limit = true },
|
||||
{ "/list", .handler = inputList },
|
||||
{ "/man", .handler = inputMan },
|
||||
{ "/me", .handler = inputMe },
|
||||
{ "/move", .handler = inputMove },
|
||||
{ "/names", .handler = inputWho },
|
||||
{ "/nick", .handler = inputNick },
|
||||
{ "/open", .handler = inputOpen },
|
||||
{ "/part", .handler = inputPart },
|
||||
{ "/query", .handler = inputQuery, .limit = true },
|
||||
{ "/quit", .handler = inputQuit },
|
||||
{ "/quote", .handler = inputQuote, .limit = true },
|
||||
{ "/raw", .handler = inputRaw, .limit = true },
|
||||
{ "/topic", .handler = inputTopic },
|
||||
{ "/url", .handler = inputURL },
|
||||
{ "/who", .handler = inputWho },
|
||||
{ "/whois", .handler = inputWhois },
|
||||
{ "/window", .handler = inputWindow },
|
||||
{ "/znc", .handler = inputZNC },
|
||||
};
|
||||
static const size_t CommandsLen = sizeof(Commands) / sizeof(Commands[0]);
|
||||
|
||||
void inputTab(void) {
|
||||
for (size_t i = 0; i < CommandsLen; ++i) {
|
||||
tabTouch(TagNone, Commands[i].command);
|
||||
}
|
||||
}
|
||||
|
||||
void input(struct Tag tag, char *input) {
|
||||
bool slash = (input[0] == '/');
|
||||
if (slash) {
|
||||
char *space = strchr(&input[1], ' ');
|
||||
char *extra = strchr(&input[1], '/');
|
||||
if (extra && (!space || extra < space)) slash = false;
|
||||
}
|
||||
|
||||
if (!slash) {
|
||||
if (tag.id == TagRaw.id) {
|
||||
ircFmt("%s\r\n", input);
|
||||
} else if (tag.id != TagStatus.id) {
|
||||
privmsg(tag, false, input);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
char *word = strsep(&input, " ");
|
||||
if (input && !input[0]) input = NULL;
|
||||
|
||||
char *trail;
|
||||
strtol(&word[1], &trail, 0);
|
||||
if (!trail[0]) {
|
||||
inputWindow(tag, &word[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *command = word;
|
||||
const char *uniq = tabNext(TagNone, command);
|
||||
if (uniq && tabNext(TagNone, command) == uniq) {
|
||||
command = uniq;
|
||||
tabAccept();
|
||||
} else {
|
||||
tabReject();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < CommandsLen; ++i) {
|
||||
if (strcasecmp(command, Commands[i].command)) continue;
|
||||
if (self.limit && Commands[i].limit) {
|
||||
uiFmt(tag, UIHot, "%s isn't available in restricted mode", command);
|
||||
return;
|
||||
}
|
||||
Commands[i].handler(tag, input);
|
||||
return;
|
||||
}
|
||||
uiFmt(tag, UIHot, "%s isn't a recognized command", command);
|
||||
}
|
147
irc.c
147
irc.c
|
@ -1,147 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sysexits.h>
|
||||
#include <tls.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
static struct tls *client;
|
||||
|
||||
int ircConnect(void) {
|
||||
int error;
|
||||
|
||||
struct tls_config *config = tls_config_new();
|
||||
error = tls_config_set_ciphers(config, "compat");
|
||||
if (error) errx(EX_SOFTWARE, "tls_config");
|
||||
|
||||
client = tls_client();
|
||||
if (!client) errx(EX_SOFTWARE, "tls_client");
|
||||
|
||||
error = tls_configure(client, config);
|
||||
if (self.insecure) {
|
||||
tls_config_insecure_noverifycert(config);
|
||||
tls_config_insecure_noverifyname(config);
|
||||
}
|
||||
if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(client));
|
||||
tls_config_free(config);
|
||||
|
||||
struct addrinfo *head;
|
||||
struct addrinfo hints = {
|
||||
.ai_family = AF_UNSPEC,
|
||||
.ai_socktype = SOCK_STREAM,
|
||||
.ai_protocol = IPPROTO_TCP,
|
||||
};
|
||||
error = getaddrinfo(self.host, self.port, &hints, &head);
|
||||
if (error) errx(EX_NOHOST, "getaddrinfo: %s", gai_strerror(error));
|
||||
|
||||
int sock = -1;
|
||||
for (struct addrinfo *ai = head; ai; ai = ai->ai_next) {
|
||||
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
||||
if (sock < 0) err(EX_OSERR, "socket");
|
||||
error = connect(sock, ai->ai_addr, ai->ai_addrlen);
|
||||
if (!error) break;
|
||||
|
||||
close(sock);
|
||||
sock = -1;
|
||||
}
|
||||
if (sock < 0) err(EX_UNAVAILABLE, "connect");
|
||||
freeaddrinfo(head);
|
||||
|
||||
error = fcntl(sock, F_SETFD, FD_CLOEXEC);
|
||||
if (error) err(EX_IOERR, "fcntl");
|
||||
|
||||
error = tls_connect_socket(client, sock, self.host);
|
||||
if (error) errx(EX_PROTOCOL, "tls_connect: %s", tls_error(client));
|
||||
|
||||
if (self.auth) ircFmt("CAP REQ :sasl\r\n");
|
||||
if (self.pass) ircFmt("PASS :%s\r\n", self.pass);
|
||||
ircFmt("NICK %s\r\n", self.nick);
|
||||
ircFmt("USER %s 0 * :%s\r\n", self.user, self.real);
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
void ircWrite(const char *ptr, size_t len) {
|
||||
while (len) {
|
||||
ssize_t ret = tls_write(client, ptr, len);
|
||||
if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) continue;
|
||||
if (ret < 0) errx(EX_IOERR, "tls_write: %s", tls_error(client));
|
||||
ptr += ret;
|
||||
len -= ret;
|
||||
}
|
||||
}
|
||||
|
||||
void ircFmt(const char *format, ...) {
|
||||
char *buf;
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
int len = vasprintf(&buf, format, ap);
|
||||
va_end(ap);
|
||||
if (!buf) err(EX_OSERR, "vasprintf");
|
||||
if (self.raw) {
|
||||
if (!isatty(STDERR_FILENO)) fprintf(stderr, "<<< %.*s\n", len - 2, buf);
|
||||
uiFmt(TagRaw, UICold, "\3%d<<<\3 %.*s", IRCWhite, len - 2, buf);
|
||||
}
|
||||
ircWrite(buf, len);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
void ircQuit(const char *mesg) {
|
||||
ircFmt("QUIT :%s\r\n", mesg);
|
||||
self.quit = true;
|
||||
}
|
||||
|
||||
void ircRead(void) {
|
||||
static char buf[4096];
|
||||
static size_t len;
|
||||
|
||||
ssize_t read;
|
||||
retry:
|
||||
read = tls_read(client, &buf[len], sizeof(buf) - len);
|
||||
if (read == TLS_WANT_POLLIN || read == TLS_WANT_POLLOUT) goto retry;
|
||||
if (read < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client));
|
||||
if (!read) {
|
||||
if (!self.quit) errx(EX_PROTOCOL, "unexpected eof");
|
||||
uiExit(EX_OK);
|
||||
}
|
||||
len += read;
|
||||
|
||||
char *crlf;
|
||||
char *line = buf;
|
||||
while (NULL != (crlf = memmem(line, &buf[len] - line, "\r\n", 2))) {
|
||||
crlf[0] = '\0';
|
||||
if (self.raw) {
|
||||
if (!isatty(STDERR_FILENO)) fprintf(stderr, ">>> %s\n", line);
|
||||
uiFmt(TagRaw, UICold, "\3%d>>>\3 %s", IRCGray, line);
|
||||
}
|
||||
handle(line);
|
||||
line = &crlf[2];
|
||||
}
|
||||
|
||||
len -= line - buf;
|
||||
memmove(buf, line, len);
|
||||
}
|
157
log.c
157
log.c
|
@ -1,157 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sysexits.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
static int logRoot = -1;
|
||||
|
||||
static struct Log {
|
||||
int dir;
|
||||
int year;
|
||||
int month;
|
||||
int day;
|
||||
FILE *file;
|
||||
} logs[TagsLen];
|
||||
|
||||
void logOpen(const char *path) {
|
||||
logRoot = open(path, O_RDONLY | O_CLOEXEC);
|
||||
if (logRoot < 0) err(EX_CANTCREAT, "%s", path);
|
||||
}
|
||||
|
||||
static void sanitize(char *name) {
|
||||
for (; name[0]; ++name) {
|
||||
if (name[0] == '/') name[0] = '_';
|
||||
}
|
||||
}
|
||||
|
||||
static FILE *logFile(struct Tag tag, const struct tm *time) {
|
||||
struct Log *log = &logs[tag.id];
|
||||
if (
|
||||
log->file
|
||||
&& log->year == time->tm_year
|
||||
&& log->month == time->tm_mon
|
||||
&& log->day == time->tm_mday
|
||||
) return log->file;
|
||||
|
||||
if (log->file) {
|
||||
fclose(log->file);
|
||||
|
||||
} else {
|
||||
char *name = strdup(tag.name);
|
||||
if (!name) err(EX_OSERR, "strdup");
|
||||
sanitize(name);
|
||||
|
||||
int error = mkdirat(logRoot, name, 0700);
|
||||
if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", name);
|
||||
|
||||
log->dir = openat(logRoot, name, O_RDONLY | O_CLOEXEC);
|
||||
if (log->dir < 0) err(EX_CANTCREAT, "%s", name);
|
||||
|
||||
free(name);
|
||||
}
|
||||
|
||||
log->year = time->tm_year;
|
||||
log->month = time->tm_mon;
|
||||
log->day = time->tm_mday;
|
||||
|
||||
char path[sizeof("YYYY-MM-DD.log")];
|
||||
strftime(path, sizeof(path), "%F.log", time);
|
||||
int fd = openat(
|
||||
log->dir, path, O_RDWR | O_APPEND | O_CREAT | O_CLOEXEC, 0600
|
||||
);
|
||||
if (fd < 0) err(EX_CANTCREAT, "%s/%s", tag.name, path);
|
||||
|
||||
log->file = fdopen(fd, "a+");
|
||||
if (!log->file) err(EX_CANTCREAT, "%s/%s", tag.name, path);
|
||||
setlinebuf(log->file);
|
||||
|
||||
return log->file;
|
||||
}
|
||||
|
||||
enum { StampLen = sizeof("YYYY-MM-DDThh:mm:ss+hhmm") - 1 };
|
||||
|
||||
void logFmt(struct Tag tag, const time_t *ts, const char *format, ...) {
|
||||
if (logRoot < 0) return;
|
||||
|
||||
time_t t;
|
||||
if (!ts) {
|
||||
t = time(NULL);
|
||||
ts = &t;
|
||||
}
|
||||
|
||||
struct tm *time = localtime(ts);
|
||||
if (!time) err(EX_SOFTWARE, "localtime");
|
||||
|
||||
FILE *file = logFile(tag, time);
|
||||
|
||||
char stamp[StampLen + 1];
|
||||
strftime(stamp, sizeof(stamp), "%FT%T%z", time);
|
||||
fprintf(file, "[%s] ", stamp);
|
||||
if (ferror(file)) err(EX_IOERR, "%s", tag.name);
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
vfprintf(file, format, ap);
|
||||
va_end(ap);
|
||||
if (ferror(file)) err(EX_IOERR, "%s", tag.name);
|
||||
|
||||
fprintf(file, "\n");
|
||||
if (ferror(file)) err(EX_IOERR, "%s", tag.name);
|
||||
}
|
||||
|
||||
static void logRead(struct Tag tag, bool replay) {
|
||||
if (logRoot < 0) return;
|
||||
|
||||
time_t t = time(NULL);
|
||||
struct tm *time = localtime(&t);
|
||||
if (!time) err(EX_SOFTWARE, "localtime");
|
||||
|
||||
FILE *file = logFile(tag, time);
|
||||
rewind(file);
|
||||
|
||||
char *line = NULL;
|
||||
size_t cap = 0;
|
||||
ssize_t len;
|
||||
while (0 < (len = getline(&line, &cap, file))) {
|
||||
if (replay) {
|
||||
if (len < 1 + StampLen + 2 + 1) continue;
|
||||
line[len - 1] = '\0';
|
||||
uiFmt(tag, UICold, "\3%d%s", IRCGray, &line[1 + StampLen + 2]);
|
||||
} else {
|
||||
printf("%s", line);
|
||||
}
|
||||
}
|
||||
if (ferror(file)) err(EX_IOERR, "%s", tag.name);
|
||||
free(line);
|
||||
}
|
||||
|
||||
void logList(struct Tag tag) {
|
||||
logRead(tag, false);
|
||||
}
|
||||
|
||||
void logReplay(struct Tag tag) {
|
||||
logRead(tag, true);
|
||||
}
|
2
man.sh
2
man.sh
|
@ -1,2 +0,0 @@
|
|||
#!/bin/sh
|
||||
exec mandoc /usr/share/man/man1/catgirl.1 | LESSSECURE=1 less
|
186
pls.c
186
pls.c
|
@ -1,186 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
wchar_t *wcsnchr(const wchar_t *wcs, size_t len, wchar_t chr) {
|
||||
len = wcsnlen(wcs, len);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
if (wcs[i] == chr) return (wchar_t *)&wcs[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
wchar_t *wcsnrchr(const wchar_t *wcs, size_t len, wchar_t chr) {
|
||||
len = wcsnlen(wcs, len);
|
||||
for (size_t i = len - 1; i < len; --i) {
|
||||
if (wcs[i] == chr) return (wchar_t *)&wcs[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
wchar_t *ambstowcs(const char *src) {
|
||||
size_t len = mbsrtowcs(NULL, &src, 0, NULL);
|
||||
if (len == (size_t)-1) return NULL;
|
||||
|
||||
wchar_t *dst = malloc(sizeof(*dst) * (1 + len));
|
||||
if (!dst) return NULL;
|
||||
|
||||
len = mbsrtowcs(dst, &src, len, NULL);
|
||||
if (len == (size_t)-1) {
|
||||
free(dst);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dst[len] = L'\0';
|
||||
return dst;
|
||||
}
|
||||
|
||||
char *awcstombs(const wchar_t *src) {
|
||||
size_t len = wcsrtombs(NULL, &src, 0, NULL);
|
||||
if (len == (size_t)-1) return NULL;
|
||||
|
||||
char *dst = malloc(sizeof(*dst) * (1 + len));
|
||||
if (!dst) return NULL;
|
||||
|
||||
len = wcsrtombs(dst, &src, len, NULL);
|
||||
if (len == (size_t)-1) {
|
||||
free(dst);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dst[len] = '\0';
|
||||
return dst;
|
||||
}
|
||||
|
||||
char *awcsntombs(const wchar_t *src, size_t nwc) {
|
||||
size_t len = wcsnrtombs(NULL, &src, nwc, 0, NULL);
|
||||
if (len == (size_t)-1) return NULL;
|
||||
|
||||
char *dst = malloc(sizeof(*dst) * (1 + len));
|
||||
if (!dst) return NULL;
|
||||
|
||||
len = wcsnrtombs(dst, &src, nwc, len, NULL);
|
||||
if (len == (size_t)-1) {
|
||||
free(dst);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dst[len] = '\0';
|
||||
return dst;
|
||||
}
|
||||
|
||||
// From <https://en.cppreference.com/w/c/io/fwprintf#Notes>:
|
||||
//
|
||||
// While narrow strings provide snprintf, which makes it possible to determine
|
||||
// the required output buffer size, there is no equivalent for wide strings
|
||||
// (until C11's snwprintf_s), and in order to determine the buffer size, the
|
||||
// program may need to call swprintf, check the result value, and reallocate a
|
||||
// larger buffer, trying again until successful.
|
||||
//
|
||||
// snwprintf_s, unlike swprintf_s, will truncate the result to fit within the
|
||||
// array pointed to by buffer, even though truncation is treated as an error by
|
||||
// most bounds-checked functions.
|
||||
int vaswprintf(wchar_t **ret, const wchar_t *format, va_list ap) {
|
||||
*ret = NULL;
|
||||
|
||||
for (size_t cap = 2 * wcslen(format);; cap *= 2) {
|
||||
wchar_t *buf = realloc(*ret, sizeof(*buf) * (1 + cap));
|
||||
if (!buf) goto fail;
|
||||
*ret = buf;
|
||||
|
||||
va_list _ap;
|
||||
va_copy(_ap, ap);
|
||||
errno = EOVERFLOW; // vswprintf may not set errno.
|
||||
int len = vswprintf(*ret, 1 + cap, format, _ap);
|
||||
va_end(_ap);
|
||||
|
||||
if (len >= 0) return len;
|
||||
if (errno != EOVERFLOW) goto fail;
|
||||
}
|
||||
|
||||
fail:
|
||||
free(*ret);
|
||||
*ret = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int aswprintf(wchar_t **ret, const wchar_t *format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
int n = vaswprintf(ret, format, ap);
|
||||
va_end(ap);
|
||||
return n;
|
||||
}
|
||||
|
||||
static const char Base64[64] = {
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
};
|
||||
|
||||
size_t base64Size(size_t len) {
|
||||
return 1 + (len + 2) / 3 * 4;
|
||||
}
|
||||
|
||||
void base64(char *dst, const byte *src, size_t len) {
|
||||
size_t i = 0;
|
||||
while (len > 2) {
|
||||
dst[i++] = Base64[0x3F & (src[0] >> 2)];
|
||||
dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)];
|
||||
dst[i++] = Base64[0x3F & (src[1] << 2 | src[2] >> 6)];
|
||||
dst[i++] = Base64[0x3F & src[2]];
|
||||
src += 3;
|
||||
len -= 3;
|
||||
}
|
||||
if (len) {
|
||||
dst[i++] = Base64[0x3F & (src[0] >> 2)];
|
||||
if (len > 1) {
|
||||
dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)];
|
||||
dst[i++] = Base64[0x3F & (src[1] << 2)];
|
||||
} else {
|
||||
dst[i++] = Base64[0x3F & (src[0] << 4)];
|
||||
dst[i++] = '=';
|
||||
}
|
||||
dst[i++] = '=';
|
||||
}
|
||||
dst[i] = '\0';
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
int main() {
|
||||
assert(5 == base64Size(1));
|
||||
assert(5 == base64Size(2));
|
||||
assert(5 == base64Size(3));
|
||||
assert(9 == base64Size(4));
|
||||
|
||||
char b64[base64Size(3)];
|
||||
assert((base64(b64, (byte *)"cat", 3), !strcmp("Y2F0", b64)));
|
||||
assert((base64(b64, (byte *)"ca", 2), !strcmp("Y2E=", b64)));
|
||||
assert((base64(b64, (byte *)"c", 1), !strcmp("Yw==", b64)));
|
||||
|
||||
assert((base64(b64, (byte *)"\xFF\x00\xFF", 3), !strcmp("/wD/", b64)));
|
||||
assert((base64(b64, (byte *)"\x00\xFF\x00", 3), !strcmp("AP8A", b64)));
|
||||
}
|
||||
|
||||
#endif
|
30
sandman.1
30
sandman.1
|
@ -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
|
88
sandman.m
88
sandman.m
|
@ -1,88 +0,0 @@
|
|||
/* Copyright (C) 2019 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <err.h>
|
||||
#import <signal.h>
|
||||
#import <stdlib.h>
|
||||
#import <sysexits.h>
|
||||
#import <unistd.h>
|
||||
|
||||
static volatile sig_atomic_t sleeping;
|
||||
|
||||
static void sigchld(int sig) {
|
||||
(void)sig;
|
||||
int status;
|
||||
pid_t pid = wait(&status);
|
||||
if (pid < 0) _exit(EX_OSERR);
|
||||
if (WIFSIGNALED(status) && WTERMSIG(status) != SIGHUP) {
|
||||
_exit(128 + WTERMSIG(status));
|
||||
} else if (!sleeping) {
|
||||
_exit(WEXITSTATUS(status));
|
||||
}
|
||||
}
|
||||
|
||||
static pid_t spawn(char *argv[]) {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) err(EX_OSERR, "fork");
|
||||
if (pid) return pid;
|
||||
execvp(argv[0], argv);
|
||||
err(EX_NOINPUT, "%s", argv[0]);
|
||||
}
|
||||
|
||||
static pid_t pid;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) return EX_USAGE;
|
||||
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
struct sigaction action = {
|
||||
.sa_handler = sigchld,
|
||||
.sa_mask = mask,
|
||||
.sa_flags = SA_NOCLDSTOP | SA_RESTART,
|
||||
};
|
||||
sigaction(SIGCHLD, &action, NULL);
|
||||
|
||||
pid = spawn(&argv[1]);
|
||||
|
||||
[
|
||||
[[NSWorkspace sharedWorkspace] notificationCenter]
|
||||
addObserverForName: NSWorkspaceWillSleepNotification
|
||||
object: nil
|
||||
queue: nil
|
||||
usingBlock: ^(NSNotification *note) {
|
||||
(void)note;
|
||||
sleeping = 1;
|
||||
int error = kill(pid, SIGHUP);
|
||||
if (error) err(EX_UNAVAILABLE, "kill %d", pid);
|
||||
}
|
||||
];
|
||||
|
||||
[
|
||||
[[NSWorkspace sharedWorkspace] notificationCenter]
|
||||
addObserverForName: NSWorkspaceDidWakeNotification
|
||||
object: nil
|
||||
queue: nil
|
||||
usingBlock: ^(NSNotification *note) {
|
||||
(void)note;
|
||||
sleeping = 0;
|
||||
pid = spawn(&argv[1]);
|
||||
}
|
||||
];
|
||||
|
||||
[[NSApplication sharedApplication] run];
|
||||
}
|
13
sshd_config
13
sshd_config
|
@ -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
|
148
tab.c
148
tab.c
|
@ -1,148 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <err.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sysexits.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
static struct Entry {
|
||||
struct Tag tag;
|
||||
char *word;
|
||||
struct Entry *prev;
|
||||
struct Entry *next;
|
||||
} *head;
|
||||
|
||||
static void prepend(struct Entry *entry) {
|
||||
entry->prev = NULL;
|
||||
entry->next = head;
|
||||
if (head) head->prev = entry;
|
||||
head = entry;
|
||||
}
|
||||
|
||||
static void unlink(struct Entry *entry) {
|
||||
if (entry->prev) entry->prev->next = entry->next;
|
||||
if (entry->next) entry->next->prev = entry->prev;
|
||||
if (head == entry) head = entry->next;
|
||||
}
|
||||
|
||||
static void touch(struct Entry *entry) {
|
||||
if (head == entry) return;
|
||||
unlink(entry);
|
||||
prepend(entry);
|
||||
}
|
||||
|
||||
static struct Entry *find(struct Tag tag, const char *word) {
|
||||
for (struct Entry *entry = head; entry; entry = entry->next) {
|
||||
if (entry->tag.id != tag.id) continue;
|
||||
if (strcmp(entry->word, word)) continue;
|
||||
return entry;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void add(struct Tag tag, const char *word) {
|
||||
struct Entry *entry = malloc(sizeof(*entry));
|
||||
if (!entry) err(EX_OSERR, "malloc");
|
||||
|
||||
entry->tag = tag;
|
||||
entry->word = strdup(word);
|
||||
if (!entry->word) err(EX_OSERR, "strdup");
|
||||
|
||||
prepend(entry);
|
||||
}
|
||||
|
||||
void tabTouch(struct Tag tag, const char *word) {
|
||||
struct Entry *entry = find(tag, word);
|
||||
if (entry) {
|
||||
touch(entry);
|
||||
} else {
|
||||
add(tag, word);
|
||||
}
|
||||
}
|
||||
|
||||
void tabAdd(struct Tag tag, const char *word) {
|
||||
if (!find(tag, word)) add(tag, word);
|
||||
}
|
||||
|
||||
void tabReplace(struct Tag tag, const char *prev, const char *next) {
|
||||
struct Entry *entry = find(tag, prev);
|
||||
if (!entry) return;
|
||||
touch(entry);
|
||||
free(entry->word);
|
||||
entry->word = strdup(next);
|
||||
if (!entry->word) err(EX_OSERR, "strdup");
|
||||
}
|
||||
|
||||
static struct Entry *iter;
|
||||
|
||||
void tabRemove(struct Tag tag, const char *word) {
|
||||
for (struct Entry *entry = head; entry; entry = entry->next) {
|
||||
if (entry->tag.id != tag.id) continue;
|
||||
if (strcmp(entry->word, word)) continue;
|
||||
if (iter == entry) iter = entry->prev;
|
||||
unlink(entry);
|
||||
free(entry->word);
|
||||
free(entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void tabClear(struct Tag tag) {
|
||||
for (struct Entry *entry = head; entry; entry = entry->next) {
|
||||
if (entry->tag.id != tag.id) continue;
|
||||
if (iter == entry) iter = entry->prev;
|
||||
unlink(entry);
|
||||
free(entry->word);
|
||||
free(entry);
|
||||
}
|
||||
}
|
||||
|
||||
struct Tag tabTag(const char *word) {
|
||||
struct Entry *start = (iter ? iter->next : head);
|
||||
for (struct Entry *entry = start; entry; entry = entry->next) {
|
||||
if (strcmp(entry->word, word)) continue;
|
||||
iter = entry;
|
||||
return entry->tag;
|
||||
}
|
||||
iter = NULL;
|
||||
return TagNone;
|
||||
}
|
||||
|
||||
const char *tabNext(struct Tag tag, const char *prefix) {
|
||||
size_t len = strlen(prefix);
|
||||
struct Entry *start = (iter ? iter->next : head);
|
||||
for (struct Entry *entry = start; entry; entry = entry->next) {
|
||||
if (entry->tag.id != TagNone.id && entry->tag.id != tag.id) continue;
|
||||
if (strncasecmp(entry->word, prefix, len)) continue;
|
||||
iter = entry;
|
||||
return entry->word;
|
||||
}
|
||||
if (!iter) return NULL;
|
||||
iter = NULL;
|
||||
return tabNext(tag, prefix);
|
||||
}
|
||||
|
||||
void tabAccept(void) {
|
||||
if (iter) touch(iter);
|
||||
iter = NULL;
|
||||
}
|
||||
|
||||
void tabReject(void) {
|
||||
iter = NULL;
|
||||
}
|
53
tag.c
53
tag.c
|
@ -1,53 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <err.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sysexits.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
static struct {
|
||||
char *name[TagsLen];
|
||||
size_t len;
|
||||
} tags = {
|
||||
.name = { "<none>", "<status>", "<raw>" },
|
||||
.len = 3,
|
||||
};
|
||||
|
||||
const struct Tag TagNone = { 0, "<none>" };
|
||||
const struct Tag TagStatus = { 1, "<status>" };
|
||||
const struct Tag TagRaw = { 2, "<raw>" };
|
||||
|
||||
struct Tag tagFind(const char *name) {
|
||||
for (size_t id = 0; id < tags.len; ++id) {
|
||||
if (strcmp(tags.name[id], name)) continue;
|
||||
return (struct Tag) { id, tags.name[id] };
|
||||
}
|
||||
return TagNone;
|
||||
}
|
||||
|
||||
struct Tag tagFor(const char *name) {
|
||||
struct Tag tag = tagFind(name);
|
||||
if (tag.id != TagNone.id) return tag;
|
||||
if (tags.len == TagsLen) return TagStatus;
|
||||
|
||||
size_t id = tags.len++;
|
||||
tags.name[id] = strdup(name);
|
||||
if (!tags.name[id]) err(EX_OSERR, "strdup");
|
||||
return (struct Tag) { id, tags.name[id] };
|
||||
}
|
100
term.c
100
term.c
|
@ -1,100 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
static bool xterm;
|
||||
|
||||
void termInit(void) {
|
||||
char *term = getenv("TERM");
|
||||
xterm = term && !strncmp(term, "xterm", 5);
|
||||
}
|
||||
|
||||
void termNoFlow(void) {
|
||||
struct termios attr;
|
||||
int error = tcgetattr(STDIN_FILENO, &attr);
|
||||
if (error) return;
|
||||
attr.c_iflag &= ~IXON;
|
||||
attr.c_cc[VDISCARD] = _POSIX_VDISABLE;
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &attr);
|
||||
}
|
||||
|
||||
void termTitle(const char *title) {
|
||||
if (!xterm) return;
|
||||
printf("\33]0;%s\33\\", title);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void privateMode(const char *mode, bool set) {
|
||||
printf("\33[?%s%c", mode, (set ? 'h' : 'l'));
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void termMode(enum TermMode mode, bool set) {
|
||||
switch (mode) {
|
||||
break; case TermFocus: privateMode("1004", set);
|
||||
break; case TermPaste: privateMode("2004", set);
|
||||
}
|
||||
}
|
||||
|
||||
#define T(s, i) ((s) << 8 | (i))
|
||||
|
||||
enum { Esc = '\33' };
|
||||
|
||||
enum TermEvent termEvent(char ch) {
|
||||
static uint state = 0;
|
||||
switch (T(state, ch)) {
|
||||
case T(0, Esc): state = 1; return 0;
|
||||
case T(1, '['): state = 2; return 0;
|
||||
case T(2, 'I'): state = 0; return TermFocusIn;
|
||||
case T(2, 'O'): state = 0; return TermFocusOut;
|
||||
case T(2, '2'): state = 3; return 0;
|
||||
case T(3, '0'): state = 4; return 0;
|
||||
case T(4, '0'): state = 5; return 0;
|
||||
case T(5, '~'): state = 0; return TermPasteStart;
|
||||
case T(4, '1'): state = 6; return 0;
|
||||
case T(6, '~'): state = 0; return TermPasteEnd;
|
||||
default: state = 0; return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
|
||||
static bool testEvent(const char *str, enum TermEvent event) {
|
||||
enum TermEvent e = TermNone;
|
||||
for (size_t i = 0; i < strlen(str); ++i) {
|
||||
if (e) return false;
|
||||
e = termEvent(str[i]);
|
||||
}
|
||||
return (e == event);
|
||||
}
|
||||
|
||||
int main() {
|
||||
assert(testEvent("\33[I", TermFocusIn));
|
||||
assert(testEvent("\33[O", TermFocusOut));
|
||||
assert(testEvent("\33[200~", TermPasteStart));
|
||||
assert(testEvent("\33[201~", TermPasteEnd));
|
||||
}
|
||||
|
||||
#endif
|
615
ui.c
615
ui.c
|
@ -1,615 +0,0 @@
|
|||
/* Copyright (C) 2018, 2019 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _XOPEN_SOURCE_EXTENDED
|
||||
|
||||
#include <curses.h>
|
||||
#include <err.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sysexits.h>
|
||||
#include <wchar.h>
|
||||
#include <wctype.h>
|
||||
|
||||
#ifndef A_ITALIC
|
||||
#define A_ITALIC A_UNDERLINE
|
||||
#endif
|
||||
|
||||
#include "chat.h"
|
||||
#undef uiFmt
|
||||
|
||||
#define CTRL(ch) ((ch) & 037)
|
||||
enum { Esc = L'\33', Del = L'\177' };
|
||||
|
||||
static const int LogLines = 512;
|
||||
|
||||
static int lastLine(void) {
|
||||
return LINES - 1;
|
||||
}
|
||||
static int lastCol(void) {
|
||||
return COLS - 1;
|
||||
}
|
||||
static int logHeight(void) {
|
||||
return LINES - 2;
|
||||
}
|
||||
|
||||
struct Window {
|
||||
struct Tag tag;
|
||||
WINDOW *log;
|
||||
bool hot;
|
||||
bool mark;
|
||||
int scroll;
|
||||
uint unread;
|
||||
struct Window *prev;
|
||||
struct Window *next;
|
||||
};
|
||||
|
||||
static struct {
|
||||
struct Window *active;
|
||||
struct Window *other;
|
||||
struct Window *head;
|
||||
struct Window *tail;
|
||||
struct Window *tag[TagsLen];
|
||||
} windows;
|
||||
|
||||
static void windowAppend(struct Window *win) {
|
||||
if (windows.tail) windows.tail->next = win;
|
||||
win->prev = windows.tail;
|
||||
win->next = NULL;
|
||||
windows.tail = win;
|
||||
if (!windows.head) windows.head = win;
|
||||
windows.tag[win->tag.id] = win;
|
||||
}
|
||||
|
||||
static void windowInsert(struct Window *win, struct Window *next) {
|
||||
win->prev = next->prev;
|
||||
win->next = next;
|
||||
if (win->prev) win->prev->next = win;
|
||||
win->next->prev = win;
|
||||
if (!win->prev) windows.head = win;
|
||||
windows.tag[win->tag.id] = win;
|
||||
}
|
||||
|
||||
static void windowRemove(struct Window *win) {
|
||||
windows.tag[win->tag.id] = NULL;
|
||||
if (win->prev) win->prev->next = win->next;
|
||||
if (win->next) win->next->prev = win->prev;
|
||||
if (windows.head == win) windows.head = win->next;
|
||||
if (windows.tail == win) windows.tail = win->prev;
|
||||
}
|
||||
|
||||
static struct Window *windowFor(struct Tag tag) {
|
||||
struct Window *win = windows.tag[tag.id];
|
||||
if (win) {
|
||||
win->tag = tag;
|
||||
return win;
|
||||
}
|
||||
|
||||
win = calloc(1, sizeof(*win));
|
||||
if (!win) err(EX_OSERR, "calloc");
|
||||
|
||||
win->tag = tag;
|
||||
win->mark = true;
|
||||
win->scroll = LogLines;
|
||||
|
||||
win->log = newpad(LogLines, COLS);
|
||||
wsetscrreg(win->log, 0, LogLines - 1);
|
||||
scrollok(win->log, true);
|
||||
wmove(win->log, LogLines - 1, 0);
|
||||
|
||||
windowAppend(win);
|
||||
return win;
|
||||
}
|
||||
|
||||
static void windowResize(struct Window *win) {
|
||||
wresize(win->log, LogLines, COLS);
|
||||
wmove(win->log, LogLines - 1, lastCol());
|
||||
}
|
||||
|
||||
static void windowMark(struct Window *win) {
|
||||
win->mark = true;
|
||||
}
|
||||
static void windowUnmark(struct Window *win) {
|
||||
win->mark = false;
|
||||
win->unread = 0;
|
||||
win->hot = false;
|
||||
}
|
||||
|
||||
static void windowShow(struct Window *win) {
|
||||
if (windows.active) windowMark(windows.active);
|
||||
if (win) {
|
||||
touchwin(win->log);
|
||||
windowUnmark(win);
|
||||
}
|
||||
windows.other = windows.active;
|
||||
windows.active = win;
|
||||
}
|
||||
|
||||
static void windowClose(struct Window *win) {
|
||||
if (windows.active == win) windowShow(win->next ? win->next : win->prev);
|
||||
if (windows.other == win) windows.other = NULL;
|
||||
windowRemove(win);
|
||||
delwin(win->log);
|
||||
free(win);
|
||||
}
|
||||
|
||||
static void windowScroll(struct Window *win, int lines) {
|
||||
if (lines < 0) {
|
||||
if (win->scroll == logHeight()) return;
|
||||
if (win->scroll == LogLines) windowMark(win);
|
||||
win->scroll = MAX(win->scroll + lines, logHeight());
|
||||
} else {
|
||||
if (win->scroll == LogLines) return;
|
||||
win->scroll = MIN(win->scroll + lines, LogLines);
|
||||
if (win->scroll == LogLines) windowUnmark(win);
|
||||
}
|
||||
}
|
||||
|
||||
static void colorInit(void) {
|
||||
start_color();
|
||||
use_default_colors();
|
||||
if (COLORS < 16) {
|
||||
for (short pair = 0; pair < 0100; ++pair) {
|
||||
if (pair < 010) {
|
||||
init_pair(1 + pair, pair, -1);
|
||||
} else {
|
||||
init_pair(1 + pair, pair & 007, (pair & 070) >> 3);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (short pair = 0; pair < 0x100; ++pair) {
|
||||
if (pair < 0x10) {
|
||||
init_pair(1 + pair, pair, -1);
|
||||
} else {
|
||||
init_pair(1 + pair, pair & 0x0F, (pair & 0xF0) >> 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static attr_t colorAttr(short color) {
|
||||
if (color < 0) return A_NORMAL;
|
||||
if (COLORS < 16 && (color & 0x08)) return A_BOLD;
|
||||
return A_NORMAL;
|
||||
}
|
||||
static short colorPair(short color) {
|
||||
if (color < 0) return 0;
|
||||
if (COLORS < 16) return 1 + ((color & 0x70) >> 1 | (color & 0x07));
|
||||
return 1 + color;
|
||||
}
|
||||
|
||||
static struct {
|
||||
bool hide;
|
||||
WINDOW *status;
|
||||
WINDOW *input;
|
||||
} ui;
|
||||
|
||||
void uiInit(void) {
|
||||
initscr();
|
||||
cbreak();
|
||||
noecho();
|
||||
termInit();
|
||||
termNoFlow();
|
||||
def_prog_mode();
|
||||
colorInit();
|
||||
ui.status = newwin(1, COLS, 0, 0);
|
||||
ui.input = newpad(1, 512);
|
||||
keypad(ui.input, true);
|
||||
nodelay(ui.input, true);
|
||||
uiShow();
|
||||
}
|
||||
|
||||
static void uiResize(void) {
|
||||
wresize(ui.status, 1, COLS);
|
||||
for (struct Window *win = windows.head; win; win = win->next) {
|
||||
windowResize(win);
|
||||
}
|
||||
}
|
||||
|
||||
void uiShow(void) {
|
||||
ui.hide = false;
|
||||
termMode(TermFocus, true);
|
||||
uiDraw();
|
||||
}
|
||||
void uiHide(void) {
|
||||
ui.hide = true;
|
||||
termMode(TermFocus, false);
|
||||
endwin();
|
||||
}
|
||||
|
||||
void uiExit(int status) {
|
||||
uiHide();
|
||||
printf(
|
||||
"This program is AGPLv3 Free Software!\n"
|
||||
"Code is available from <" SOURCE_URL ">.\n"
|
||||
);
|
||||
exit(status);
|
||||
}
|
||||
|
||||
static int _;
|
||||
void uiDraw(void) {
|
||||
if (ui.hide) return;
|
||||
wnoutrefresh(ui.status);
|
||||
if (windows.active) {
|
||||
pnoutrefresh(
|
||||
windows.active->log,
|
||||
windows.active->scroll - logHeight(), 0,
|
||||
1, 0,
|
||||
lastLine() - 1, lastCol()
|
||||
);
|
||||
}
|
||||
int x;
|
||||
getyx(ui.input, _, x);
|
||||
pnoutrefresh(
|
||||
ui.input,
|
||||
0, MAX(0, x - lastCol() + 3),
|
||||
lastLine(), 0,
|
||||
lastLine(), lastCol()
|
||||
);
|
||||
doupdate();
|
||||
}
|
||||
|
||||
static const short Colors[] = {
|
||||
[IRCWhite] = 8 + COLOR_WHITE,
|
||||
[IRCBlack] = 0 + COLOR_BLACK,
|
||||
[IRCBlue] = 0 + COLOR_BLUE,
|
||||
[IRCGreen] = 0 + COLOR_GREEN,
|
||||
[IRCRed] = 8 + COLOR_RED,
|
||||
[IRCBrown] = 0 + COLOR_RED,
|
||||
[IRCMagenta] = 0 + COLOR_MAGENTA,
|
||||
[IRCOrange] = 0 + COLOR_YELLOW,
|
||||
[IRCYellow] = 8 + COLOR_YELLOW,
|
||||
[IRCLightGreen] = 8 + COLOR_GREEN,
|
||||
[IRCCyan] = 0 + COLOR_CYAN,
|
||||
[IRCLightCyan] = 8 + COLOR_CYAN,
|
||||
[IRCLightBlue] = 8 + COLOR_BLUE,
|
||||
[IRCPink] = 8 + COLOR_MAGENTA,
|
||||
[IRCGray] = 8 + COLOR_BLACK,
|
||||
[IRCLightGray] = 0 + COLOR_WHITE,
|
||||
};
|
||||
|
||||
static void addFormat(WINDOW *win, const struct Format *format) {
|
||||
attr_t attr = A_NORMAL;
|
||||
if (format->bold) attr |= A_BOLD;
|
||||
if (format->italic) attr |= A_ITALIC;
|
||||
if (format->underline) attr |= A_UNDERLINE;
|
||||
if (format->reverse) attr |= A_REVERSE;
|
||||
|
||||
short color = -1;
|
||||
if (format->fg != IRCDefault) color = Colors[format->fg];
|
||||
if (format->bg != IRCDefault) color |= Colors[format->bg] << 4;
|
||||
|
||||
wattr_set(win, attr | colorAttr(color), colorPair(color), NULL);
|
||||
waddnwstr(win, format->str, format->len);
|
||||
}
|
||||
|
||||
static int printWidth(const wchar_t *str, size_t len) {
|
||||
int width = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
if (iswprint(str[i])) width += wcwidth(str[i]);
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
static int addWrap(WINDOW *win, const wchar_t *str) {
|
||||
int lines = 0;
|
||||
struct Format format = { .str = str };
|
||||
formatReset(&format);
|
||||
|
||||
while (formatParse(&format, NULL)) {
|
||||
size_t word = 1 + wcscspn(&format.str[1], L" ");
|
||||
if (word < format.len) format.len = word;
|
||||
|
||||
int x, xMax;
|
||||
getyx(win, _, x);
|
||||
getmaxyx(win, _, xMax);
|
||||
if (xMax - x - 1 < printWidth(format.str, word)) {
|
||||
if (format.str[0] == L' ') {
|
||||
format.str++;
|
||||
format.len--;
|
||||
}
|
||||
waddch(win, '\n');
|
||||
lines++;
|
||||
}
|
||||
addFormat(win, &format);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
static void title(const struct Window *win) {
|
||||
int unread;
|
||||
char *str;
|
||||
int len = asprintf(&str, "%s%n (%u)", win->tag.name, &unread, win->unread);
|
||||
if (len < 0) err(EX_OSERR, "asprintf");
|
||||
if (!win->unread) str[unread] = '\0';
|
||||
termTitle(str);
|
||||
free(str);
|
||||
}
|
||||
|
||||
static void uiStatus(void) {
|
||||
wmove(ui.status, 0, 0);
|
||||
int num = 0;
|
||||
for (const struct Window *win = windows.head; win; win = win->next, ++num) {
|
||||
if (!win->unread && windows.active != win) continue;
|
||||
if (windows.active == win) title(win);
|
||||
int unread;
|
||||
wchar_t *str;
|
||||
int len = aswprintf(
|
||||
&str, L"%c\3%d %d %s %n(\3%02d%u\3%d) ",
|
||||
(windows.active == win ? IRCReverse : IRCReset), colorFor(win->tag),
|
||||
num, win->tag.name,
|
||||
&unread, (win->hot ? IRCWhite : colorFor(win->tag)), win->unread,
|
||||
colorFor(win->tag)
|
||||
);
|
||||
if (len < 0) err(EX_OSERR, "aswprintf");
|
||||
if (!win->unread) str[unread] = L'\0';
|
||||
addWrap(ui.status, str);
|
||||
free(str);
|
||||
}
|
||||
wclrtoeol(ui.status);
|
||||
}
|
||||
|
||||
static void uiShowWindow(struct Window *win) {
|
||||
windowShow(win);
|
||||
uiStatus();
|
||||
uiPrompt(false);
|
||||
}
|
||||
|
||||
void uiShowTag(struct Tag tag) {
|
||||
uiShowWindow(windowFor(tag));
|
||||
}
|
||||
|
||||
static void uiShowAuto(void) {
|
||||
struct Window *unread = NULL;
|
||||
struct Window *hot;
|
||||
for (hot = windows.head; hot; hot = hot->next) {
|
||||
if (hot->hot) break;
|
||||
if (!unread && hot->unread) unread = hot;
|
||||
}
|
||||
if (!hot && !unread) return;
|
||||
uiShowWindow(hot ? hot : unread);
|
||||
}
|
||||
|
||||
void uiShowNum(int num, bool relative) {
|
||||
struct Window *win = (relative ? windows.active : windows.head);
|
||||
if (num < 0) {
|
||||
for (; win; win = win->prev) if (!num++) break;
|
||||
} else {
|
||||
for (; win; win = win->next) if (!num--) break;
|
||||
}
|
||||
if (win) uiShowWindow(win);
|
||||
}
|
||||
|
||||
void uiMoveTag(struct Tag tag, int num, bool relative) {
|
||||
struct Window *win = windowFor(tag);
|
||||
windowRemove(win);
|
||||
struct Window *ins = (relative ? win : windows.head);
|
||||
if (num < 0) {
|
||||
for (; ins; ins = ins->prev) if (!num++) break;
|
||||
} else {
|
||||
if (relative) ins = ins->next;
|
||||
for (; ins; ins = ins->next) if (!num--) break;
|
||||
}
|
||||
ins ? windowInsert(win, ins) : windowAppend(win);
|
||||
uiStatus();
|
||||
}
|
||||
|
||||
void uiCloseTag(struct Tag tag) {
|
||||
windowClose(windowFor(tag));
|
||||
uiStatus();
|
||||
uiPrompt(false);
|
||||
}
|
||||
|
||||
static void notify(struct Tag tag, const wchar_t *str) {
|
||||
beep();
|
||||
if (!self.notify) return;
|
||||
|
||||
size_t len = 0;
|
||||
char buf[256];
|
||||
struct Format format = { .str = str };
|
||||
formatReset(&format);
|
||||
while (formatParse(&format, NULL)) {
|
||||
int n = snprintf(
|
||||
&buf[len], sizeof(buf) - len,
|
||||
"%.*ls", (int)format.len, format.str
|
||||
);
|
||||
if (n < 0) err(EX_OSERR, "snprintf");
|
||||
len += n;
|
||||
if (len >= sizeof(buf)) break;
|
||||
}
|
||||
eventPipe((const char *[]) { "notify-send", tag.name, buf, NULL });
|
||||
}
|
||||
|
||||
void uiLog(struct Tag tag, enum UIHeat heat, const wchar_t *str) {
|
||||
struct Window *win = windowFor(tag);
|
||||
int lines = 1;
|
||||
waddch(win->log, '\n');
|
||||
if (win->mark && heat > UICold) {
|
||||
if (!win->unread++) {
|
||||
lines++;
|
||||
waddch(win->log, '\n');
|
||||
}
|
||||
if (heat > UIWarm) {
|
||||
win->hot = true;
|
||||
notify(tag, str);
|
||||
}
|
||||
uiStatus();
|
||||
}
|
||||
lines += addWrap(win->log, str);
|
||||
if (win->scroll != LogLines) win->scroll -= lines;
|
||||
}
|
||||
|
||||
void uiFmt(struct Tag tag, enum UIHeat heat, const wchar_t *format, ...) {
|
||||
wchar_t *str;
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
vaswprintf(&str, format, ap);
|
||||
va_end(ap);
|
||||
if (!str) err(EX_OSERR, "vaswprintf");
|
||||
uiLog(tag, heat, str);
|
||||
free(str);
|
||||
}
|
||||
|
||||
static void keyCode(wchar_t code) {
|
||||
if (code == KEY_RESIZE) uiResize();
|
||||
struct Window *win = windows.active;
|
||||
if (!win) return;
|
||||
switch (code) {
|
||||
break; case KEY_UP: windowScroll(win, -1);
|
||||
break; case KEY_DOWN: windowScroll(win, +1);
|
||||
break; case KEY_PPAGE: windowScroll(win, -(logHeight() - 1));
|
||||
break; case KEY_NPAGE: windowScroll(win, +(logHeight() - 1));
|
||||
break; case KEY_LEFT: edit(win->tag, EditLeft, 0);
|
||||
break; case KEY_RIGHT: edit(win->tag, EditRight, 0);
|
||||
break; case KEY_HOME: edit(win->tag, EditHome, 0);
|
||||
break; case KEY_END: edit(win->tag, EditEnd, 0);
|
||||
break; case KEY_DC: edit(win->tag, EditDelete, 0);
|
||||
break; case KEY_BACKSPACE: edit(win->tag, EditBackspace, 0);
|
||||
break; case KEY_ENTER: edit(win->tag, EditEnter, 0);
|
||||
break; default: return;
|
||||
}
|
||||
uiStatus();
|
||||
}
|
||||
|
||||
static void keyMeta(wchar_t ch) {
|
||||
struct Window *win = windows.active;
|
||||
if (ch >= L'0' && ch <= L'9') uiShowNum(ch - L'0', false);
|
||||
if (ch == L'a') uiShowAuto();
|
||||
if (ch == L'/' && windows.other) uiShowWindow(windows.other);
|
||||
if (!win) return;
|
||||
switch (ch) {
|
||||
break; case L'b': edit(win->tag, EditBackWord, 0);
|
||||
break; case L'f': edit(win->tag, EditForeWord, 0);
|
||||
break; case L'\b': edit(win->tag, EditKillBackWord, 0);
|
||||
break; case L'd': edit(win->tag, EditKillForeWord, 0);
|
||||
break; case L'l': uiHide(); logList(win->tag);
|
||||
break; case L'm': uiLog(win->tag, UICold, L"");
|
||||
}
|
||||
}
|
||||
|
||||
static void keyChar(wchar_t ch) {
|
||||
struct Window *win = windows.active;
|
||||
if (ch == CTRL(L'L')) clearok(curscr, true);
|
||||
if (!win) return;
|
||||
switch (ch) {
|
||||
break; case CTRL(L'N'): uiShowNum(+1, true);
|
||||
break; case CTRL(L'P'): uiShowNum(-1, true);
|
||||
|
||||
break; case CTRL(L'A'): edit(win->tag, EditHome, 0);
|
||||
break; case CTRL(L'B'): edit(win->tag, EditLeft, 0);
|
||||
break; case CTRL(L'D'): edit(win->tag, EditDelete, 0);
|
||||
break; case CTRL(L'E'): edit(win->tag, EditEnd, 0);
|
||||
break; case CTRL(L'F'): edit(win->tag, EditRight, 0);
|
||||
break; case CTRL(L'K'): edit(win->tag, EditKillEnd, 0);
|
||||
break; case CTRL(L'U'): edit(win->tag, EditKill, 0);
|
||||
break; case CTRL(L'W'): edit(win->tag, EditKillBackWord, 0);
|
||||
|
||||
break; case CTRL(L'C'): edit(win->tag, EditInsert, IRCColor);
|
||||
break; case CTRL(L'O'): edit(win->tag, EditInsert, IRCBold);
|
||||
break; case CTRL(L'R'): edit(win->tag, EditInsert, IRCColor);
|
||||
break; case CTRL(L'S'): edit(win->tag, EditInsert, IRCReset);
|
||||
break; case CTRL(L'T'): edit(win->tag, EditInsert, IRCItalic);
|
||||
break; case CTRL(L'V'): edit(win->tag, EditInsert, IRCReverse);
|
||||
break; case CTRL(L'_'): edit(win->tag, EditInsert, IRCUnderline);
|
||||
|
||||
break; case L'\b': edit(win->tag, EditBackspace, 0);
|
||||
break; case L'\t': edit(win->tag, EditComplete, 0);
|
||||
break; case L'\n': edit(win->tag, EditEnter, 0);
|
||||
|
||||
break; default: if (iswprint(ch)) edit(win->tag, EditInsert, ch);
|
||||
}
|
||||
}
|
||||
|
||||
void uiRead(void) {
|
||||
if (ui.hide) uiShow();
|
||||
static bool meta;
|
||||
int ret;
|
||||
wint_t ch;
|
||||
enum TermEvent event;
|
||||
while (ERR != (ret = wget_wch(ui.input, &ch))) {
|
||||
if (ret == KEY_CODE_YES) {
|
||||
keyCode(ch);
|
||||
} else if (ch < 0200 && (event = termEvent((char)ch))) {
|
||||
struct Window *win = windows.active;
|
||||
switch (event) {
|
||||
break; case TermFocusIn: if (win) windowUnmark(win);
|
||||
break; case TermFocusOut: if (win) windowMark(win);
|
||||
break; default: {}
|
||||
}
|
||||
uiStatus();
|
||||
} else if (ch == Esc) {
|
||||
meta = true;
|
||||
continue;
|
||||
} else if (meta) {
|
||||
keyMeta(ch == Del ? '\b' : ch);
|
||||
} else {
|
||||
keyChar(ch == Del ? '\b' : ch);
|
||||
}
|
||||
meta = false;
|
||||
}
|
||||
uiPrompt(false);
|
||||
}
|
||||
|
||||
static bool isAction(struct Tag tag, const wchar_t *input) {
|
||||
if (tag.id == TagStatus.id || tag.id == TagRaw.id) return false;
|
||||
return !wcsncasecmp(input, L"/me ", 4);
|
||||
}
|
||||
|
||||
static bool isCommand(struct Tag tag, const wchar_t *input) {
|
||||
if (tag.id == TagStatus.id || tag.id == TagRaw.id) return true;
|
||||
if (input[0] != L'/') return false;
|
||||
const wchar_t *space = wcschr(&input[1], L' ');
|
||||
const wchar_t *extra = wcschr(&input[1], L'/');
|
||||
return !extra || (space && extra > space);
|
||||
}
|
||||
|
||||
void uiPrompt(bool nickChanged) {
|
||||
static wchar_t *promptMesg;
|
||||
static wchar_t *promptAction;
|
||||
if (nickChanged || !promptMesg || !promptAction) {
|
||||
free(promptMesg);
|
||||
free(promptAction);
|
||||
enum IRCColor color = colorGen(self.user);
|
||||
int len = aswprintf(&promptMesg, L"\3%d<%s>\3 ", color, self.nick);
|
||||
if (len < 0) err(EX_OSERR, "aswprintf");
|
||||
len = aswprintf(&promptAction, L"\3%d* %s\3 ", color, self.nick);
|
||||
if (len < 0) err(EX_OSERR, "aswprintf");
|
||||
}
|
||||
|
||||
const wchar_t *input = editHead();
|
||||
|
||||
wmove(ui.input, 0, 0);
|
||||
if (windows.active) {
|
||||
if (isAction(windows.active->tag, input) && editTail() >= &input[4]) {
|
||||
input = &input[4];
|
||||
addWrap(ui.input, promptAction);
|
||||
} else if (!isCommand(windows.active->tag, input)) {
|
||||
addWrap(ui.input, promptMesg);
|
||||
}
|
||||
}
|
||||
|
||||
int x = 0;
|
||||
struct Format format = { .str = input };
|
||||
formatReset(&format);
|
||||
while (formatParse(&format, editTail())) {
|
||||
if (format.split) getyx(ui.input, _, x);
|
||||
addFormat(ui.input, &format);
|
||||
}
|
||||
wclrtoeol(ui.input);
|
||||
wmove(ui.input, 0, x);
|
||||
}
|
111
url.c
111
url.c
|
@ -1,111 +0,0 @@
|
|||
/* Copyright (C) 2018 C. McEnroe <june@causal.agency>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sysexits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "chat.h"
|
||||
|
||||
static const char *Schemes[] = {
|
||||
"cvs:",
|
||||
"ftp:",
|
||||
"git:",
|
||||
"http:",
|
||||
"https:",
|
||||
"irc:",
|
||||
"ircs:",
|
||||
"magnet:",
|
||||
"sftp:",
|
||||
"ssh:",
|
||||
"svn:",
|
||||
"telnet:",
|
||||
"vnc:",
|
||||
};
|
||||
static const size_t SchemesLen = sizeof(Schemes) / sizeof(Schemes[0]);
|
||||
|
||||
struct Entry {
|
||||
size_t tag;
|
||||
char *url;
|
||||
};
|
||||
|
||||
enum { RingLen = 32 };
|
||||
static_assert(!(RingLen & (RingLen - 1)), "power of two RingLen");
|
||||
|
||||
static struct {
|
||||
struct Entry buf[RingLen];
|
||||
size_t end;
|
||||
} ring;
|
||||
|
||||
static void ringPush(struct Tag tag, const char *url, size_t len) {
|
||||
free(ring.buf[ring.end].url);
|
||||
ring.buf[ring.end].tag = tag.id;
|
||||
ring.buf[ring.end].url = strndup(url, len);
|
||||
if (!ring.buf[ring.end].url) err(EX_OSERR, "strndup");
|
||||
ring.end = (ring.end + 1) & (RingLen - 1);
|
||||
}
|
||||
|
||||
static struct Entry ringEntry(size_t i) {
|
||||
return ring.buf[(ring.end + i) & (RingLen - 1)];
|
||||
}
|
||||
|
||||
void urlScan(struct Tag tag, const char *str) {
|
||||
while (str[0]) {
|
||||
size_t len = 1;
|
||||
for (size_t i = 0; i < SchemesLen; ++i) {
|
||||
if (strncmp(str, Schemes[i], strlen(Schemes[i]))) continue;
|
||||
len = strcspn(str, " >\"");
|
||||
ringPush(tag, str, len);
|
||||
}
|
||||
str = &str[len];
|
||||
}
|
||||
}
|
||||
|
||||
void urlList(struct Tag tag) {
|
||||
uiHide();
|
||||
for (size_t i = 0; i < RingLen; ++i) {
|
||||
struct Entry entry = ringEntry(i);
|
||||
if (!entry.url || entry.tag != tag.id) continue;
|
||||
printf("%s\n", entry.url);
|
||||
}
|
||||
}
|
||||
|
||||
void urlOpenMatch(struct Tag tag, const char *substr) {
|
||||
for (size_t i = RingLen - 1; i < RingLen; --i) {
|
||||
struct Entry entry = ringEntry(i);
|
||||
if (!entry.url || entry.tag != tag.id) continue;
|
||||
if (!strstr(entry.url, substr)) continue;
|
||||
eventPipe((const char *[]) { "open", entry.url, NULL });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void urlOpenRange(struct Tag tag, size_t at, size_t to) {
|
||||
size_t argc = 1;
|
||||
const char *argv[2 + RingLen] = { "open" };
|
||||
size_t tagIndex = 0;
|
||||
for (size_t i = RingLen - 1; i < RingLen; --i) {
|
||||
struct Entry entry = ringEntry(i);
|
||||
if (!entry.url || entry.tag != tag.id) continue;
|
||||
if (tagIndex >= at && tagIndex < to) argv[argc++] = entry.url;
|
||||
tagIndex++;
|
||||
}
|
||||
argv[argc] = NULL;
|
||||
if (argc > 1) eventPipe(argv);
|
||||
}
|
Loading…
Reference in New Issue