From 93c516d8991c4b6935fc03a24317ea0785e78a93 Mon Sep 17 00:00:00 2001 From: btakahashi Date: Wed, 6 Oct 2004 12:14:03 +0000 Subject: [PATCH] Initial revision --- COPYING | 504 ++++++++++++++++++++++++++++++++++++++++++++++++ Makefile.in | 78 ++++++++ README | 50 +++++ configure.ac | 38 ++++ demo/boxshell.c | 120 ++++++++++++ inject.c | 245 +++++++++++++++++++++++ inject_csi.c | 309 +++++++++++++++++++++++++++++ inject_csi.h | 13 ++ rote-config.in | 33 ++++ rote.c | 209 ++++++++++++++++++++ rote.h | 274 ++++++++++++++++++++++++++ rote_keymap.c | 52 +++++ roteprivate.h | 31 +++ 13 files changed, 1956 insertions(+) create mode 100644 COPYING create mode 100644 Makefile.in create mode 100644 README create mode 100644 configure.ac create mode 100644 demo/boxshell.c create mode 100644 inject.c create mode 100644 inject_csi.c create mode 100644 inject_csi.h create mode 100644 rote-config.in create mode 100644 rote.c create mode 100644 rote.h create mode 100644 rote_keymap.c create mode 100644 roteprivate.h diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..b1e3f5a --- /dev/null +++ b/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..543bbe5 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,78 @@ +# Makefile for librote +# +# Copyright (c) 2004 Bruno T. C. de Oliveira +# +# LICENSE INFORMATION: +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2 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 +# General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Copyright (c) 2002 Bruno T. C. de Oliveira + +SOURCES=$(wildcard *.c) +HEADERS=$(wildcard *.h) + +OBJECTS=$(patsubst %.c,%.o,$(SOURCES)) + +ROTE_VERSION=@PACKAGE_VERSION@ +ROTE_SONAME=librote.so.0 + +CC=@CC@ +CFLAGS=@CFLAGS@ -Wall -fPIC +LIBS=@LIBS@ +LDFLAGS=@LDFLAGS@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ +bindir=@bindir@ + +all: librote.so.$(ROTE_VERSION) + +install: all + mkdir -p $(includedir)/rote + rm -f $(includedir)/rote/*.h + cp rote.h $(includedir)/rote + mkdir -p $(libdir) + cp librote.so.$(ROTE_VERSION) $(libdir) + cd $(libdir) && ln -sf librote.so.$(ROTE_VERSION) librote.so + cd $(libdir) && ln -sf librote.so.$(ROTE_VERSION) $(ROTE_SONAME) + chmod 755 rote-config + cp -p rote-config $(bindir) + @echo "-----------------------------------------------------------" + @echo "ROTE - Our Own Terminal Emulation Library v$(ROTE_VERSION)" + @echo + @echo "Include files installed at: $(includedir)" + @echo "Library files installed at: $(libdir)" + @echo "rote-config executable : $(bindir)/rote-config" + @echo + @echo "To find out what compiler arguments you should use to" + @echo "compile programs that use rote, use the rote-config" + @echo "program (make sure $(bindir) is in your path)." + @echo "-----------------------------------------------------------" + +librote.so.$(ROTE_VERSION): $(OBJECTS) + $(CC) $(CFLAGS) -shared -o $@ -Wl,-soname=$(ROTE_SONAME) $(OBJECTS) $(LDFLAGS) $(LIBS) + +.depends: $(SOURCES) $(HEADERS) + $(CC) $(CFLAGS) -MM $(SOURCES) >.depends + +-include .depends + +clean: + rm -f *.o .depends librote.so.* + +pristine: clean + rm -rf autom4te.cache configure config.status config.log Makefile rote-config + +.PHONY: clean all install pristine + diff --git a/README b/README new file mode 100644 index 0000000..7563eac --- /dev/null +++ b/README @@ -0,0 +1,50 @@ +ROTE - OUR OWN TERMINAL EMULATION LIBRARY +Copyright (c) 2004 Bruno T. C. de Oliveira +Licensed under the GNU Lesser General Public License +See the COPYING file for more details on the license terms. + +[[ AUTHOR AND PROJECT HOMEPAGE ]] +This library was written by Bruno Takahashi C. de Oliveira, a +Computer Science student at Universidade de São Paulo, Brazil. +The project home page is: + + http://rote.sourceforge.net + +[[ WHAT IS IT? ]] + +ROTE is a simple C library for VT102 terminal emulation. It allows the +programmer to set up virtual 'screens' and send them data. The virtual +screens will emulate the behavior of a VT102 terminal, interpreting +escape sequences, control characters and such. The library supports +ncurses as well so that you may render the virtual screen to the real +screen when you need to. + +[[ MORE DETAILS ]] + +There are several programs that do terminal emulation, such as xterm, rxvt, +screen and even the Linux console driver itself. However, it is not easy to +isolate their terminal emulation logic and put it in a module that can be +easily reused in other programs. That's where the ROTE library comes in. + +The goal of the ROTE library is to provide terminal emulation support +for C/C++ applications, making it possible to write programs that display +terminals in embedded windows within them, or even monitor the display +produced by other programs. + +The ROTE library does not depend on any other library (except libc, of course), +and ncurses support can be enabled or disabled at compile-time. With ncurses +support compiled in, the ROTE library is able to render the virtual screens +to the physical screen (actually any ncurses window) and can also translate +ncurses key codes to the escape sequences the Linux console would have +produced (and feed them into the terminal). Ncurses support is not mandatory +however, and ROTE will work fine without it, but in that case the application +must take care of drawing the terminal to the screen in whichever way it +sees fit. + +ROTE also encapsulates the functionality needed to execute a child process +using the virtual screen as the controlling terminal. It will handle the +creation of the pseudo-terminal and the child process. All the application +has to do is tell it the command to run in the terminal and call an update +function at regular intervals to allow the terminal to update itself. + + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..91fd994 --- /dev/null +++ b/configure.ac @@ -0,0 +1,38 @@ +dnl Process this file with autoconf to produce a configure script. + +AC_INIT([rote], 0.2.6) + +AC_SUBST(PACKAGE_NAME) +AC_SUBST(PACKAGE_VERSION) + +AC_CONFIG_SRCDIR([rote.c]) + +dnl Checks for programs. +AC_PROG_CC + +dnl Checks for header files. +AC_HEADER_STDC +AC_HEADER_STDBOOL +AC_CHECK_HEADERS([sys/types.h unistd.h stdlib.h string.h]) + +dnl Check for libraries +AC_CHECK_LIB([ncurses], [initscr], [], + AC_MSG_ERROR([Need ncurses to compile librote.])) +AC_CHECK_LIB([util], [forkpty], [], + AC_MSG_ERROR([Need util library (forkpty function) to compile librote.])) + +dnl Checks for library functions +AC_CHECK_FUNCS([memset select setenv]) +AC_FUNC_MALLOC +AC_FUNC_SELECT_ARGTYPES + +dnl Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_HEADER_TIME +AC_C_INLINE +AC_TYPE_PID_T +AC_TYPE_SIGNAL + +AC_CONFIG_FILES([Makefile rote-config]) +AC_OUTPUT + diff --git a/demo/boxshell.c b/demo/boxshell.c new file mode 100644 index 0000000..4ce2257 --- /dev/null +++ b/demo/boxshell.c @@ -0,0 +1,120 @@ +/* Just a simple example program that creates a terminal in a frame + * and lets the user interact with it. + * + * To compile: + * gcc -o boxshell boxshell.c $(rote-config --cflags --libs) + */ + +#include +#include +#include +#include + +static unsigned char getout = 0; +void sigchld(int signo) { getout = 1; } +int my_custom_handler(RoteTerm *rt, const char *es); + +int screen_w, screen_h; +WINDOW *term_win; + +int main() { + RoteTerm *rt; + int i, j, ch; + + signal(SIGCHLD, sigchld); + + initscr(); + noecho(); + start_color(); + raw(); + nodelay(stdscr, TRUE); /* prevents getch() from blocking; rather + * it will return ERR when there is no + * keypress available */ + + keypad(stdscr, TRUE); /* necessary to use rote_vt_keypress */ + getmaxyx(stdscr, screen_h, screen_w); + + /* initialize the color pairs the way rote_vt_draw expects it. You might + * initialize them differently, but in that case you would need + * to supply a custom conversion function for rote_vt_draw to + * call when setting attributes. The idea of this "default" mapping + * is to map (fg,bg) to the color pair bg * 8 + 7 - fg. This way, + * the pair (white,black) ends up mapped to 0, which means that + * it does not need a color pair (since it is the default). Since + * there are only 63 available color pairs (and 64 possible fg/bg + * combinations), we really have to save 1 pair by assigning no pair + * to the combination white/black. */ + for (i = 0; i < 8; i++) for (j = 0; j < 8; j++) + if (i != 7 || j != 0) + init_pair(j*8+7-i, i, j); + + /* paint the screen blue */ + attrset(COLOR_PAIR(32)); + for (i = 0; i < screen_h; i++) for (j = 0; j < screen_w; j++) addch(' '); + refresh(); + + /* create a window with a frame */ + term_win = newwin(22,72,1,4); + wattrset(term_win, COLOR_PAIR(7*8+7-0)); /* black over white */ + wborder(term_win, 0, 0, 0, 0, 0, 0, 0, 0); + mvwprintw(term_win, 0, 27, " Term In a Box "); + wrefresh(term_win); + + /* create the terminal and have it run bash */ + rt = rote_vt_create(20, 70); + rote_vt_forkpty(rt, "/bin/bash --login"); + + /* add a sample custom escape sequence handler... say we want to handle + * the sequence, say, \e{N}, which will change the application's background + * color to N (where N is a number between 0 and 7). */ + rote_vt_install_handler(rt, my_custom_handler); + + /* keep reading keypresses from the user and passing them to the terminal; + * also, redraw the terminal to the window at each iteration */ + ch = '\0'; + while (!getout) { + rote_vt_draw(rt, term_win, 1, 1, NULL); + wrefresh(term_win); + + ch = getch(); + if (ch != ERR) + rote_vt_keypress(rt, ch); /* pass the keypress for handling */ + } + + endwin(); + return 0; +} + + +int my_custom_handler(RoteTerm *rt, const char *es) { + int color; + int i, j; + + /* if the escape sequence does not begin with '{', we give up */ + if (*es != '{') return ROTE_HANDLERESULT_NOWAY; + + /* ok, now we know it begins with '{'. Now, if it does not end with '}', + * it is not yet complete */ + if (es[strlen(es)-1] != '}') return ROTE_HANDLERESULT_NOTYET; + + /* ok, the sequence is complete */ + color = atoi(es + 1); + if (color < 0 || color > 7) return false; /* don't recognize it */ + + /* paint the background with that color */ + attrset(COLOR_PAIR(color * 8)); + move(0, 0); + for (i = 0; i < screen_h; i++) for (j = 0; j < screen_w; j++) addch(' '); + touchwin(stdscr); + refresh(); + + /* touch term_win to force it to do a full redraw next time */ + touchwin(term_win); + + /* and redraw the terminal window */ + wrefresh(term_win); + + /* escape sequence was handled ok */ + return ROTE_HANDLERESULT_OK; +} + diff --git a/inject.c b/inject.c new file mode 100644 index 0000000..0ba7d69 --- /dev/null +++ b/inject.c @@ -0,0 +1,245 @@ +#include "rote.h" +#include "roteprivate.h" +#include "inject_csi.h" +#include + +static void cursor_line_down(RoteTerm *rt) { + int i; + rt->crow++; + rt->curpos_dirty = true; + if (rt->crow <= rt->pd->scrollbottom) return; + + /* must scroll the scrolling region up by 1 line, and put cursor on + * last line of it */ + rt->crow = rt->pd->scrollbottom; + + for (i = rt->pd->scrolltop; i < rt->pd->scrollbottom; i++) { + rt->line_dirty[i] = true; + memcpy(rt->cells[i], rt->cells[i+1], sizeof(RoteCell) * rt->cols); + } + + rt->line_dirty[rt->pd->scrollbottom] = true; + + /* clear last row of the scrolling region */ + for (i = 0; i < rt->cols; i++) { + rt->cells[rt->pd->scrollbottom][i].ch = 0x20; + rt->cells[rt->pd->scrollbottom][i].attr = 0x70; + } + +} + +static void cursor_line_up(RoteTerm *rt) { + int i; + rt->crow--; + rt->curpos_dirty = true; + if (rt->crow >= rt->pd->scrolltop) return; + + /* must scroll the scrolling region up by 1 line, and put cursor on + * first line of it */ + rt->crow = rt->pd->scrolltop; + + for (i = rt->pd->scrollbottom; i > rt->pd->scrolltop; i--) { + rt->line_dirty[i] = true; + memcpy(rt->cells[i], rt->cells[i-1], sizeof(RoteCell) * rt->cols); + } + + rt->line_dirty[rt->pd->scrolltop] = true; + + /* clear first row of the scrolling region */ + for (i = 0; i < rt->cols; i++) { + rt->cells[rt->pd->scrolltop][i].ch = 0x20; + rt->cells[rt->pd->scrolltop][i].attr = 0x70; + } + +} + +static inline void put_normal_char(RoteTerm *rt, char c) { + rt->cells[rt->crow][rt->ccol].ch = c; + rt->cells[rt->crow][rt->ccol].attr = rt->curattr; + rt->ccol++; + + rt->line_dirty[rt->crow] = true; + rt->curpos_dirty = true; + + if (rt->ccol >= rt->cols) { + rt->ccol = 0; + cursor_line_down(rt); + } +} + +static inline void put_graphmode_char(RoteTerm *rt, char c) { + char nc; + /* do some very pitiful translation to regular ascii chars */ + switch (c) { + case 'j': case 'k': case 'l': case 'm': case 'n': case 't': + case 'u': case 'v': case 'w': + nc = '+'; break; + case 'x': + nc = '|'; break; + default: + nc = '%'; + } + + put_normal_char(rt, nc); +} + +static inline void new_escape_sequence(RoteTerm *rt) { + rt->pd->escaped = true; + rt->pd->esbuf_len = 0; + rt->pd->esbuf[0] = '\0'; +} + +static inline void cancel_escape_sequence(RoteTerm *rt) { + rt->pd->escaped = false; + rt->pd->esbuf_len = 0; + rt->pd->esbuf[0] = '\0'; +} + +static void handle_control_char(RoteTerm *rt, char c) { + switch (c) { + case '\r': rt->ccol = 0; break; /* carriage return */ + case '\n': /* line feed */ + rt->ccol = 0; cursor_line_down(rt); + rt->curpos_dirty = true; + break; + case '\b': /* backspace */ + if (rt->ccol > 0) rt->ccol--; + rt->curpos_dirty = true; + break; + case '\t': /* tab */ + while (rt->ccol % 8) put_normal_char(rt, ' '); + break; + case '\x1B': /* begin escape sequence (aborting previous one if any) */ + new_escape_sequence(rt); + break; + case '\x0E': /* enter graphical character mode */ + rt->pd->graphmode = true; + break; + case '\x0F': /* exit graphical character mode */ + rt->pd->graphmode = false; + break; + case '\x9B': /* CSI character. Equivalent to ESC [ */ + new_escape_sequence(rt); + rt->pd->esbuf[rt->pd->esbuf_len++] = '['; + break; + case '\x18': case '\x1A': /* these interrupt escape sequences */ + cancel_escape_sequence(rt); + break; + case '\a': /* bell */ + /* do nothing for now... maybe a visual bell would be nice? */ + break; + #ifdef DEBUG + default: + fprintf(stderr, "Unrecognized control char: %d (^%c)\n", c, c + '@'); + break; + #endif + } +} + +static inline bool is_valid_csi_ender(char c) { + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '@' || c == '`'; +} + +static void try_interpret_escape_seq(RoteTerm *rt) { + char firstchar = rt->pd->esbuf[0]; + char lastchar = rt->pd->esbuf[rt->pd->esbuf_len-1]; + + if (!firstchar) return; /* too early to do anything */ + + if (rt->pd->handler) { + /* call custom handler */ + #ifdef DEBUG + fprintf(stderr, "Calling custom handler for ES <%s>.\n", rt->pd->esbuf); + #endif + + int answer = (*(rt->pd->handler))(rt, rt->pd->esbuf); + if (answer == ROTE_HANDLERESULT_OK) { + /* successfully handled */ + #ifdef DEBUG + fprintf(stderr, "Handler returned OK. Done with escape sequence.\n"); + #endif + + cancel_escape_sequence(rt); + return; + } + else if (answer == ROTE_HANDLERESULT_NOTYET) { + /* handler might handle it when more characters are appended to + * it. So for now we don't interpret it */ + #ifdef DEBUG + fprintf(stderr, "Handler returned NOTYET. Waiting for more chars.\n"); + #endif + + return; + } + + /* If we got here then answer == ROTE_HANDLERESULT_NOWAY */ + /* handler said it can't handle that escape sequence, + * but we can still try handling it ourselves, so + * we proceed normally. */ + #ifdef DEBUG + fprintf(stderr, "Handler returned NOWAY. Trying our handlers.\n"); + #endif + } + + /* interpret ESC-M as reverse line-feed */ + if (firstchar == 'M') { + cursor_line_up(rt); + cancel_escape_sequence(rt); + return; + } + + if (firstchar != '[' && firstchar != ']') { + /* unrecognized escape sequence. Let's forget about it. */ + #ifdef DEBUG + fprintf(stderr, "Unrecognized ES: <%s>\n", rt->pd->esbuf); + #endif + + cancel_escape_sequence(rt); + return; + } + + if (firstchar == '[' && is_valid_csi_ender(lastchar)) { + /* we have a csi escape sequence: interpret it */ + rote_es_interpret_csi(rt); + cancel_escape_sequence(rt); + } + else if (firstchar == ']' && lastchar == '\a') { + /* we have an xterm escape sequence: interpret it */ + + /* rote_es_interpret_xterm_es(rt); -- TODO!*/ + #ifdef DEBUG + fprintf(stderr, "Ignored XTerm ES.\n"); + #endif + cancel_escape_sequence(rt); + } + + /* if the escape sequence took up all available space and could + * not yet be parsed, abort it */ + if (rt->pd->esbuf_len + 1 >= ESEQ_BUF_SIZE) cancel_escape_sequence(rt); +} + +void rote_vt_inject(RoteTerm *rt, const char *data, int len) { + int i; + for (i = 0; i < len; i++, data++) { + if (*data == 0) continue; /* completely ignore NUL */ + if (*data >= 1 && *data <= 31) { + handle_control_char(rt, *data); + continue; + } + + if (rt->pd->escaped && rt->pd->esbuf_len < ESEQ_BUF_SIZE) { + /* append character to ongoing escape sequence */ + rt->pd->esbuf[rt->pd->esbuf_len] = *data; + rt->pd->esbuf[++rt->pd->esbuf_len] = 0; + + try_interpret_escape_seq(rt); + } + else if (rt->pd->graphmode) + put_graphmode_char(rt, *data); + else + put_normal_char(rt, *data); + } +} + diff --git a/inject_csi.c b/inject_csi.c new file mode 100644 index 0000000..2d3ba65 --- /dev/null +++ b/inject_csi.c @@ -0,0 +1,309 @@ +#include "inject_csi.h" +#include "roteprivate.h" +#include +#include + +#define MAX_CSI_ES_PARAMS 32 + +static inline void clamp_cursor_to_bounds(RoteTerm *rt) { + if (rt->crow < 0) rt->curpos_dirty = true, rt->crow = 0; + if (rt->ccol < 0) rt->curpos_dirty = true, rt->ccol = 0; + + if (rt->crow >= rt->rows) + rt->curpos_dirty = true, rt->crow = rt->rows - 1; + + if (rt->ccol >= rt->cols) + rt->curpos_dirty = true, rt->ccol = rt->cols - 1; +} + +/* interprets a 'set attribute' (SGR) CSI escape sequence */ +static void interpret_csi_SGR(RoteTerm *rt, int param[], int pcount) { + int i; + + if (pcount == 0) { + /* special case: reset attributes */ + rt->curattr = 0x70; + return; + } + + for (i = 0; i < pcount; i++) { + if (param[i] == 0) rt->curattr = 0x70; + else if (param[i] == 1 || param[i] == 2 || param[i] == 4) /* set bold */ + rt->curattr |= 0x80; + else if (param[i] == 5) rt->curattr |= 0x08; /* set blink */ + else if (param[i] == 7) rt->curattr = 0x07; /* reverse video */ + else if (param[i] >= 30 && param[i] <= 37) /* set fg */ + ROTE_ATTR_MOD_FG(rt->curattr, param[i] - 30); + else if (param[i] >= 40 && param[i] <= 47) /* set bg */ + ROTE_ATTR_MOD_BG(rt->curattr, param[i] - 40); + else if (param[i] == 39) /* reset foreground to default */ + ROTE_ATTR_MOD_FG(rt->curattr, 7); + else if (param[i] == 49) /* reset background to default */ + ROTE_ATTR_MOD_BG(rt->curattr, 0); + } +} + +/* interprets an 'erase display' (ED) escape sequence */ +static void interpret_csi_ED(RoteTerm *rt, int param[], int pcount) { + int r, c; + int start_row, start_col, end_row, end_col; + + /* decide range */ + if (pcount && param[0] == 2) + start_row = 0, start_col = 0, end_row = rt->rows - 1, + end_col = rt->cols - 1; + + else if (pcount && param[0] == 1) + start_row = 0, start_col = 0, end_row = rt->crow, + end_col = rt->ccol; + + else start_row = rt->crow, start_col = rt->ccol, + end_row = rt->rows - 1, end_col = rt->cols - 1; + + /* clean range */ + for (r = start_row; r <= end_row; r++) { + rt->line_dirty[r] = true; + + for (c = (r == start_row ? start_col : 0); + c <= (r == end_row ? end_col : rt->cols - 1); + c++) { + rt->cells[r][c].ch = 0x20; + rt->cells[r][c].attr = rt->curattr; + } + } +} + +/* interprets a 'move cursor' (CUP) escape sequence */ +static void interpret_csi_CUP(RoteTerm *rt, int param[], int pcount) { + if (pcount == 0) { + /* special case */ + rt->crow = rt->ccol = 0; + return; + } + else if (pcount < 2) return; /* malformed */ + + rt->crow = param[0] - 1; /* convert from 1-based to 0-based */ + rt->ccol = param[1] - 1; /* convert from 1-based to 0-based */ + + rt->curpos_dirty = true; + + clamp_cursor_to_bounds(rt); +} + +/* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL, + * CPL, CHA, HPR, VPA, VPR, HPA */ +static void interpret_csi_C(RoteTerm *rt, char verb, + int param[], int pcount) { + int n = (pcount && param[0] > 0) ? param[0] : 1; + + switch (verb) { + case 'A': rt->crow -= n; break; + case 'B': case 'e': rt->crow += n; break; + case 'C': case 'a': rt->ccol += n; break; + case 'D': rt->ccol -= n; break; + case 'E': rt->crow += n; rt->ccol = 0; break; + case 'F': rt->crow -= n; rt->ccol = 0; break; + case 'G': case '`': rt->ccol = param[0] - 1; break; + case 'd': rt->crow = param[0] - 1; break; + } + + rt->curpos_dirty = true; + clamp_cursor_to_bounds(rt); +} + +/* Interpret the 'erase line' escape sequence */ +static void interpret_csi_EL(RoteTerm *rt, int param[], int pcount) { + int erase_start, erase_end, i; + int cmd = pcount ? param[0] : 0; + + switch (cmd) { + case 1: erase_start = 0; erase_end = rt->ccol; break; + case 2: erase_start = 0; erase_end = rt->cols - 1; break; + default: erase_start = rt->ccol; erase_end = rt->cols - 1; break; + } + + for (i = erase_start; i <= erase_end; i++) { + rt->cells[rt->crow][i].ch = 0x20; + rt->cells[rt->crow][i].attr = rt->curattr; + } + + rt->line_dirty[rt->crow] = true; +} + +/* Interpret the 'insert blanks' sequence (ICH) */ +static void interpret_csi_ICH(RoteTerm *rt, int param[], int pcount) { + int n = (pcount && param[0] > 0) ? param[0] : 1; + int i; + for (i = rt->cols - 1; i >= rt->ccol + n; i--) + rt->cells[rt->crow][i] = rt->cells[rt->crow][i - n]; + for (i = rt->ccol; i < rt->ccol + n; i++) { + rt->cells[rt->crow][i].ch = 0x20; + rt->cells[rt->crow][i].attr = rt->curattr; + } + + rt->line_dirty[rt->crow] = true; +} + +/* Interpret the 'delete chars' sequence (DCH) */ +static void interpret_csi_DCH(RoteTerm *rt, int param[], int pcount) { + int n = (pcount && param[0] > 0) ? param[0] : 1; + int i; + for (i = rt->ccol; i < rt->cols; i++) { + if (i + n < rt->cols) + rt->cells[rt->crow][i] = rt->cells[rt->crow][i + n]; + else { + rt->cells[rt->crow][i].ch = 0x20; + rt->cells[rt->crow][i].attr = rt->curattr; + } + } + + rt->line_dirty[rt->crow] = true; +} + +/* Interpret an 'insert line' sequence (IL) */ +static void interpret_csi_IL(RoteTerm *rt, int param[], int pcount) { + int n = (pcount && param[0] > 0) ? param[0] : 1; + int i, j; + + for (i = rt->pd->scrollbottom; i >= rt->crow + n; i--) + memcpy(rt->cells[i], rt->cells[i - n], sizeof(RoteCell) * rt->cols); + + for (i = rt->crow; i < rt->crow + n && i <= rt->pd->scrollbottom; i++) { + rt->line_dirty[i] = true; + for (j = 0; j < rt->cols; j++) + rt->cells[i][j].ch = 0x20, rt->cells[i][j].attr = rt->curattr; + } + +} + +/* Interpret a 'delete line' sequence (DL) */ +static void interpret_csi_DL(RoteTerm *rt, int param[], int pcount) { + int n = (pcount && param[0] > 0) ? param[0] : 1; + int i, j; + + for (i = rt->crow; i <= rt->pd->scrollbottom; i++) { + rt->line_dirty[i] = true; + if (i + n <= rt->pd->scrollbottom) + memcpy(rt->cells[i], rt->cells[i+n], sizeof(RoteCell) * rt->cols); + else { + for (j = 0; j < rt->cols; j++) + rt->cells[i][j].ch = 0x20, rt->cells[i][j].attr = rt->curattr; + } + } +} + +/* Interpret an 'erase characters' (ECH) sequence */ +static void interpret_csi_ECH(RoteTerm *rt, int param[], int pcount) { + int n = (pcount && param[0] > 0) ? param[0] : 1; + int i; + + for (i = rt->ccol; i < rt->ccol + n && i < rt->cols; i++) { + rt->cells[rt->crow][i].ch = 0x20; + rt->cells[rt->crow][i].attr = rt->curattr; + } + + rt->line_dirty[rt->crow] = true; +} + +/* Interpret a 'set scrolling region' (DECSTBM) sequence */ +static void interpret_csi_DECSTBM(RoteTerm *rt, int param[], int pcount) { + int newtop, newbottom; + + if (!pcount) { + newtop = 0; + newbottom = rt->rows - 1; + } + else if (pcount < 2) return; /* malformed */ + else { + newtop = param[0] - 1; + newbottom = param[1] - 1; + } + + /* clamp to bounds */ + if (newtop < 0) newtop = 0; + if (newtop >= rt->rows) newtop = rt->rows - 1; + if (newbottom < 0) newbottom = 0; + if (newbottom >= rt->rows) newbottom = rt->rows - 1; + + /* check for range validity */ + if (newtop > newbottom) return; + rt->pd->scrolltop = newtop; + rt->pd->scrollbottom = newbottom; +} + +static void interpret_csi_SAVECUR(RoteTerm *rt, int param[], int pcount) { + rt->pd->saved_x = rt->ccol; + rt->pd->saved_y = rt->crow; +} + +static void interpret_csi_RESTORECUR(RoteTerm *rt, int param[], int pcount) { + rt->ccol = rt->pd->saved_x; + rt->crow = rt->pd->saved_y; + rt->curpos_dirty = true; +} + +void rote_es_interpret_csi(RoteTerm *rt) { + static int csiparam[MAX_CSI_ES_PARAMS]; + int param_count = 0; + const char *p = rt->pd->esbuf + 1; + char verb = rt->pd->esbuf[rt->pd->esbuf_len - 1]; + + if (!strncmp(rt->pd->esbuf, "[?", 2)) { /* private-mode CSI, ignore */ + #ifdef DEBUG + fprintf(stderr, "Ignoring private-mode CSI: <%s>\n", rt->pd->esbuf); + #endif + return; + } + + /* parse numeric parameters */ + while ((*p >= '0' && *p <= '9') || *p == ';') { + if (*p == ';') { + if (param_count >= MAX_CSI_ES_PARAMS) return; /* too long! */ + csiparam[param_count++] = 0; + } + else { + if (param_count == 0) csiparam[param_count++] = 0; + csiparam[param_count - 1] *= 10; + csiparam[param_count - 1] += *p - '0'; + } + + p++; + } + + /* delegate handling depending on command character (verb) */ + switch (verb) { + case 'm': /* it's a 'set attribute' sequence */ + interpret_csi_SGR(rt, csiparam, param_count); break; + case 'J': /* it's an 'erase display' sequence */ + interpret_csi_ED(rt, csiparam, param_count); break; + case 'H': case 'f': /* it's a 'move cursor' sequence */ + interpret_csi_CUP(rt, csiparam, param_count); break; + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'e': case 'a': case 'd': case '`': + /* it is a 'relative move' */ + interpret_csi_C(rt, verb, csiparam, param_count); break; + case 'K': /* erase line */ + interpret_csi_EL(rt, csiparam, param_count); break; + case '@': /* insert characters */ + interpret_csi_ICH(rt, csiparam, param_count); break; + case 'P': /* delete characters */ + interpret_csi_DCH(rt, csiparam, param_count); break; + case 'L': /* insert lines */ + interpret_csi_IL(rt, csiparam, param_count); break; + case 'M': /* delete lines */ + interpret_csi_DL(rt, csiparam, param_count); break; + case 'X': /* erase chars */ + interpret_csi_ECH(rt, csiparam, param_count); break; + case 'r': /* set scrolling region */ + interpret_csi_DECSTBM(rt, csiparam, param_count); break; + case 's': /* save cursor location */ + interpret_csi_SAVECUR(rt, csiparam, param_count); break; + case 'u': /* restore cursor location */ + interpret_csi_RESTORECUR(rt, csiparam, param_count); break; + #ifdef DEBUG + default: + fprintf(stderr, "Unrecogized CSI: <%s>\n", rt->pd->esbuf); break; + #endif + } +} + diff --git a/inject_csi.h b/inject_csi.h new file mode 100644 index 0000000..15fbde7 --- /dev/null +++ b/inject_csi.h @@ -0,0 +1,13 @@ +#ifndef btco_ROTE_inject_csi_h +#define btco_ROTE_inject_csi_h + +#include "rote.h" + +/* Interprets a CSI escape sequence stored in rt->pd->esbuf, + * changing rt to reflect the effect of the sequence. This function + * will not change rt->pd->esbuf, rt->pd->escaped or other escape-sequence + * related fields in it */ +void rote_es_interpret_csi(RoteTerm *rt); + +#endif + diff --git a/rote-config.in b/rote-config.in new file mode 100644 index 0000000..f5728af --- /dev/null +++ b/rote-config.in @@ -0,0 +1,33 @@ +#!/bin/sh + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ +version=@PACKAGE_VERSION@ + +if test $# -eq 0; then + echo "Usage: rote-config [--cflags] [--libs]" + echo " rote-config --version" + echo +fi + +while test $# -gt 0; do + case $1 in + --cflags) + echo "-I$includedir" + ;; + --libs) + echo "-L$libdir -lrote" + ;; + --version) + echo "ROTE version $version" + echo "Our Own Terminal Emulation Library" + echo + echo "Copyright (c) 2004 Bruno T. C. de Oliveira" + echo "All rights reserved" + ;; + esac + shift +done + diff --git a/rote.c b/rote.c new file mode 100644 index 0000000..e6b6bda --- /dev/null +++ b/rote.c @@ -0,0 +1,209 @@ +#include "rote.h" +#include "roteprivate.h" +#include +#include +#include +#include + +RoteTerm *rote_vt_create(int rows, int cols) { + RoteTerm *rt; + int i, j; + + if (rows <= 0 || cols <= 0) return NULL; + + if (! (rt = (RoteTerm*) malloc(sizeof(RoteTerm))) ) return NULL; + memset(rt, 0, sizeof(RoteTerm)); + + /* record dimensions */ + rt->rows = rows; + rt->cols = cols; + + /* create the cell matrix */ + rt->cells = (RoteCell**) malloc(sizeof(RoteCell*) * rt->rows); + for (i = 0; i < rt->rows; i++) { + /* create row */ + rt->cells[i] = (RoteCell*) malloc(sizeof(RoteCell) * rt->cols); + + /* fill row with spaces */ + for (j = 0; j < rt->cols; j++) { + rt->cells[i][j].ch = 0x20; /* a space */ + rt->cells[i][j].attr = 0x70; /* white text, black background */ + } + } + + /* allocate dirtiness array */ + rt->line_dirty = (bool*) malloc(sizeof(bool) * rt->rows); + + /* initialization of other public fields */ + rt->crow = rt->ccol = 0; + rt->curattr = 0x70; /* white text over black background */ + + /* allocate private data */ + rt->pd = (RoteTermPrivate*) malloc(sizeof(RoteTermPrivate)); + memset(rt->pd, 0, sizeof(RoteTermPrivate)); + + rt->pd->pty = -1; /* no pty for now */ + + /* initial scrolling area is the whole window */ + rt->pd->scrolltop = 0; + rt->pd->scrollbottom = rt->rows - 1; + + #ifdef DEBUG + fprintf(stderr, "Created a %d x %d terminal.\n", rt->rows, rt->cols); + #endif + + return rt; +} + +void rote_vt_destroy(RoteTerm *rt) { + int i; + if (!rt) return; + + free(rt->pd); + free(rt->line_dirty); + for (i = 0; i < rt->rows; i++) free(rt->cells[i]); + free(rt->cells); + free(rt); +} + +static void default_cur_set_attr(WINDOW *win, unsigned char attr) { + int cp = ROTE_ATTR_BG(attr) * 8 + 7 - ROTE_ATTR_FG(attr); + if (!cp) wattrset(win, A_NORMAL); + else wattrset(win, COLOR_PAIR(cp)); + + if (ROTE_ATTR_BOLD(attr)) wattron(win, A_BOLD); + if (ROTE_ATTR_BLINK(attr)) wattron(win, A_BLINK); +} + +static inline unsigned char ensure_printable(unsigned char ch) + { return ch >= 32 ? ch : 32; } + +void rote_vt_draw(RoteTerm *rt, WINDOW *win, int srow, int scol, + void (*cur_set_attr)(WINDOW*,unsigned char)) { + + int i, j; + rote_vt_update(rt); + + if (!cur_set_attr) cur_set_attr = default_cur_set_attr; + for (i = 0; i < rt->rows; i++) { + wmove(win, srow + i, scol); + for (j = 0; j < rt->cols; j++) { + (*cur_set_attr)(win, rt->cells[i][j].attr); + waddch(win, ensure_printable(rt->cells[i][j].ch)); + } + } + + wmove(win, srow + rt->crow, scol + rt->ccol); +} + +pid_t rote_vt_forkpty(RoteTerm *rt, const char *command) { + struct winsize ws; + pid_t childpid; + + ws.ws_row = rt->rows; + ws.ws_col = rt->cols; + ws.ws_xpixel = ws.ws_ypixel = 0; + + childpid = forkpty(&rt->pd->pty, NULL, NULL, &ws); + if (childpid < 0) return -1; + + if (childpid == 0) { + /* we are the child, running under the slave side of the pty. */ + + /* Cajole application into using linux-console-compatible escape + * sequences (which is what we are prepared to interpret) */ + setenv("TERM", "linux", 1); + + /* Now we will exec /bin/sh -c command. */ + execl("/bin/sh", "/bin/sh", "-c", command, NULL); + + fprintf(stderr, "\nexecl() failed.\nCommand: '%s'\n", command); + exit(127); /* error exec'ing */ + } + + /* if we got here we are the parent process */ + rt->childpid = childpid; + return childpid; +} + +void rote_vt_forsake_child(RoteTerm *rt) { + if (rt->pd->pty >= 0) close(rt->pd->pty); + rt->pd->pty = -1; + rt->childpid = 0; +} + +void rote_vt_update(RoteTerm *rt) { + fd_set ifs; + struct timeval tvzero; + char buf[512]; + int bytesread; + if (rt->pd->pty < 0) return; /* nothing to pump */ + + while (1) { + /* check if pty has something to say */ + FD_ZERO(&ifs); FD_SET(rt->pd->pty, &ifs); + tvzero.tv_sec = 0; tvzero.tv_usec = 0; + + if (select(rt->pd->pty + 1, &ifs, NULL, NULL, &tvzero) <= 0) + return; /* nothing to read, or select() failed */ + + /* read what we can. This is guaranteed not to block, since + * select() told us there was something to read. */ + bytesread = read(rt->pd->pty, buf, 512); + if (bytesread <= 0) return; + + /* inject the data into the terminal */ + rote_vt_inject(rt, buf, bytesread); + } +} + +void rote_vt_write(RoteTerm *rt, const char *data, int len) { + if (rt->pd->pty < 0) { + /* no pty, so just inject the data plain and simple */ + rote_vt_inject(rt, data, len); + return; + } + + /* write data to pty. Keep calling write() until we have written + * everything. */ + while (len > 0) { + int byteswritten = write(rt->pd->pty, data, len); + if (byteswritten < 0) { + /* very ugly way to inform the error. Improvements welcome! */ + static char errormsg[] = "\n(ROTE: pty write() error)\n"; + rote_vt_inject(rt, errormsg, strlen(errormsg)); + return; + } + + data += byteswritten; + len -= byteswritten; + } +} + +void rote_vt_install_handler(RoteTerm *rt, rote_es_handler_t handler) { + rt->pd->handler = handler; +} + +void *rote_vt_take_snapshot(RoteTerm *rt) { + int i; + int bytes_per_row = sizeof(RoteCell) * rt->cols; + void *buf = malloc(bytes_per_row * rt->rows); + void *ptr = buf; + + for (i = 0; i < rt->rows; i++, ptr += bytes_per_row) + memcpy(ptr, rt->cells[i], bytes_per_row); + + return buf; +} + +void rote_vt_restore_snapshot(RoteTerm *rt, void *snapbuf) { + int i; + int bytes_per_row = sizeof(RoteCell) * rt->cols; + + for (i = 0; i < rt->rows; i++, snapbuf += bytes_per_row) { + rt->line_dirty[i] = true; + memcpy(rt->cells[i], snapbuf, bytes_per_row); + } +} + + diff --git a/rote.h b/rote.h new file mode 100644 index 0000000..8972a61 --- /dev/null +++ b/rote.h @@ -0,0 +1,274 @@ +/* ROTE - Our Own Terminal Emulation library + * Copyright (c) 2004 Bruno T. C. de Oliveira + * All rights reserved + * + * 2004-08-25 + */ + +#ifndef btco_ROTE_rote_h +#define btco_ROTE_rote_h + +#include +#include +#include +#include + +/* Color codes: 0 = black, 1 = red, 2 = green, 3 = yellow, 4 = blue, + * 5 = magenta, 6 = cyan, 7 = white. + * + * An 'attribute' as used in this library means an 8-bit value that conveys + * a foreground color code, a background color code, and the bold + * and blink bits. Each cell in the virtual terminal screen is associated + * with an attribute that specifies its appearance. The bits of an attribute, + * from most significant to least significant, are + * + * bit: 7 6 5 4 3 2 1 0 + * content: S F F F H B B B + * | `-,-' | `-,-' + * | | | | + * | | | `----- 3-bit background color code (0 - 7) + * | | `--------- blink bit (if on, text is blinking) + * | `------------- 3-bit foreground color code (0 - 7) + * `----------------- bold bit + * + * It is however recommended that you use the provided macros rather + * than dealing with this format directly. + * + * Sometimes we will call the 'SFFF' nibble above the 'extended + * foreground color code', and the 'HBBB' nibble the 'extended background + * color code'. So the extended color codes are just the regular + * color codes except that they have an additional bit (the most significant + * bit) indicating bold/blink. + */ + +/* retrieve attribute fields */ +#define ROTE_ATTR_BG(attr) ((attr) & 0x07) +#define ROTE_ATTR_FG(attr) (((attr) & 0x70) >> 4) + +/* retrieve 'extended' color codes (see above for info) */ +#define ROTE_ATTR_XBG(attr) ((attr) & 0x0F) +#define ROTE_ATTR_XFG(attr) (((attr) & 0xF0) >> 4) + +/* set attribute fields. This requires attr to be an lvalue, and it will + * be evaluated more than once. Use with care. */ +#define ROTE_ATTR_MOD_BG(attr, newbg) attr &= 0xF8, attr |= (newbg) +#define ROTE_ATTR_MOD_FG(attr, newfg) attr &= 0x8F, attr |= ((newfg) << 4) +#define ROTE_ATTR_MOD_XBG(attr, newxbg) attr &= 0xF0, attr |= (newxbg) +#define ROTE_ATTR_MOD_XFG(attr, newxfg) attr &= 0x0F, attr |= ((newxfg) << 4) +#define ROTE_ATTR_MOD_BOLD(attr, boldbit) \ + attr &= 0x7F, attr |= (boldbit)?0x80:0x00 +#define ROTE_ATTR_MOD_BLINK(attr, blinkbit) \ + attr &= 0xF7, attr |= (boldbit)?0x08:0x00 + +/* these return non-zero for 'yes', zero for 'no'. Don't rely on them being + * any more specific than that (e.g. being exactly 1 for 'yes' or whatever). */ +#define ROTE_ATTR_BOLD(attr) ((attr) & 0x80) +#define ROTE_ATTR_BLINK(attr) ((attr) & 0x08) + +/* Represents each of the text cells in the terminal screen */ +typedef struct RoteCell_ { + unsigned char ch; /* >= 32, that is, control characters are not + * allowed to be on the virtual screen */ + + unsigned char attr; /* a color attribute, as described previously */ +} RoteCell; + +/* Declaration of opaque rote_Term_Private structure */ +typedef struct RoteTermPrivate_ RoteTermPrivate; + +/* Represents a virtual terminal. You may directly access the fields + * of this structure, but please pay close attention to the fields + * marked read-only or with special usage notes. */ +typedef struct RoteTerm_ { + int rows, cols; /* terminal dimensions, READ-ONLY. You + * can't resize the terminal by changing + * this (a segfault is about all you will + * accomplish). */ + + RoteCell **cells; /* matrix of cells. This + * matrix is indexed as cell[row][column] + * where 0 <= row < rows and + * 0 <= col < cols + * + * You may freely modify the contents of + * the cells. + */ + + int crow, ccol; /* cursor coordinates. READ-ONLY. */ + + unsigned char curattr; /* current attribute, that is the attribute + * that will be used for newly inserted + * characters */ + + pid_t childpid; /* pid of the child process running in the + * terminal; 0 for none. This is READ-ONLY. */ + + RoteTermPrivate *pd; /* private state data */ + + /* --- dirtiness flags: the following flags will be raised when the + * corresponding items are modified. They can only be unset by YOU + * (when, for example, you redraw the term or something) --- */ + bool curpos_dirty; /* whether cursor location has changed */ + bool *line_dirty; /* whether each row is dirty */ + /* --- end dirtiness flags */ +} RoteTerm; + +/* Creates a new virtual terminal with the given dimensions. You + * must destroy it with rote_vt_destroy after you are done with it. + * The terminal will be initially blank and the cursor will + * be at the top-left corner. + * + * Returns NULL on error. + */ +RoteTerm *rote_vt_create(int rows, int cols); + +/* Destroys a virtual terminal previously created with + * rote_vt_create. If rt == NULL, does nothing. */ +void rote_vt_destroy(RoteTerm *rt); + +/* Starts a forked process in the terminal. The parameter + * is a shell command to execute (it will be interpreted by '/bin/sh -c') + * Returns the pid of the forked process. + * + * Some useful reminders: If you want to be notified when child processes exit, + * you should handle the SIGCHLD signal. If, on the other hand, you want to + * ignore exitting child processes, you should set the SIGCHLD handler to + * SIG_IGN to prevent child processes from hanging around the system as 'zombie + * processes'. + * + * Continuing to write to a RoteTerm whose child process has died does not + * accomplish a lot, but is not an error and should not cause your program + * to crash or block indefinitely or anything of that sort :-) + * If, however, you want to be tidy and inform the RoteTerm that its + * child has died, call rote_vt_forsake_child when appropriate. + * + * If there is an error, returns -1. Notice that passing an invalid + * command will not cause an error at this level: the shell will try + * to execute the command and will exit with status 127. You can catch + * that by installing a SIGCHLD handler if you want. + */ +pid_t rote_vt_forkpty(RoteTerm *rt, const char *command); + +/* Disconnects the RoteTerm from its forked child process. This function + * should be called when the child process dies or something of the sort. + * It is not strictly necessary to call this function, but it is + * certainly tidy. */ +void rote_vt_forsake_child(RoteTerm *rt); + +/* Does some data plumbing, that is, sees if the sub process has + * something to write to the terminal, and if so, write it. If you + * called rote_vt_fork to start a forked process, you must call + * this function regularly to update the terminal. + * + * This function will not block, that is, if there is no data to be + * read from the child process it will return immediately. */ +void rote_vt_update(RoteTerm *rt); + +/* Puts data into the terminal: if there is a forked process running, + * the data will be sent to it. If there is no forked process, + * the data will simply be injected into the terminal (as in + * rote_vt_inject) */ +void rote_vt_write(RoteTerm *rt, const char *data, int length); + +/* Inject data into the terminal. needs NOT be 0-terminated: + * its length is solely determined by the parameter. Please + * notice that this writes directly to the terminal, that is, + * this function does NOT send the data to the forked process + * running in the terminal (if any). For that, you might want + * to use rote_vt_write. + */ +void rote_vt_inject(RoteTerm *rt, const char *data, int length); + +/* Paints the virtual terminal screen on the given window, putting + * the top-left corner at the given position. The cur_set_attr + * function must set the curses attributes given a Rote attribute + * byte. It should, for example, do wattrset(win, COLOR_PAIR(n)) where + * n is the colorpair appropriate for the attribute and such. + * + * If you pass NULL for cur_set_attr, the default implementation will + * set the color pair given by (bg * 8 + 7 - fg), which seems to be + * a common mapping, and the bold and blink attributes will be mapped + * to A_BOLD and A_BLINK. + * + * At the end of the function, the cursor will be left where the virtual + * cursor of the terminal is supposed to be. + * + * This function does not call wrefresh(win); you have to do that yourself. + * This function automatically calls rote_vt_update prior to drawing + * so that the drawn contents are accurate. + */ +void rote_vt_draw(RoteTerm *rt, WINDOW *win, int startrow, int startcol, + void (*cur_set_attr)(WINDOW *win, unsigned char attr)); + +/* Indicates to the terminal that the given key has been pressed. + * This will cause the terminal to rote_vt_write() the appropriate + * escape sequence for that key (that is, the escape sequence + * that the linux text-mode console would produce for it). The argument, + * keycode, must be a CURSES EXTENDED KEYCODE, the ones you get + * when you use keypad(somewin, TRUE) (see man page). */ +void rote_vt_keypress(RoteTerm *rt, int keycode); + +/* Takes a snapshot of the current contents of the terminal and + * saves them to a dynamically allocated buffer. Returns a pointer + * to the newly created buffer, which you can pass to + * rote_vt_restore_snapshot. Caller is responsible for free()'ing when + * the snapshot is no longer needed. */ +void *rote_vt_take_snapshot(RoteTerm *rt); + +/* Restores a snapshot previously taken with rote_vt_take_snapshot. + * This function does NOT free() the passed buffer */ +void rote_vt_restore_snapshot(RoteTerm *rt, void *snapbuf); + +/* Declaration of custom escape sequence callback type. See the + * rote_vt_add_es_handler function for more info */ +typedef int (*rote_es_handler_t)(RoteTerm *rt, const char *es); + +/* Installs a custom escape sequence handler for the given RoteTerm. + * The handler will be called by the library every time it tries to + * recognize an escape sequence; depending on the return value of the + * handler, it will proceed in a different manner. See the description + * of the possible return values (ROTE_HANDLERESULT_* constants) below + * for more info. + * + * This handler will be called EACH TIME THE ESCAPE SEQUENCE BUFFER + * RECEIVES A CHARACTER. Therefore, it must execute speedily in order + * not to create too heavy a performance penalty. In particular, the + * writer of the handler should take care to quickly test for invalid + * or incomplete escape sequences before trying to do more elaborate + * parsing. + * + * The handler will NOT be called with an empty escape sequence (i.e. + * one in which only the initial ESC was received). + * + * The custom handler receives the terminal it pertains to and the + * escape sequence as a string (without the initial escape character). + * + * The handler may of course modify the terminal as it sees fit, taking + * care not to corrupt it of course (in particular, it should appropriately + * raise the line_dirty[] and curpos_dirty flags to indicate what it has + * changed). + */ +void rote_vt_install_handler(RoteTerm *rt, rote_es_handler_t handler); + +/* Possible return values for the custom handler function and their + * meanings: */ +#define ROTE_HANDLERESULT_OK 0 /* means escape sequence was handled */ + +#define ROTE_HANDLERESULT_NOTYET 1 /* means the escape sequence was not + * recognized yet, but there is hope that + * it still will once more characters + * arrive (i.e. it is not yet complete). + * + * The library will thus continue collecting + * characters and calling the handler as + * each character arrives until + * either OK or NOWAY is returned. + */ + +#define ROTE_HANDLERESULT_NOWAY 2 /* means the escape sequence was not + * recognized, and there is no chance + * that it will even if more characters + * are added to it. */ + +#endif + diff --git a/rote_keymap.c b/rote_keymap.c new file mode 100644 index 0000000..3ecfc40 --- /dev/null +++ b/rote_keymap.c @@ -0,0 +1,52 @@ +#include "rote.h" + +#include +#include +#include + +static const char *keytable[KEY_MAX+1]; +static int initialized = 0; + +static void keytable_init(); + +void rote_vt_keypress(RoteTerm *rt, int keycode) { + char c = (char) keycode; + + if (!initialized) keytable_init(); + + if (keycode >= 0 && keycode < KEY_MAX && keytable[keycode]) + rote_vt_write(rt, keytable[keycode], strlen(keytable[keycode])); + else + rote_vt_write(rt, &c, 1); /* not special, just write it */ +} + +static void keytable_init() { + initialized = 1; + memset(keytable, 0, KEY_MAX+1 * sizeof(const char*)); + + keytable['\n'] = "\r"; + keytable[KEY_UP] = "\e[A"; + keytable[KEY_DOWN] = "\e[B"; + keytable[KEY_RIGHT] = "\e[C"; + keytable[KEY_LEFT] = "\e[D"; + keytable[KEY_BACKSPACE] = "\b"; + keytable[KEY_HOME] = "\e[1~"; + keytable[KEY_IC] = "\e[2~"; + keytable[KEY_DC] = "\e[3~"; + keytable[KEY_END] = "\e[4~"; + keytable[KEY_PPAGE] = "\e[5~"; + keytable[KEY_NPAGE] = "\e[6~"; + keytable[KEY_SUSPEND] = "\x1A"; /* Ctrl+Z gets mapped to this */ + keytable[KEY_F(1)] = "\e[[A"; + keytable[KEY_F(2)] = "\e[[B"; + keytable[KEY_F(3)] = "\e[[C"; + keytable[KEY_F(4)] = "\e[[D"; + keytable[KEY_F(5)] = "\e[[E"; + keytable[KEY_F(6)] = "\e[17~"; + keytable[KEY_F(7)] = "\e[18~"; + keytable[KEY_F(8)] = "\e[19~"; + keytable[KEY_F(9)] = "\e[20~"; + keytable[KEY_F(10)] = "\e[21~"; +} + + diff --git a/roteprivate.h b/roteprivate.h new file mode 100644 index 0000000..e052cdb --- /dev/null +++ b/roteprivate.h @@ -0,0 +1,31 @@ +#ifndef btco_ROTE_vtstate_h +#define btco_ROTE_vtstate_h + +#define ESEQ_BUF_SIZE 128 /* size of escape sequence buffer */ +#define MAX_CUSTOM_ES_HANDLERS 32 + +/* Terminal private data */ +struct RoteTermPrivate_ { + bool escaped; /* whether we are currently reading an + * escape sequence */ + + bool graphmode; /* whether terminal is in graphical + * character mode or not */ + + int scrolltop, scrollbottom; /* current scrolling region of terminal */ + int saved_x, saved_y; /* saved cursor position */ + + char esbuf[ESEQ_BUF_SIZE]; /* 0-terminated string. Does NOT include + * the initial escape (\x1B) character. */ + int esbuf_len; /* length of buffer. The following property + * is always kept: esbuf[esbuf_len] == '\0' */ + + int pty; /* file descriptor for the pty attached to + * this terminal. -1 if none. */ + + /* custom escape sequence handler */ + rote_es_handler_t handler; +}; + +#endif + -- 2.20.1