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