├── .github
└── workflows
│ └── c-cpp.yml
├── .gitignore
├── HISTORY
├── INSTALL
├── ISSUES
├── LICENSE
├── Makefile,v
├── Makefile.am
├── README.rst
├── TODO
├── action.c
├── autogen.sh
├── bindings.c
├── build-aux
└── package-version
├── configure.ac
├── cset.c
├── handler.c
├── smtx-main.c
├── smtx.c
├── smtx.h
├── smtx.ti
├── smtx.txt
├── test-coverage
├── test-describe.c
├── test-main.c
├── test-shell
├── test-unit.c
├── test-unit.h
├── vtparser.c
└── vtparser.h
/.github/workflows/c-cpp.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: bootstrap
17 | run: autoreconf -ivf
18 | - name: configure
19 | run: ./configure
20 | - name: make
21 | run: make
22 | - name: make dist
23 | run: make dist
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Makefile
2 | Makefile.in
3 | aclocal.m4
4 | ar-lib
5 | autom4te.cache/
6 | build/
7 | compile
8 | config.guess
9 | config.h.in
10 | config.sub
11 | configure
12 | depcomp
13 | install-sh
14 | ltmain.sh
15 | missing
16 | m4/
17 | smtx.1
18 | test-driver
19 |
--------------------------------------------------------------------------------
/HISTORY:
--------------------------------------------------------------------------------
1 |
2 |
3 | smtx is derived from mtm (git@github.com:deadpixi/mtm.git). See the git
4 | log for details.
5 |
6 | What is the motivation for smtx?
7 |
8 | Originally, I was annoyed at the diffculty of pane management in tmux. I would
9 | typically only use one pane per window, with occasionally (rarely) 2 panes. It
10 | is just too difficult to manipulate panes. I experimented with dvtm, and got as
11 | far as https://github.com/wrp/dvtm/tree/mvtm but was not happy with where that
12 | was going. While doing that development, I realized that what I wanted was
13 | modes and the ability to scroll horizontally in a window. I wanted to be able
14 | to use the multiplexer on my phone, so I needed the pty to be wider than the
15 | physical screen. While trying to decide on a name, I discovered mtm and shifted
16 | over to using it because the code base is simpler than dvtm. I played around with
17 | different behaviors of keystrokes in various modes. Pretty sure now that what I
18 | want is the ability to easily swap out keybindings. Right now, playing with the
19 | idea of having one keybinding in which '<>' will scroll the pty horizontally
20 | while all other keys pass through. Perhaps also have a mode in which 'hjkl' is
21 | used to navigate. But the key is that it must be very simple to change key
22 | bindings to make the terminal usable on a device with limited keyboard (like
23 | termux on Android). So the ultimate answer to the existential question is
24 | that smtx provides a window into ptys that are larger than the phsical
25 | device on which it is run. Also, the ability to easily swap out preset
26 | layouts or generate layouts dynamically. For example,
27 | to access the preset layout with 5 screens, you could do: CMD-5v
28 | (or printf '\033[60;1:.5 .25:1 .5:1 .75:1 1:1\007') to generate
29 | a layout that looks like:
30 |
31 | +--------------------------------------+---------------------------------------+
32 | | | |
33 | | | |
34 | | +---------------------------------------+
35 | | | |
36 | | | |
37 | | +---------------------------------------+
38 | | | |
39 | | | |
40 | | +---------------------------------------+
41 | | | |
42 | | | |
43 | +--------------------------------------+---------------------------------------+
44 |
45 | By generating layouts on the fly with an osc string, it is trivial to generate
46 | scripts to manipulate the windows.
47 |
--------------------------------------------------------------------------------
/INSTALL:
--------------------------------------------------------------------------------
1 |
2 | To build from the git repo, you will need to install the autotools (autoconf,
3 | libtool, and automake), asciidoctor, rcs, and probably other packages that I am
4 | forgetting about at the moment. It will be simpler to download a source
5 | tarball and build from that. As I write this document there are no
6 | official source tarballs since no official release has yet been made, so
7 | acaqiring a source tarball will be problematic. Perhaps a release will be
8 | made in the near future.
9 |
10 | This package is intended to be usable in the standard way. If you have
11 | met all the dependencies, just cd into the top level directory and run 'make'.
12 | This is a slight divergence from the usual autoconfiscated package, but should
13 | work. The git repo includes a Makefile,v which 'make' should recognize. The
14 | initial invocation of make will checkout a Makefile (using rcs) from the
15 | Makefile,v which will be used to generate a build directory and run the typical
16 | command chain. If you don't want to abuse the autotools this way, just ignore
17 | the Makefile,v and run:
18 |
19 | autoreconf -ivf
20 | ./configure --prefix=/p/a/t/h
21 | make
22 |
23 |
24 | The test suite is currently fragile. At the moment, it mostly works in
25 | a debian docker image on macos, but breaks completely elsewhere. In the
26 | debian docker image, typically 1 or 2 of the 51 tests will fail. IOW,
27 | don't expect 'make check' to be reliable.
28 |
--------------------------------------------------------------------------------
/ISSUES:
--------------------------------------------------------------------------------
1 |
2 | BUGS:
3 | Test suite is broken. Tests sporadically pass in a debian docker image
4 | on macos, but I get consistent failures in other platforms.
5 |
6 | Syntax highlighting doesn't work in vim.
7 |
8 | This is dog slow on macos. top regularly reports 100% cpu. Should
9 | stop using select and switch to kqueue. Probably not worth the
10 | effort to use libevent or libev.
11 |
12 | Need to handle memory allocation errors more thoroughly.
13 |
14 | Make tput rep work. eg, tput rep w 5 should write 5 'w' to term,
15 | but the parameters do not seem to be getting sent properly. We get
16 | argc == 1 and argv[0] == 5 - 1, but the w is chomped. Note that this
17 | is the only terminfo entry that uses %c, and I suspect there is a bug
18 | in vtparser
19 |
20 | Figure out why I am getting an extra line at the bottom of screen in
21 | termux. Note this happens outside of smtx, so it is not our bug to
22 | fix, but may be a terminfo issue. It needs to be understood.
23 |
24 | As of 61c41fe0f1dd73829315c959b69cf44df2af102f, the test suite hangs
25 | on macos. Note that all the tests in esctest.py also timeout.
26 |
27 | test_ich consistently fails on macos. This seems to
28 | be an issue with terminfo. On macos:
29 | $ infocmp $TERM | grep '\
'
30 | dl=\E[%p1%dM, dl1=\E[M, ech=\E[%p1%dX, ed=\E[J, el=\E[K,
31 | $ tput dl 5 | xxd
32 | 00000000: 1b5b 4d .[M
33 | but on debian:
34 | $ infocmp $TERM | grep '\'
35 | dl=\E[%p1%dM, dl1=\E[M, ech=\E[%p1%dX, ed=\E[J, el=\E[K,
36 | $ tput dl 5 | xxd
37 | 00000000: 1b5b 354d .[5M
38 | tput is not passing the parameter, so test ich on macos only deletes
39 | one line.
40 |
41 | Title bars are wonky with zero height canvas.
42 |
43 | csr does not work well with multple canvasses. Probably the
44 | correct thing to do is to remove csr functionality.
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/Makefile,v:
--------------------------------------------------------------------------------
1 | head 1.1;
2 | access;
3 | symbols;
4 | locks; strict;
5 | comment @# @;
6 |
7 |
8 | 1.1
9 | date 2020.10.19.16.14.18; author williamp; state Exp;
10 | branches;
11 | next ;
12 |
13 |
14 | desc
15 | @A stub Makefile to bootstrap the project
16 | @
17 |
18 |
19 | 1.1
20 | log
21 | @Initial revision
22 | @
23 | text
24 | @# A stub Makefile used to bootstrap.
25 |
26 | srcdir := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
27 | BUILDDIR := $(srcdir)build/$(shell uname -s)/$(shell uname -m)
28 |
29 | .PHONY: all check clean dist distclean install loc release version
30 | all: $(BUILDDIR) $(BUILDDIR)/Makefile
31 | cd $(BUILDDIR) && make $(MAKEFLAGS) all
32 |
33 | release: $(BUILDDIR)
34 | -cd $(BUILDDIR) && make $(MAKEFLAGS) distclean
35 | CFLAGS='-O2 -DNDEBUG' make $(MAKEFLAGS) all
36 | cd $(BUILDDIR) && make $(MAKEFLAGS) version && make $(MAKEFLAGS) dist
37 |
38 | configure:
39 | autoreconf -ivf
40 |
41 | $(BUILDDIR)/Makefile: configure
42 | cd $(BUILDDIR) && $(srcdir)configure CFLAGS="$${CFLAGS---coverage -g -O0}"
43 |
44 | $(BUILDDIR):
45 | mkdir -p $(BUILDDIR)
46 |
47 | loc:
48 | @@cd $(BUILDDIR) && \
49 | for file in action handler smtx-main vtparser smtx; do \
50 | printf assert:; grep 'assert(' $(srcdir)$${file}.c | wc -l; \
51 | gcov $${file}.c $$(test -f .libs/$${file}.gcda \
52 | && printf -- '-o .libs'); \
53 | done | tr "':" ' '| awk ' \
54 | /^assert/ { assert = $$2 } \
55 | /^File/ {file=$$2; gsub(".*/", "", file)} \
56 | /Lines executed/{c = $$5 - assert; \
57 | printf "%32s:\t%s\t(%s)\n", file, c, $$3; t+=c} \
58 | END{printf "%32s:\t%s\n", "Total", t}'
59 |
60 | install check clean dist distclean version: $(BUILDDIR) $(BUILDDIR)/Makefile
61 | cd $(BUILDDIR) && make $(MAKEFLAGS) $@@
62 | @
63 |
--------------------------------------------------------------------------------
/Makefile.am:
--------------------------------------------------------------------------------
1 |
2 | bin_PROGRAMS = smtx
3 | man1_MANS = smtx.1
4 | ACLOCAL_AMFLAGS = -I m4
5 |
6 | LDADD = libsmtx.la
7 | noinst_LTLIBRARIES = libsmtx.la
8 | libsmtx_la_SOURCES = vtparser.c smtx-main.c cset.c handler.c action.c test-describe.c \
9 | bindings.c
10 | check_PROGRAMS = test-main
11 | AM_TESTS_ENVIRONMENT = LC_ALL=en_US.UTF-8; export LC_ALL;
12 | TESTS = test-shell test-main test-coverage
13 | test_main_SOURCES = test-main.c test-unit.c
14 | test_main_DEPENDENCIES = smtx
15 |
16 | CLEANFILES = *.gcda *.gcno *.gcov version *.1
17 | EXTRA_DIST = build-aux/package-version version test-shell test-coverage smtx.ti \
18 | smtx.1 smtx.txt
19 | noinst_HEADERS = vtparser.h smtx.h test-unit.h
20 |
21 | version: $(DIST_SOURCES) $(DIST_COMMON)
22 | { cd $(top_srcdir) && ./autogen.sh && build-aux/package-version >&3; } 3> version
23 |
24 | install-data-hook:
25 | mkdir -p $(DESTDIR)$(sysconfdir)/terminfo
26 | tic -o $(DESTDIR)$(sysconfdir)/terminfo -s -x $(top_srcdir)/smtx.ti
27 |
28 | uninstall-hook:
29 | rm -rf $(DESTDIR)$(sysconfdir)/terminfo/s/smtx
30 | rm -rf $(DESTDIR)$(sysconfdir)/terminfo/s/smtx-256color
31 |
32 | smtx.1: smtx.txt
33 | @asciidoctor -a ver=${PACKAGE_VERSION} -b manpage $< 2> /dev/null \
34 | || echo 'This page blank since asciidoctor was not available' > $@
35 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Introduction
2 | ============
3 |
4 | smtx -- Simple Modal Terminal Multiplexer
5 |
6 | Designed for simplicity and ease of use in a limited environment (eg,
7 | on a small physical device), smtx provides provides ptys that are wider
8 | than the physical device and the ability to have multiple views in
9 | different locations of the pty.
10 |
11 | https://terminalizer.com/view/28083a105080
12 | https://terminalizer.com/view/e38467005081
13 |
14 | Quickstart
15 | ==========
16 |
17 | When first started, `smtx` creates a single window with a pty running
18 | the program specified in SHELL. The window that is created will fill
19 | the physical screen, and the underlying pty that it is viewing will have
20 | a width of at least 80. The `CMD` keysequence (default is `CTRL+g`)
21 | will put smtx in `command` mode in which key sequences are interpreted
22 | to manipulate the windows. Press `RETURN` to exit command mode.
23 | Press `CMD` to exit command mode and send `CMD` to the underlying pty.
24 | In command mode, create new windows using `c` or `C`. Use `hjkl` to
25 | navigate between windows. Use `<` and `>` to horizontally scroll the
26 | underlying pty. Most commands accept a count, so you could create 5 new
27 | windows with `5c` and move down N times with `Nj`. (In the follwoing, `N`
28 | will represent an arbitrary integer). Choose one of the preset layouts
29 | with `Nv`. To attach a different pty to the current window, use `Na`.
30 | To swap the pty in the current window with the pty in a different
31 | window, use `NS`. To move the focus to the window with pty N, use `Ng`.
32 | To transpose the orientation of the current window, use `T`. To
33 | close the currently focused window (the underlying pty is not
34 | affected), use `x`. To destroy all of the ptys and exit, use `0x`.
35 |
36 | Features
37 | ========
38 |
39 | smtx is modal, so you can enter multiple commands from command mode without
40 | typing the `CMD` keysequence multiple times. From command mode, you can
41 | create 5 new windows with `ccccc`, or `5c`. There are several preset layouts,
42 | so you can get a layout of 5 windows with `5v`.
43 | You can recursively convert an axial split to a sagittal split with `T`
44 | (transpose), and you can discard the currently focused window and all
45 | its children with `x`. To attach the current window to a different pty,
46 | use `a`. To swap the pty of the current window with a pty in a different
47 | window, use `s`. Change the width of the pty in the currectly focused window
48 | with `W`.
49 |
50 | You can also generate a window layout with an osc sequence.
51 | To generate a layout with seven windows, you could do::
52 |
53 | printf '\033]60;.5:.5 .25:.66 .5:.66 .5:.83 .5:1 1:.25 1:1\007'
54 |
55 | where each coordinate pair represents the lower right hand corner of the window
56 | as a fraction of the full screen (note that order matters).
57 |
58 | Windows
59 | =======
60 |
61 | New windows are created in `command` mode with `create`, which is by
62 | default bound to the keystrokes `c` and `C`. To navigate among the
63 | windows use `j`, `k`, `l`, and `h`. The navigation is not directly related to
64 | the physical layout on the screen, but navigates the tree structure of the
65 | extant windows. Each window belongs to one "canvas",
66 | which is simply a node in the tree used to keep track of windows.
67 | The root canvas fills the entire physical screen and contains the window
68 | in the upper left. Each canvas can have 0, 1, or 2 children. If the root
69 | window is split on the axial plane (ie, split top to bottom), the root
70 | canvas is said to be of "type 0" and has one child. If the root window
71 | is split on the sagittal plane (ie, split left to right), the root
72 | canvas is of "type 1". That is, a layout with 3 windows in which the
73 | left half of the screen is full height and the right half of the screen
74 | is split into 2 half-height windows indicates a root canvas of type 1.
75 | In that layout, the root canvas has one child (the right half of the screen),
76 | and that child (which is a type 0 canvas) has one child. The window navigation
77 | follows this tree structure naturally.
78 | `h` and `l` move the cursor to the left and right child of the
79 | currently focused canvas. `k` moves to the parent canvas, and `j` moves to the
80 | primary child; left for type 0, and right for type 1.
81 |
82 |
83 | Usage
84 | =====
85 |
86 | Usage is simple::
87 |
88 | smtx [-c ctrl-key] [-s history-size] [-t terminal-type] [-v] [-w width]
89 |
90 | The `-t` flag tells smtx what terminal type to advertise itself as.
91 | (This just controls what the `TERM` environment variable is set to.)
92 |
93 | The `-c` flag lets you specify a keyboard character to use as the "command
94 | prefix" for smtx when modified with *control* (see below). By default,
95 | this is `g`.
96 |
97 | The `-s` flag controls the amount of scrollback saved for each terminal.
98 |
99 | The `-w` flag sets the minimum width for newly created ptys (default is 80).
100 |
101 | Ths `-v` flag causes smtx to print its version and exit.
102 |
103 | Once inside smtx, things pretty much work like any other terminal. However,
104 | smtx lets you split up the terminal into multiple virtual terminals.
105 |
106 | At any given moment, exactly one virtual terminal is *focused*. It is
107 | to this terminal that keyboad input is sent. The focused terminal is
108 | indicated by the location of the cursor.
109 |
110 | The following commands are recognized in smtx when in command mode:
111 |
112 | h/j/k/l/Up/Down/Left/Right Arrow
113 | Move the focus to the virtual terminal in the window of the canvas
114 | that is the child/parent of
115 | the currently focused terminal.
116 |
117 | c / C
118 | Split the focused virtual terminal in half horizontally/vertically,
119 | creating a new virtual terminal to the right/below. The old virtual
120 | terminal retains the focus.
121 |
122 | b/f
123 | Scroll the screen back/forward half a screenful, or recenter the
124 | screen on the actual terminal.
125 |
126 | -/|
127 | Change the size of the current window to be the specifiec percentage
128 | of the enclosing canvas. eg, '25|' will make the current window use
129 | 25% of the horizontal space of the canvas.
130 |
131 | = Recursively rebalance windows. By itself, rebalance in both
132 | directions. 1= and 2= will rebalance only horizontally or
133 | vertically, respectively.
134 |
135 | W
136 | Set the width of the focused pty. eg, to set the width of the currently
137 | focused pty to 120, enter command mode and type `120W`
138 |
139 | [0-9] Set a command count.
140 |
141 | (Note that these keybindings can be changed at compile time, and that the
142 | above list is incomplete and subject to change.)
143 |
144 | Compatibility
145 | =============
146 |
147 | The `smtx` Terminal Types
148 | ------------------------
149 | smtx comes with a terminfo description file called smtx.ti. This file
150 | describes all of the features supported by smtx.
151 |
152 | If you want to install this terminal type, use the `tic` compiler that
153 | comes with ncurses::
154 |
155 | tic -s -x smtx.ti
156 |
157 |
158 | Using these terminfo entries allows programs to use the full power of smtx's
159 | terminal emulation, but it is entirely optional. If the terminfo file is
160 | not installed, smtx will use reasonable defaults.
161 |
162 | Copyright and License
163 | =====================
164 |
165 | Copyright 2016-2019 Rob King
166 |
167 | Copyright 2020-2023 William Pursell
168 |
169 | This program is free software: you can redistribute it and/or modify
170 | it under the terms of the GNU General Public License as published by
171 | the Free Software Foundation, either version 3 of the License, or
172 | (at your option) any later version.
173 |
174 | This program is distributed in the hope that it will be useful,
175 | but WITHOUT ANY WARRANTY; without even the implied warranty of
176 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
177 | GNU General Public License for more details.
178 |
179 | You should have received a copy of the GNU General Public License
180 | along with this program. If not, see .
181 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 |
2 | TODO:
3 | Make the preset keybindings modifiable at startup.
4 | (eg, implement bind_key as osc)
5 |
6 | Copy-mode, with stack of registers and ability to edit.
7 | Or, maybe just have a binding (y) that dumps the current content
8 | of the scrollback region to a file like ~/.smtx-pid-timestamp
9 | Perhaps use (e) to edit the file (eg, spawn $EDITOR), then (p)
10 | to paste it.
11 |
12 | Replace select() with kqueue()/epoll()
13 |
14 | Enable a mode to read input and interpret key sequences. ie,
15 | a filter to convert sequences like "abcdxx^H^Hef" to "abcdef"
16 | See: https://stackoverflow.com/questions/67415977/is-there-a-shell-utility-to-cleanup-interactive-tty-sessions
17 |
18 |
19 |
20 | MAYBE TODO:
21 |
22 | Make it easy to swap bindings. eg, so that hjkl could be used for
23 | scrolling in non-full screen mode. Maybe have labelled bindings,
24 | so perhaps 'a or 'b would select binding a or b. Would be simpler
25 | to use numbers, so 1B or 2B could select bindings 1 or 2. It seems
26 | hjkl would be better for scrolling than <> and fb
27 |
28 | Configure bindings from a startup file or ?
29 |
30 | Speed things up for hidden windows. If a pty is not visible
31 | on the display, we should (?) read a big chunk of data and store
32 | it but not actually display anything until the pty is added to
33 | a canvas. This may be a bad idea since we would lose escape
34 | sequences, but overall I like the idea.
35 |
36 | Multi-key bindings (?)
37 |
38 | Add character in title bar to indicate mode. Need to make
39 | title bar more functional in general, so this thought should
40 | probably be consumed by that. Probably want to be able to send
41 | arbitrary format strings in through an escape sequence. Note
42 | that we currently can set the "title" of the window via an osc
43 | sequence, but that title is only a portion of the title bar.
44 | Probably want the ability to control the format of the title bar.
45 | Also, the title bar/row should probably be a subwindow rather
46 | than just consuming one line of the main window. All the '-1'
47 | in the code gets confusing.
48 |
49 | Make it possible for windows to overlap. (Almost certainly don't do this.)
50 |
51 | Implement method to save the current layout into a register.
52 |
53 | Make an action on that moves to the next canvas.
54 |
55 | In full-screen mode, make hjkl scroll the pty.
56 |
57 | -1v should make the focused canvas the root and keep
58 | the layout below it instead of making it single screen. Perhaps
59 | 0v should attempt to layout all windows at one line each.
60 |
61 | speed up the grep() in the tests. We read one byte at a time because
62 | the row validation and layout validation is crazy fragile. If we make
63 | it more robust, we can probably read larger data buffers.
64 |
65 | Allocate a chunk of struct pty on startup, or just use a static array
66 | of size 512 or 1024 and stop callocing individual elements. That should
67 | cleanup the implementation of new_pty() a bit.
68 |
--------------------------------------------------------------------------------
/action.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 - 2019 Rob King
3 | * Copyright 2020 - 2023 William Pursell
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | #include "smtx.h"
19 |
20 | /* Index of a pty is based on its fd */
21 | #define IDX(t) ((t)->fd - 2)
22 |
23 | /* Attach pty p to the canvas n. */
24 | static void
25 | attach_pty(struct canvas *n, struct pty *p)
26 | {
27 | n->p->count -= 1;
28 | n->p = p;
29 | p->count += 1;
30 | S.reshape = 1; /* Need to adjust row count of pty */
31 | }
32 |
33 | /*
34 | * Attach the pty with id matching the user selected index
35 | * to the currently focused canvas.
36 | */
37 | void
38 | attach(void)
39 | {
40 | for (struct pty *t = S.p; t != NULL; t = t->next) {
41 | if (IDX(t) == S.count) {
42 | attach_pty(S.f, t);
43 | return;
44 | }
45 | }
46 | check(0, errno = 0, "No pty exists with id %d", S.count);
47 | }
48 |
49 | /* Count the canvassas below the focus point and reset split points */
50 | static const char *default_balance_arg[] = { "-|", "-", "|", "-|" };
51 | void
52 | balance(const char *arg)
53 | {
54 | int c = MIN(S.count + 1, 3);
55 | const char *a = (*arg == '=') ? default_balance_arg[c] : arg;
56 | for ( ; *a != '\0'; a += 1) {
57 | int d = *a == '|';
58 | int count = 0;
59 | for (struct canvas *n = S.f; n != NULL; n = n->c[d]) {
60 | count += 1;
61 | }
62 | for (struct canvas *n = S.f; n != NULL; n = n->c[d]) {
63 | *(d ? &n->split.x : &n->split.y) = 1.0 / count--;
64 | n->p->ws.ws_row = 0; /* rows is set during resize */
65 | }
66 | }
67 | S.reshape = 1;
68 | }
69 |
70 | /*
71 | * Create S.count new canvasses in the currently focused canvas.
72 | * If the argument is '|', add new canvasses in the right side
73 | * of the current canvas. Otherwise, create new canvasses in
74 | * the bottom. Rebalance so all new canvasses have equal portion
75 | * of the current canvas.
76 | */
77 | void
78 | create(const char *arg)
79 | {
80 | int dir = *arg == '|';
81 | struct canvas *n = S.f;
82 | while (n->c[dir]) {
83 | n = n->c[dir];
84 | }
85 | for (int i = S.count < 1 ? 1 : S.count; i > 0; i -= 1) {
86 | struct canvas *v = n->c[dir] = newcanvas(0, n);
87 | if (v && v->p) {
88 | v->typ = n->typ = n->c[!dir] ? n->typ : dir;
89 | assert(v->parent == n);
90 | n = v;
91 | } else if (v && !v->p) {
92 | freecanvas(v);
93 | }
94 | }
95 | balance(arg);
96 | reshape(S.root, 0, 0, LINES, COLS);
97 | }
98 |
99 | /*
100 | * Increment S.count when the user enters a digit
101 | */
102 | void
103 | digit(const char *arg)
104 | {
105 | S.count = 10 * (S.count == -1 ? 0 : S.count) + *arg - '0';
106 | }
107 |
108 | static struct canvas * find_canvas(struct canvas *c, int id);
109 |
110 | /*
111 | * Move the focus to the canvas with id matching S.count
112 | */
113 | void
114 | focus(void)
115 | {
116 | struct canvas *t = find_canvas(S.root, S.count);
117 | S.f = t ? t : S.f;
118 | }
119 |
120 | /*
121 | * Navigate the canvas tree
122 | */
123 | void
124 | mov(const char *arg)
125 | {
126 | int count = S.count < 1 ? 1 : S.count;
127 | struct canvas *n = S.f;
128 | for (struct canvas *t = S.f; t && count--; n = t ? t : n) {
129 | switch (*arg) {
130 | case 'k':
131 | t = t->parent;
132 | break;
133 | case 'j':
134 | t = t->c[t->typ];
135 | break;
136 | case 'h':
137 | t = t->c[0];
138 | break;
139 | case 'l':
140 | t = t->c[1];
141 | break;
142 | }
143 | }
144 | S.f = n ? n : S.root;
145 | }
146 |
147 | /*
148 | * Recursive function used to find a canvas with id in the tree.
149 | */
150 | static struct canvas *
151 | find_canvas(struct canvas *c, int id)
152 | {
153 | struct canvas *r = NULL;
154 | if (c && id > 0) {
155 | if (IDX(c->p) == id) {
156 | r = c;
157 | } else if ((r = find_canvas(c->c[0], id)) == NULL) {
158 | r = find_canvas(c->c[1], id);
159 | }
160 | }
161 | return id < 1 ? S.f : r;
162 | }
163 |
164 | static void
165 | pty_size(struct pty *p)
166 | {
167 | check(p->fd == -1 || ! ioctl(p->fd, TIOCGWINSZ, &p->ws), errno = 0,
168 | "ioctl error getting size of pty %d", IDX(p));
169 | }
170 |
171 | void
172 | new_tabstop(void)
173 | {
174 | pty_size(S.f->p); /* Update S.f->p->ws */
175 | set_tabs(S.f->p, S.f->p->tabstop = S.count > -1 ? S.count : 8);
176 | }
177 |
178 | void
179 | new_shell(void)
180 | {
181 | attach_pty(S.f, new_pty(S.history, MAX(S.width, S.f->extent.x), true));
182 | }
183 |
184 | void
185 | next(void)
186 | {
187 | S.f->p = S.f->p->next ? S.f->p->next : S.p;
188 | }
189 |
190 | void
191 | prune(void)
192 | {
193 | struct canvas *t = find_canvas(S.root, S.count);
194 | if (S.count == 0) {
195 | S.root = NULL; /* Trigger an exit from main loop */
196 | } else if (t && t->parent) {
197 | struct canvas *p = t->parent;
198 | int child_index = t == p->c[1];
199 | *(child_index ? &p->split.x : &p->split.y) = 1.0;
200 | p->c[child_index] = NULL;
201 | change_count(t, -1, 1);
202 | }
203 | S.reshape = 1;
204 | }
205 |
206 | static void grow_screens(struct pty *p, int siz);
207 |
208 | void
209 | reshape_root(void)
210 | {
211 | if (LINES > S.history) {
212 | S.history = LINES;
213 | }
214 | for (struct pty *p = S.p; p; p = p->next) {
215 | grow_screens(p, LINES);
216 | }
217 | resize_pad(&S.werr, 1, COLS);
218 | resize_pad(&S.wbkg, LINES, COLS);
219 | reshape(S.root, 0, 0, LINES, COLS);
220 | }
221 |
222 | void
223 | resize(const char *arg)
224 | {
225 | struct canvas *n = S.f, *child = S.f->c[!strchr("-", *arg)];
226 | double *s = strchr("-", *arg) ? &n->split.y : &n->split.x;
227 | int count = S.count < 0 ? 50 : S.count > 100 ? 100 : S.count;
228 | *s = child ? count / 100.0 : 1.0;
229 | S.reshape = 1;
230 | }
231 |
232 | void
233 | scrollh(const char *arg)
234 | {
235 | struct canvas *n = S.f;
236 | if (n && n->p && n->p->s && n->p->s->w) {
237 | int c = S.count;
238 | int count = c < 0 ? n->extent.x : !c ? n->p->ws.ws_col : c;
239 | int x = n->offset.x + (*arg == '<' ? -count : count);
240 | n->offset.x = MAX(0, MIN(x, n->p->ws.ws_col - n->extent.x));
241 | n->manualscroll = !!c;
242 | }
243 | }
244 |
245 | void
246 | scrolln(const char *arg)
247 | {
248 | struct canvas *n = S.f;
249 | if (n && n->p && n->p->s && n->p->s->w) {
250 | int count = S.count == -1 ? n->extent.y - 1 : S.count;
251 | int top = n->p->s->maxy - n->extent.y + 1;
252 | n->offset.y += *arg == '-' ? -count : count;
253 | n->offset.y = MIN(MAX(0, n->offset.y), top);
254 | }
255 | }
256 |
257 | void
258 | send(const char *arg)
259 | {
260 | rewrite(S.f->p->fd, arg + 1, arg[0]);
261 | scrollbottom(S.f);
262 | }
263 |
264 | void
265 | send_cr(void)
266 | {
267 | rewrite(S.f->p->fd, "\r\n", S.f->p->lnm ? 2 : 1);
268 | scrollbottom(S.f);
269 | }
270 |
271 | static void
272 | grow_screens(struct pty *p, int siz)
273 | {
274 | struct screen *s, *w[] = { &p->scr[0], &p->scr[1], NULL };
275 | for (struct screen **sp = w; *sp && (s = *sp)->rows < siz; sp++) {
276 | WINDOW *new = NULL;
277 | if (resize_pad(&new, siz, p->ws.ws_col)) {
278 | copywin(s->w, new, 0, 0, siz - s->rows, 0,
279 | siz - 1, p->ws.ws_col - 1, 1);
280 | delwin(s->w);
281 | s->w = new;
282 | wmove(s->w, s->c.y += siz - s->rows, s->c.x);
283 | /* TODO?: mov maxy to struct pty */
284 | s->maxy += siz - s->rows;
285 | p->tos = MAX(0, s->maxy - p->ws.ws_row + 1);
286 | s->scroll.top += siz - s->rows;
287 | s->scroll.bot += siz - s->rows;
288 | s->rows = siz;
289 | }
290 | }
291 | }
292 |
293 | void
294 | help(void)
295 | {
296 | for (int i = 0; i < LINES * COLS; i++) {
297 | putchar(' ');
298 | }
299 | putchar('\r');
300 | putchar('\n');
301 | printf("Command key is ^%c\r\n", S.rawkey);
302 | puts("Avaliable commands (in command mode):\r");
303 | puts("[N]C create N new windows (left/right)\r");
304 | puts("[N]c create N new windows (up/down)\r");
305 | puts("[N]g move focus to window N\r");
306 | puts("[N]v use preset window layout N\r");
307 | puts("[N]W set width of underlying tty to N\r");
308 | puts("[N]x Close window N. If N == 0, exit smtx\r");
309 | puts("[N]= rebalance all windows below current\r");
310 | puts("[N]< scroll left N characters\r");
311 | puts("[N]> scroll right N characters\r");
312 | puts("[N]- set height of current window to N% of canvas\r");
313 | puts("[N]| set width of current window to N% of canvas\r");
314 | fflush(stdout);
315 | }
316 |
317 | void
318 | set_history(void)
319 | {
320 | struct canvas *n = S.f;
321 | struct pty *p = n->p;
322 | S.history = MAX(LINES, S.count);
323 | grow_screens(p, S.history);
324 | S.reshape = 1;
325 | }
326 |
327 | void
328 | set_layout(void)
329 | {
330 | const char *defaults[] = {
331 | [0] = "1,1",
332 | [1] = "1,1",
333 | [2] = ".5,1 1,1",
334 | [3] = "1,.5 .5,1 1,1",
335 | [4] = ".5,.5 .5,1 1,.5 1,1",
336 | [5] = "1,.5 .25,1 .5,1 .75,1 1,1",
337 | [6] = ".5,1 .75,.5 .75,1 1,.33 1,.66 1,1",
338 | [7] = ".4,1 .8,1 1,.2 1,.4 1,.6 1,.8 1,1",
339 | [8] = ".5,.25 .5,.5 .5,.75 .5,1 1,.25 1,.5 1,.75 1,1",
340 | [9] = (
341 | ".34,.33 .34,.67 .34,1 .68,.33 .68,.67 .68,1 "
342 | "1,.33 1,.67 1,1"
343 | ),
344 | };
345 | size_t count = S.count < 0 ? 1 : S.count;
346 | if (count < sizeof defaults / sizeof *defaults) {
347 | build_layout(defaults[count]);
348 | } else {
349 | check(0, errno = 0, "Invalid layout: %d", count);
350 | }
351 | }
352 |
353 | /* Set width of the underlying tty */
354 | void
355 | set_width(const char *arg)
356 | {
357 | struct canvas *n = S.f;
358 | struct pty *p = n->p;
359 | int w = *arg ? strtol(arg, NULL, 10) : S.count;
360 | if (w == -1) {
361 | w = n->extent.x;
362 | }
363 | if (p->fd > 0 && (pty_size(p), w != p->ws.ws_col)) {
364 | p->ws.ws_col = w;
365 | resize_pad(&p->scr[0].w, p->scr[0].rows, w);
366 | resize_pad(&p->scr[1].w, p->scr[1].rows, w);
367 | if (p->s->c.x > w - 1) {
368 | wmove(p->s->w, p->s->c.y, p->s->c.x = w - 1);
369 | }
370 | set_tabs(p, p->tabstop);
371 | reshape_window(p);
372 | }
373 | }
374 |
375 | void
376 | swap(void)
377 | {
378 | struct canvas *n = S.f;
379 | struct canvas *t;
380 | if (S.count == -1) {
381 | t = n->c[n->typ];
382 | t = t ? t : n->c[!n->typ];
383 | t = t ? t : n->parent;
384 | } else {
385 | t = find_canvas(S.root, S.count);
386 | }
387 | if (t) {
388 | struct pty *tmp = n->p;
389 | n->p = t->p;
390 | t->p = tmp;
391 | S.reshape = 1;
392 | } else {
393 | check(0, errno = 0, "Cannot find target canvas");
394 | }
395 | }
396 |
397 | void
398 | transition(const char *arg)
399 | {
400 | switch (*arg++) {
401 | case '*':
402 | rewrite(S.f->p->fd, (char *)&S.ctlkey, 1);
403 | break;
404 | case '\n':
405 | send_cr();
406 | }
407 | S.errmsg[0] = 0; /* Clear any existing error message */
408 | switch (*arg) {
409 | case 'e':
410 | S.binding = k1; /* enter */
411 | break;
412 | case 'i':
413 | S.binding = k2; /* insert */
414 | break;
415 | case 'c':
416 | S.binding = ctl; /* control */
417 | break;
418 | }
419 | scrollbottom(S.f);
420 | wrefresh(curscr);
421 | }
422 |
423 | static void
424 | transpose_r(struct canvas *c)
425 | {
426 | if (c) {
427 | c->typ = !c->typ;
428 | struct canvas *t = c->c[0];
429 | double s = c->split.y;
430 | c->split.y = c->split.x;
431 | c->split.x = s;
432 | transpose_r(c->c[0] = c->c[1]);
433 | transpose_r(c->c[1] = t);
434 | }
435 | }
436 |
437 | void
438 | transpose(void)
439 | {
440 | if (S.f && !S.f->c[0] && !S.f->c[1]) {
441 | transpose_r(S.f->parent);
442 | } else {
443 | transpose_r(S.f);
444 | }
445 | S.reshape = 1;
446 | }
447 |
448 | void
449 | vbeep(void)
450 | {
451 | (void)beep();
452 | }
453 |
--------------------------------------------------------------------------------
/autogen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | autoreconf -ivf
3 |
--------------------------------------------------------------------------------
/bindings.c:
--------------------------------------------------------------------------------
1 |
2 | #include "smtx.h"
3 | #pragma GCC diagnostic ignored "-Woverride-init"
4 |
5 | struct handler ctl[128] = {
6 | [0x00 ... 0x7f] = { .act = { .v = vbeep }, NULL },
7 |
8 | [L'0' ] = { { .a = digit}, "0" },
9 | [L'1' ] = { { .a = digit}, "1" },
10 | [L'2' ] = { { .a = digit}, "2" },
11 | [L'3' ] = { { .a = digit}, "3" },
12 | [L'4' ] = { { .a = digit}, "4" },
13 | [L'5' ] = { { .a = digit}, "5" },
14 | [L'6' ] = { { .a = digit}, "6" },
15 | [L'7' ] = { { .a = digit}, "7" },
16 | [L'8' ] = { { .a = digit}, "8" },
17 | [L'9' ] = { { .a = digit}, "9" },
18 | [L'-' ] = { { .a = resize}, "-" },
19 | [L'<' ] = { { .a = scrollh}, "<" },
20 | [L'=' ] = { { .a = balance}, "=" },
21 | [L'>' ] = { { .a = scrollh}, ">" },
22 | [L'?' ] = { { .v = help}, NULL },
23 | [L'|' ] = { { .a = resize}, "|" },
24 | [L'C' ] = { { .a = create}, "|" },
25 | [L'N' ] = { { .v = new_shell}, NULL },
26 | #ifndef NDEBUG
27 | [L'Q' ] = { { .a = show_status}, "x" },
28 | #endif
29 | [L'S' ] = { { .v = swap}, NULL },
30 | [L'T' ] = { { .v = transpose}, NULL },
31 | [L'W' ] = { { .a = set_width}, "" },
32 | [L'Z' ] = { { .v = set_history}, NULL },
33 | [L'\n'] = { { .a = transition}, " enter" },
34 | [L'\r'] = { { .a = transition}, " enter" },
35 | [L'a' ] = { { .v = attach}, NULL },
36 | [L'b' ] = { { .a = scrolln}, "-" },
37 | [L'c' ] = { { .a = create}, "-" },
38 | [L'f' ] = { { .a = scrolln}, "+" },
39 | [L'g' ] = { { .v = focus}, NULL },
40 | [L'h' ] = { { .a = mov}, "h" },
41 | [L'i' ] = { { .a = transition}, " insert" },
42 | [L'j' ] = { { .a = mov}, "j" },
43 | [L'k' ] = { { .a = mov}, "k" },
44 | [L'l' ] = { { .a = mov}, "l" },
45 | [L'n' ] = { { .v = next}, NULL },
46 | [L't' ] = { { .v = new_tabstop}, NULL },
47 | [L'v' ] = { { .v = set_layout}, NULL },
48 | [L'x' ] = { { .v = prune}, NULL },
49 | };
50 |
51 | struct handler code_keys[KEY_MAX - KEY_MIN + 1] = {
52 | [KEY_RESIZE - KEY_MIN] = { { .v = reshape_root }, NULL },
53 | [KEY_F(1) - KEY_MIN] = { { .a = send }, "\x01\033" },
54 | [KEY_F(2) - KEY_MIN] = { { .a = send }, "\x03\033OQ" },
55 | [KEY_F(3) - KEY_MIN] = { { .a = send }, "\x03\033OR" },
56 | [KEY_F(4) - KEY_MIN] = { { .a = send }, "\x03\033OS" },
57 | [KEY_F(5) - KEY_MIN] = { { .a = send }, "\x05\033[15~" },
58 | [KEY_F(6) - KEY_MIN] = { { .a = send }, "\x05\033[17~" },
59 | [KEY_F(7) - KEY_MIN] = { { .a = send }, "\x05\033[18~" },
60 | [KEY_F(8) - KEY_MIN] = { { .a = send }, "\x05\033[19~" },
61 | [KEY_F(9) - KEY_MIN] = { { .a = send }, "\x05\033[20~" },
62 | [KEY_F(10) - KEY_MIN] = { { .a = send }, "\x05\033[21~" },
63 | [KEY_F(11) - KEY_MIN] = { { .a = send }, "\x05\033[23~" },
64 | [KEY_F(12) - KEY_MIN] = { { .a = send }, "\x05\033[24~" },
65 | [KEY_HOME - KEY_MIN] = { { .a = send }, "\x04\033[1~" },
66 | [KEY_END - KEY_MIN] = { { .a = send }, "\x04\033[4~" },
67 | [KEY_PPAGE - KEY_MIN] = { { .a = send }, "\x04\033[5~" },
68 | [KEY_NPAGE - KEY_MIN] = { { .a = send }, "\x04\033[6~" },
69 | [KEY_BACKSPACE - KEY_MIN] = { { .a = send }, "\x01\177" },
70 | [KEY_DC - KEY_MIN] = { { .a = send }, "\x04\033[3~" },
71 | [KEY_IC - KEY_MIN] = { { .a = send }, "\x04\033[2~" },
72 | [KEY_BTAB - KEY_MIN] = { { .a = send }, "\x04\033[Z" },
73 | [KEY_ENTER - KEY_MIN] = { { .v = send_cr }, NULL },
74 | [KEY_UP - KEY_MIN] = { { .a = sendarrow }, "A" },
75 | [KEY_DOWN - KEY_MIN] = { { .a = sendarrow }, "B" },
76 | [KEY_RIGHT - KEY_MIN] = { { .a = sendarrow }, "C" },
77 | [KEY_LEFT - KEY_MIN] = { { .a = sendarrow }, "D" },
78 | };
79 |
80 | struct handler k1[128] = {
81 | [0x00] = { .act = { .a = send }, "\x01\x00" },
82 | [0x01] = { .act = { .a = send }, "\x01\x01" },
83 | [0x02] = { .act = { .a = send }, "\x01\x02" },
84 | [0x03] = { .act = { .a = send }, "\x01\x03" },
85 | [0x04] = { .act = { .a = send }, "\x01\x04" },
86 | [0x05] = { .act = { .a = send }, "\x01\x05" },
87 | [0x06] = { .act = { .a = send }, "\x01\x06" },
88 | [0x07] = { .act = { .a = send }, "\x01\x07" },
89 | [0x08] = { .act = { .a = send }, "\x01\x08" },
90 | [0x09] = { .act = { .a = send }, "\x01\x09" },
91 | [0x0a] = { .act = { .a = send }, "\x01\x0a" },
92 | [0x0b] = { .act = { .a = send }, "\x01\x0b" },
93 | [0x0c] = { .act = { .a = send }, "\x01\x0c" },
94 | [0x0d] = { .act = { .v = send_cr }, NULL },
95 | [0x0e] = { .act = { .a = send }, "\x01\x0e" },
96 | [0x0f] = { .act = { .a = send }, "\x01\x0f" },
97 |
98 | [0x10] = { .act = { .a = send }, "\x01\x10" },
99 | [0x11] = { .act = { .a = send }, "\x01\x11" },
100 | [0x12] = { .act = { .a = send }, "\x01\x12" },
101 | [0x13] = { .act = { .a = send }, "\x01\x13" },
102 | [0x14] = { .act = { .a = send }, "\x01\x14" },
103 | [0x15] = { .act = { .a = send }, "\x01\x15" },
104 | [0x16] = { .act = { .a = send }, "\x01\x16" },
105 | [0x17] = { .act = { .a = send }, "\x01\x17" },
106 | [0x18] = { .act = { .a = send }, "\x01\x18" },
107 | [0x19] = { .act = { .a = send }, "\x01\x19" },
108 | [0x1a] = { .act = { .a = send }, "\x01\x1a" },
109 | [0x1b] = { .act = { .a = send }, "\x01\x1b" },
110 | [0x1c] = { .act = { .a = send }, "\x01\x1c" },
111 | [0x1d] = { .act = { .a = send }, "\x01\x1d" },
112 | [0x1e] = { .act = { .a = send }, "\x01\x1e" },
113 | [0x1f] = { .act = { .a = send }, "\x01\x1f" },
114 |
115 | [0x20] = { .act = { .a = send }, "\x01\x20" },
116 | [0x21] = { .act = { .a = send }, "\x01\x21" },
117 | [0x22] = { .act = { .a = send }, "\x01\x22" },
118 | [0x23] = { .act = { .a = send }, "\x01\x23" },
119 | [0x24] = { .act = { .a = send }, "\x01\x24" },
120 | [0x25] = { .act = { .a = send }, "\x01\x25" },
121 | [0x26] = { .act = { .a = send }, "\x01\x26" },
122 | [0x27] = { .act = { .a = send }, "\x01\x27" },
123 | [0x28] = { .act = { .a = send }, "\x01\x28" },
124 | [0x29] = { .act = { .a = send }, "\x01\x29" },
125 | [0x2a] = { .act = { .a = send }, "\x01\x2a" },
126 | [0x2b] = { .act = { .a = send }, "\x01\x2b" },
127 | [0x2c] = { .act = { .a = send }, "\x01\x2c" },
128 | [0x2d] = { .act = { .a = send }, "\x01\x2d" },
129 | [0x2e] = { .act = { .a = send }, "\x01\x2e" },
130 | [0x2f] = { .act = { .a = send }, "\x01\x2f" },
131 |
132 | [0x30] = { .act = { .a = send }, "\x01\x30" },
133 | [0x31] = { .act = { .a = send }, "\x01\x31" },
134 | [0x32] = { .act = { .a = send }, "\x01\x32" },
135 | [0x33] = { .act = { .a = send }, "\x01\x33" },
136 | [0x34] = { .act = { .a = send }, "\x01\x34" },
137 | [0x35] = { .act = { .a = send }, "\x01\x35" },
138 | [0x36] = { .act = { .a = send }, "\x01\x36" },
139 | [0x37] = { .act = { .a = send }, "\x01\x37" },
140 | [0x38] = { .act = { .a = send }, "\x01\x38" },
141 | [0x39] = { .act = { .a = send }, "\x01\x39" },
142 | [0x3a] = { .act = { .a = send }, "\x01\x3a" },
143 | [0x3b] = { .act = { .a = send }, "\x01\x3b" },
144 | [0x3c] = { .act = { .a = send }, "\x01\x3c" },
145 | [0x3d] = { .act = { .a = send }, "\x01\x3d" },
146 | [0x3e] = { .act = { .a = send }, "\x01\x3e" },
147 | [0x3f] = { .act = { .a = send }, "\x01\x3f" },
148 |
149 | [0x40] = { .act = { .a = send }, "\x01\x40" },
150 | [0x41] = { .act = { .a = send }, "\x01\x41" },
151 | [0x42] = { .act = { .a = send }, "\x01\x42" },
152 | [0x43] = { .act = { .a = send }, "\x01\x43" },
153 | [0x44] = { .act = { .a = send }, "\x01\x44" },
154 | [0x45] = { .act = { .a = send }, "\x01\x45" },
155 | [0x46] = { .act = { .a = send }, "\x01\x46" },
156 | [0x47] = { .act = { .a = send }, "\x01\x47" },
157 | [0x48] = { .act = { .a = send }, "\x01\x48" },
158 | [0x49] = { .act = { .a = send }, "\x01\x49" },
159 | [0x4a] = { .act = { .a = send }, "\x01\x4a" },
160 | [0x4b] = { .act = { .a = send }, "\x01\x4b" },
161 | [0x4c] = { .act = { .a = send }, "\x01\x4c" },
162 | [0x4d] = { .act = { .a = send }, "\x01\x4d" },
163 | [0x4e] = { .act = { .a = send }, "\x01\x4e" },
164 | [0x4f] = { .act = { .a = send }, "\x01\x4f" },
165 |
166 | [0x50] = { .act = { .a = send }, "\x01\x50" },
167 | [0x51] = { .act = { .a = send }, "\x01\x51" },
168 | [0x52] = { .act = { .a = send }, "\x01\x52" },
169 | [0x53] = { .act = { .a = send }, "\x01\x53" },
170 | [0x54] = { .act = { .a = send }, "\x01\x54" },
171 | [0x55] = { .act = { .a = send }, "\x01\x55" },
172 | [0x56] = { .act = { .a = send }, "\x01\x56" },
173 | [0x57] = { .act = { .a = send }, "\x01\x57" },
174 | [0x58] = { .act = { .a = send }, "\x01\x58" },
175 | [0x59] = { .act = { .a = send }, "\x01\x59" },
176 | [0x5a] = { .act = { .a = send }, "\x01\x5a" },
177 | [0x5b] = { .act = { .a = send }, "\x01\x5b" },
178 | [0x5c] = { .act = { .a = send }, "\x01\x5c" },
179 | [0x5d] = { .act = { .a = send }, "\x01\x5d" },
180 | [0x5e] = { .act = { .a = send }, "\x01\x5e" },
181 | [0x5f] = { .act = { .a = send }, "\x01\x5f" },
182 |
183 | [0x60] = { .act = { .a = send }, "\x01\x60" },
184 | [0x61] = { .act = { .a = send }, "\x01\x61" },
185 | [0x62] = { .act = { .a = send }, "\x01\x62" },
186 | [0x63] = { .act = { .a = send }, "\x01\x63" },
187 | [0x64] = { .act = { .a = send }, "\x01\x64" },
188 | [0x65] = { .act = { .a = send }, "\x01\x65" },
189 | [0x66] = { .act = { .a = send }, "\x01\x66" },
190 | [0x67] = { .act = { .a = send }, "\x01\x67" },
191 | [0x68] = { .act = { .a = send }, "\x01\x68" },
192 | [0x69] = { .act = { .a = send }, "\x01\x69" },
193 | [0x6a] = { .act = { .a = send }, "\x01\x6a" },
194 | [0x6b] = { .act = { .a = send }, "\x01\x6b" },
195 | [0x6c] = { .act = { .a = send }, "\x01\x6c" },
196 | [0x6d] = { .act = { .a = send }, "\x01\x6d" },
197 | [0x6e] = { .act = { .a = send }, "\x01\x6e" },
198 | [0x6f] = { .act = { .a = send }, "\x01\x6f" },
199 |
200 | [0x70] = { .act = { .a = send }, "\x01\x70" },
201 | [0x71] = { .act = { .a = send }, "\x01\x71" },
202 | [0x72] = { .act = { .a = send }, "\x01\x72" },
203 | [0x73] = { .act = { .a = send }, "\x01\x73" },
204 | [0x74] = { .act = { .a = send }, "\x01\x74" },
205 | [0x75] = { .act = { .a = send }, "\x01\x75" },
206 | [0x76] = { .act = { .a = send }, "\x01\x76" },
207 | [0x77] = { .act = { .a = send }, "\x01\x77" },
208 | [0x78] = { .act = { .a = send }, "\x01\x78" },
209 | [0x79] = { .act = { .a = send }, "\x01\x79" },
210 | [0x7a] = { .act = { .a = send }, "\x01\x7a" },
211 | [0x7b] = { .act = { .a = send }, "\x01\x7b" },
212 | [0x7c] = { .act = { .a = send }, "\x01\x7c" },
213 | [0x7d] = { .act = { .a = send }, "\x01\x7d" },
214 | [0x7e] = { .act = { .a = send }, "\x01\x7e" },
215 | [0x7f] = { .act = { .a = send }, "\x01\x7f" },
216 | };
217 |
218 | struct handler k2[128] = {
219 | [0x00] = { .act = { .a = send }, "\x01\x00" },
220 | [0x01] = { .act = { .a = send }, "\x01\x01" },
221 | [0x02] = { .act = { .a = send }, "\x01\x02" },
222 | [0x03] = { .act = { .a = send }, "\x01\x03" },
223 | [0x04] = { .act = { .a = send }, "\x01\x04" },
224 | [0x05] = { .act = { .a = send }, "\x01\x05" },
225 | [0x06] = { .act = { .a = send }, "\x01\x06" },
226 | [0x07] = { .act = { .a = send }, "\x01\x07" },
227 | [0x08] = { .act = { .a = send }, "\x01\x08" },
228 | [0x09] = { .act = { .a = send }, "\x01\x09" },
229 | [0x0a] = { .act = { .a = send }, "\x01\x0a" },
230 | [0x0b] = { .act = { .a = send }, "\x01\x0b" },
231 | [0x0c] = { .act = { .a = send }, "\x01\x0c" },
232 | [0x0d] = { .act = { .a = transition }, "\ncontrol" },
233 | [0x0e] = { .act = { .a = send }, "\x01\x0e" },
234 | [0x0f] = { .act = { .a = send }, "\x01\x0f" },
235 |
236 | [0x10] = { .act = { .a = send }, "\x01\x10" },
237 | [0x11] = { .act = { .a = send }, "\x01\x11" },
238 | [0x12] = { .act = { .a = send }, "\x01\x12" },
239 | [0x13] = { .act = { .a = send }, "\x01\x13" },
240 | [0x14] = { .act = { .a = send }, "\x01\x14" },
241 | [0x15] = { .act = { .a = send }, "\x01\x15" },
242 | [0x16] = { .act = { .a = send }, "\x01\x16" },
243 | [0x17] = { .act = { .a = send }, "\x01\x17" },
244 | [0x18] = { .act = { .a = send }, "\x01\x18" },
245 | [0x19] = { .act = { .a = send }, "\x01\x19" },
246 | [0x1a] = { .act = { .a = send }, "\x01\x1a" },
247 | [0x1b] = { .act = { .a = send }, "\x01\x1b" },
248 | [0x1c] = { .act = { .a = send }, "\x01\x1c" },
249 | [0x1d] = { .act = { .a = send }, "\x01\x1d" },
250 | [0x1e] = { .act = { .a = send }, "\x01\x1e" },
251 | [0x1f] = { .act = { .a = send }, "\x01\x1f" },
252 |
253 | [0x20] = { .act = { .a = send }, "\x01\x20" },
254 | [0x21] = { .act = { .a = send }, "\x01\x21" },
255 | [0x22] = { .act = { .a = send }, "\x01\x22" },
256 | [0x23] = { .act = { .a = send }, "\x01\x23" },
257 | [0x24] = { .act = { .a = send }, "\x01\x24" },
258 | [0x25] = { .act = { .a = send }, "\x01\x25" },
259 | [0x26] = { .act = { .a = send }, "\x01\x26" },
260 | [0x27] = { .act = { .a = send }, "\x01\x27" },
261 | [0x28] = { .act = { .a = send }, "\x01\x28" },
262 | [0x29] = { .act = { .a = send }, "\x01\x29" },
263 | [0x2a] = { .act = { .a = send }, "\x01\x2a" },
264 | [0x2b] = { .act = { .a = send }, "\x01\x2b" },
265 | [0x2c] = { .act = { .a = send }, "\x01\x2c" },
266 | [0x2d] = { .act = { .a = send }, "\x01\x2d" },
267 | [0x2e] = { .act = { .a = send }, "\x01\x2e" },
268 | [0x2f] = { .act = { .a = send }, "\x01\x2f" },
269 |
270 | [0x30] = { .act = { .a = send }, "\x01\x30" },
271 | [0x31] = { .act = { .a = send }, "\x01\x31" },
272 | [0x32] = { .act = { .a = send }, "\x01\x32" },
273 | [0x33] = { .act = { .a = send }, "\x01\x33" },
274 | [0x34] = { .act = { .a = send }, "\x01\x34" },
275 | [0x35] = { .act = { .a = send }, "\x01\x35" },
276 | [0x36] = { .act = { .a = send }, "\x01\x36" },
277 | [0x37] = { .act = { .a = send }, "\x01\x37" },
278 | [0x38] = { .act = { .a = send }, "\x01\x38" },
279 | [0x39] = { .act = { .a = send }, "\x01\x39" },
280 | [0x3a] = { .act = { .a = send }, "\x01\x3a" },
281 | [0x3b] = { .act = { .a = send }, "\x01\x3b" },
282 | [0x3c] = { .act = { .a = send }, "\x01\x3c" },
283 | [0x3d] = { .act = { .a = send }, "\x01\x3d" },
284 | [0x3e] = { .act = { .a = send }, "\x01\x3e" },
285 | [0x3f] = { .act = { .a = send }, "\x01\x3f" },
286 |
287 | [0x40] = { .act = { .a = send }, "\x01\x40" },
288 | [0x41] = { .act = { .a = send }, "\x01\x41" },
289 | [0x42] = { .act = { .a = send }, "\x01\x42" },
290 | [0x43] = { .act = { .a = send }, "\x01\x43" },
291 | [0x44] = { .act = { .a = send }, "\x01\x44" },
292 | [0x45] = { .act = { .a = send }, "\x01\x45" },
293 | [0x46] = { .act = { .a = send }, "\x01\x46" },
294 | [0x47] = { .act = { .a = send }, "\x01\x47" },
295 | [0x48] = { .act = { .a = send }, "\x01\x48" },
296 | [0x49] = { .act = { .a = send }, "\x01\x49" },
297 | [0x4a] = { .act = { .a = send }, "\x01\x4a" },
298 | [0x4b] = { .act = { .a = send }, "\x01\x4b" },
299 | [0x4c] = { .act = { .a = send }, "\x01\x4c" },
300 | [0x4d] = { .act = { .a = send }, "\x01\x4d" },
301 | [0x4e] = { .act = { .a = send }, "\x01\x4e" },
302 | [0x4f] = { .act = { .a = send }, "\x01\x4f" },
303 |
304 | [0x50] = { .act = { .a = send }, "\x01\x50" },
305 | [0x51] = { .act = { .a = send }, "\x01\x51" },
306 | [0x52] = { .act = { .a = send }, "\x01\x52" },
307 | [0x53] = { .act = { .a = send }, "\x01\x53" },
308 | [0x54] = { .act = { .a = send }, "\x01\x54" },
309 | [0x55] = { .act = { .a = send }, "\x01\x55" },
310 | [0x56] = { .act = { .a = send }, "\x01\x56" },
311 | [0x57] = { .act = { .a = send }, "\x01\x57" },
312 | [0x58] = { .act = { .a = send }, "\x01\x58" },
313 | [0x59] = { .act = { .a = send }, "\x01\x59" },
314 | [0x5a] = { .act = { .a = send }, "\x01\x5a" },
315 | [0x5b] = { .act = { .a = send }, "\x01\x5b" },
316 | [0x5c] = { .act = { .a = send }, "\x01\x5c" },
317 | [0x5d] = { .act = { .a = send }, "\x01\x5d" },
318 | [0x5e] = { .act = { .a = send }, "\x01\x5e" },
319 | [0x5f] = { .act = { .a = send }, "\x01\x5f" },
320 |
321 | [0x60] = { .act = { .a = send }, "\x01\x60" },
322 | [0x61] = { .act = { .a = send }, "\x01\x61" },
323 | [0x62] = { .act = { .a = send }, "\x01\x62" },
324 | [0x63] = { .act = { .a = send }, "\x01\x63" },
325 | [0x64] = { .act = { .a = send }, "\x01\x64" },
326 | [0x65] = { .act = { .a = send }, "\x01\x65" },
327 | [0x66] = { .act = { .a = send }, "\x01\x66" },
328 | [0x67] = { .act = { .a = send }, "\x01\x67" },
329 | [0x68] = { .act = { .a = send }, "\x01\x68" },
330 | [0x69] = { .act = { .a = send }, "\x01\x69" },
331 | [0x6a] = { .act = { .a = send }, "\x01\x6a" },
332 | [0x6b] = { .act = { .a = send }, "\x01\x6b" },
333 | [0x6c] = { .act = { .a = send }, "\x01\x6c" },
334 | [0x6d] = { .act = { .a = send }, "\x01\x6d" },
335 | [0x6e] = { .act = { .a = send }, "\x01\x6e" },
336 | [0x6f] = { .act = { .a = send }, "\x01\x6f" },
337 |
338 | [0x70] = { .act = { .a = send }, "\x01\x70" },
339 | [0x71] = { .act = { .a = send }, "\x01\x71" },
340 | [0x72] = { .act = { .a = send }, "\x01\x72" },
341 | [0x73] = { .act = { .a = send }, "\x01\x73" },
342 | [0x74] = { .act = { .a = send }, "\x01\x74" },
343 | [0x75] = { .act = { .a = send }, "\x01\x75" },
344 | [0x76] = { .act = { .a = send }, "\x01\x76" },
345 | [0x77] = { .act = { .a = send }, "\x01\x77" },
346 | [0x78] = { .act = { .a = send }, "\x01\x78" },
347 | [0x79] = { .act = { .a = send }, "\x01\x79" },
348 | [0x7a] = { .act = { .a = send }, "\x01\x7a" },
349 | [0x7b] = { .act = { .a = send }, "\x01\x7b" },
350 | [0x7c] = { .act = { .a = send }, "\x01\x7c" },
351 | [0x7d] = { .act = { .a = send }, "\x01\x7d" },
352 | [0x7e] = { .act = { .a = send }, "\x01\x7e" },
353 | [0x7f] = { .act = { .a = send }, "\x01\x7f" },
354 | };
355 |
--------------------------------------------------------------------------------
/build-aux/package-version:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Find a version.
4 | cd $( dirname $0 )/.. || exit 1
5 |
6 | GIT_CEILING_DIRECTORIES=$(cd ..; pwd -P)
7 | export GIT_CEILING_DIRECTORIES
8 |
9 | # Take version from git if we can...
10 | v=$(git describe --always --dirty --match 'v*' 2>/dev/null)
11 | if test -n "$v"; then
12 | printf "%s" "${v#v}" | tr - .
13 | # else from the version file (eg in a dist tarball)
14 | elif v=$(cat version); test -n "$v"; then
15 | printf "%s" "$v"
16 | else
17 | printf unknown
18 | fi
19 |
--------------------------------------------------------------------------------
/configure.ac:
--------------------------------------------------------------------------------
1 | # -*- Autoconf -*-
2 | # Process this file with autoconf to produce a configure script.
3 |
4 | AC_PREREQ([2.69])
5 | AC_INIT([smtx],
6 | m4_esyscmd([./build-aux/package-version]),
7 | [william.r.pursell@gmail.com])
8 | AM_INIT_AUTOMAKE([-Wall -Werror foreign])
9 | AM_PROG_AR
10 | LT_INIT
11 | AC_CONFIG_MACRO_DIRS([m4])
12 | AC_CONFIG_HEADERS([config.h])
13 |
14 | AC_PROG_CC
15 | AC_PROG_CC_STDC
16 | AC_CHECK_HEADERS([unistd.h util.h libutil.h termios.h pty.h wchar.h wctype.h])
17 | AC_CHECK_HEADERS([curses.h ncursesw/curses.h])
18 | AC_CHECK_DECL([A_ITALIC],AC_DEFINE([HAVE_A_ITALIC],[1],[ ]),[],[[#include ]])
19 | AC_TYPE_SIZE_T
20 | AC_TYPE_SSIZE_T
21 |
22 | AC_FUNC_REALLOC
23 | AC_SEARCH_LIBS([endwin],[ncursesw ncurses],[],AC_MSG_ERROR([unable to find ncurses library]))
24 | AC_SEARCH_LIBS([forkpty],[util],[],AC_MSG_ERROR([unable to find util library]))
25 | AC_CHECK_FUNC([alloc_pair],AC_DEFINE([HAVE_ALLOC_PAIR],[1],[ ]))
26 |
27 |
28 | AM_CPPFLAGS='-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED'
29 | AC_CANONICAL_HOST
30 | case "${host_os}" in
31 | darwin*) AM_CPPFLAGS="${AM_CPPFLAGS} -D_DARWIN_C_SOURCE" ;;
32 | esac
33 |
34 |
35 | AC_SUBST([AM_CPPFLAGS])
36 | AC_CONFIG_FILES([Makefile])
37 | AC_OUTPUT
38 |
--------------------------------------------------------------------------------
/cset.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 - 2019 Rob King
3 | * Copyright 2020 - 2023 William Pursell
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | #include "smtx.h"
19 |
20 | wchar_t CSET_US[0x7f]; /* "USASCII" */
21 |
22 | /*
23 | * Avoid unicode except when NDEBUG is defined because
24 | * testing is easier. More thought needs to go here.
25 | */
26 | #if (defined(__STDC_ISO_10646__) || defined(WCHAR_IS_UNICODE)) \
27 | && defined(NDEBUG)
28 |
29 | wchar_t CSET_UK[0x7f] = { [L'#'] = 0x00a3 };
30 | wchar_t CSET_GRAPH[0x7f] = { /* Graphics Set One */
31 | [L'-'] = 0x2191, [L'a'] = 0x2592, [L'k'] = 0x2510, [L'u'] = 0x2524,
32 | [L'}'] = 0x00a3, [L'b'] = 0x2409, [L'l'] = 0x250c, [L'v'] = 0x2534,
33 | [L'~'] = 0x00b7, [L'c'] = 0x240c, [L'm'] = 0x2514, [L'w'] = 0x252c,
34 | [L'{'] = 0x03c0, [L'd'] = 0x240d, [L'n'] = 0x253c, [L'x'] = 0x2502,
35 | [L','] = 0x2190, [L'e'] = 0x240a, [L'o'] = 0x23ba, [L'y'] = 0x2264,
36 | [L'+'] = 0x2192, [L'f'] = 0x00b0, [L'p'] = 0x23bb, [L'z'] = 0x2265,
37 | [L'.'] = 0x2193, [L'g'] = 0x00b1, [L'q'] = 0x2500, [L'_'] = L' ',
38 | [L'|'] = 0x2260, [L'h'] = 0x2592, [L'r'] = 0x23bc, [L'0'] = 0x25ae,
39 | [L'>'] = 0x2265, [L'i'] = 0x2603, [L's'] = 0x23bd,
40 | [L'`'] = 0x25c6, [L'j'] = 0x2518, [L't'] = 0x251c,
41 | };
42 |
43 | #else /* wchar_t doesn't map to Unicode */
44 |
45 | wchar_t CSET_UK[0x7f] = { [L'#'] = L'&' };
46 | wchar_t CSET_GRAPH[0x7f] = { /* Graphics Set One */
47 | [L'-'] = '^', [L'a'] = L':', [L'k'] = L'+', [L'u'] = L'+',
48 | [L'}'] = L'&', [L'b'] = L' ', [L'l'] = L'+', [L'v'] = L'+',
49 | [L'~'] = L'o', [L'c'] = L' ', [L'm'] = L'+', [L'w'] = L'+',
50 | [L'{'] = L'p', [L'd'] = L' ', [L'n'] = '+', [L'x'] = L'|',
51 | [L','] = L'<', [L'e'] = L' ', [L'o'] = L'-', [L'y'] = L'<',
52 | [L'+'] = L'>', [L'f'] = L'\'', [L'p'] = L'-', [L'z'] = L'>',
53 | [L'.'] = L'v', [L'g'] = L'#', [L'q'] = L'-', [L'_'] = L' ',
54 | [L'|'] = L'!', [L'h'] = L'#', [L'r'] = L'-', [L'0'] = L'#',
55 | [L'>'] = L'>', [L'i'] = L'i', [L's'] = L'_',
56 | [L'`'] = L'+', [L'j'] = L'+', [L't'] = L'+',
57 | };
58 |
59 | #endif
60 |
--------------------------------------------------------------------------------
/handler.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 - 2019 Rob King
3 | * Copyright 2020 - 2023 William Pursell
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | #include "smtx.h"
19 |
20 | void
21 | set_status(struct pty *p, const char *arg)
22 | {
23 | snprintf(p->status, sizeof p->status, "%s", arg);
24 | }
25 |
26 | static void
27 | restore_cursor(struct screen *s)
28 | {
29 | if (s->sc.gc) {
30 | s->c = s->sc;
31 | #if HAVE_ALLOC_PAIR
32 | s->c.p = alloc_pair(s->c.color[0], s->c.color[1]);
33 | #endif
34 | wattr_set(s->w, s->c.attr, s->c.p, NULL);
35 | wbkgrndset(s->w, &s->c.bkg);
36 | }
37 | }
38 |
39 | static void
40 | save_cursor(struct screen *s)
41 | {
42 | wattr_get(s->w, &s->c.attr, &s->c.p, NULL);
43 | pair_content(s->c.p, s->sc.color, s->sc.color + 1);
44 | s->sc = s->c;
45 | }
46 |
47 | static void
48 | reset_sgr(struct screen *s)
49 | {
50 | pair_content(s->c.p = COLOR_PAIR(0), s->c.color, s->c.color + 1);
51 | setcchar(&s->c.bkg, L" ", A_NORMAL, s->c.p, NULL);
52 | wattr_set(s->w, A_NORMAL, s->c.p, NULL);
53 | wbkgrndset(s->w, &s->c.bkg);
54 | }
55 |
56 | static void
57 | newline(struct screen *s, int cr)
58 | {
59 | if (cr) {
60 | s->c.xenl = s->c.x = 0;
61 | }
62 | if (s->c.y == s->scroll.bot) {
63 | scroll(s->w);
64 | } else {
65 | wmove(s->w, ++s->c.y, s->c.x);
66 | }
67 | }
68 |
69 | static void
70 | print_char(wchar_t w, struct pty *p)
71 | {
72 | if (p->s->insert) {
73 | wins_wch(p->s->w, &p->s->c.bkg);
74 | }
75 | if (p->s->c.xenl && p->s->decawm) {
76 | newline(p->s, 1);
77 | }
78 | if (w < 0x7f && p->s->c.gc[w]) {
79 | w = p->s->c.gc[w];
80 | }
81 | if (p->s->c.x >= p->ws.ws_col - wcwidth(w)) {
82 | p->s->c.xenl = 1;
83 | wins_nwstr(p->s->w, &w, 1);
84 | } else {
85 | p->s->c.xenl = 0;
86 | waddnwstr(p->s->w, &w, 1);
87 | p->s->c.x += wcwidth(w);
88 | }
89 | p->s->c.gc = p->s->c.gs;
90 | }
91 |
92 | static short colors[] = {
93 | COLOR_BLACK,
94 | COLOR_RED,
95 | COLOR_GREEN,
96 | COLOR_YELLOW,
97 | COLOR_BLUE,
98 | COLOR_MAGENTA,
99 | COLOR_CYAN,
100 | COLOR_WHITE,
101 | };
102 |
103 | static int attrs[] = {
104 | [1] = A_BOLD,
105 | [2] = A_DIM,
106 | #if HAVE_A_ITALIC
107 | [3] = A_ITALIC,
108 | #else
109 | [3] = 0,
110 | #endif
111 | [4] = A_UNDERLINE,
112 | [5] = A_BLINK,
113 | [7] = A_REVERSE,
114 | [8] = A_INVIS,
115 | };
116 |
117 | void
118 | tput(struct pty *p, wchar_t w, wchar_t iw, int argc, int *argv, int handler)
119 | {
120 | int i, t1;
121 |
122 | /* First arg, defaulting to 0 or 1 */
123 | int p0[] = { argc ? *argv : 0, argc ? *argv : 1 };
124 | struct screen *s = p->s; /* the current SCRN buffer */
125 | WINDOW *win = s->w; /* the current window */
126 |
127 | const int tos = p->tos;
128 | const int y = s->c.y - tos; /* cursor position w.r.t. top of screen */
129 | const int bot = s->scroll.bot - tos + 1;
130 | const int top = MAX(0, s->scroll.top - tos);
131 | const int dtop = tos + (p->decom ? top : 0);
132 |
133 | switch ((enum cmd)handler) {
134 | case ack:
135 | rewrite(p->fd, "\006", 1);
136 | break;
137 | case bell:
138 | beep();
139 | break;
140 | case cr:
141 | s->c.x = 0;
142 | break;
143 | case csr:
144 | t1 = argc > 1 ? argv[1] : p->ws.ws_row;
145 | set_scroll(s, tos + p0[1] - 1, tos + t1 - 1);
146 | s->c.y = dtop;
147 | s->c.x = 0;
148 | break;
149 | case cub:
150 | s->c.x -= p0[1];
151 | break;
152 | case cud:
153 | s->c.y += p0[1];
154 | break;
155 | case cuf:
156 | s->c.x += p0[1];
157 | break;
158 | case cup:
159 | s->c.y = dtop + p0[1] - 1;
160 | s->c.x = argc > 1 ? argv[1] - 1 : 0;
161 | break;
162 | case cuu:
163 | s->c.y -= p0[1];
164 | break;
165 | case dch:
166 | for (i = 0; i < p0[1]; i++) {
167 | wdelch(win);
168 | }
169 | break;
170 | case ech:
171 | for (i = 0; i < p0[1]; i++) {
172 | mvwadd_wch(win, s->c.y, s->c.x + i, &s->c.bkg);
173 | }
174 | break;
175 | case ed: /* Fallthru */
176 | case el:
177 | switch (p0[0]) {
178 | case 2:
179 | wmove(win, handler == el ? s->c.y : tos, 0);
180 | /* Fall Thru */
181 | case 0:
182 | (handler == el ? wclrtoeol : wclrtobot)(win);
183 | break;
184 | case 3:
185 | if (handler == ed) {
186 | werase(win);
187 | }
188 | break;
189 | case 1:
190 | if (handler == ed) {
191 | for (i = tos; i < s->c.y; i++) {
192 | wmove(win, i, 0);
193 | wclrtoeol(win);
194 | }
195 | wmove(win, s->c.y, s->c.x);
196 | }
197 | for (i = 0; i <= s->c.x; i++) {
198 | mvwadd_wch(win, s->c.y, i, &s->c.bkg);
199 | }
200 | }
201 | break;
202 | case hpa:
203 | s->c.x = -1;
204 | /* Fallthru */
205 | case hpr:
206 | s->c.x += p0[1];
207 | break;
208 | case hts:
209 | p->tabs[s->c.x] = true;
210 | break;
211 | case ich:
212 | for (i = 0; i < p0[1]; i++) {
213 | wins_wch(win, &p->s->c.bkg);
214 | }
215 | break;
216 | case idl:
217 | /* We don't use insdelln here because it inserts above and
218 | not below, and has a few other edge cases. */
219 | i = MIN(p0[1], p->ws.ws_row - 1 - y);
220 |
221 | assert( y == s->c.y - tos);
222 | assert( tos == 0 || p->ws.ws_row - 1 - y == s->maxy - s->c.y );
223 |
224 | wsetscrreg(win, s->c.y, s->scroll.bot);
225 | wscrl(win, w == L'L' ? -i : i);
226 | wsetscrreg(win, s->scroll.top, s->scroll.bot);
227 | s->c.x = 0;
228 | break;
229 | case numkp:
230 | p->pnm = (w == L'=');
231 | break;
232 | case osc:
233 | assert( 0 );
234 | break;
235 | case rc:
236 | if (iw == L'#' ) for (int r = 0; r < p->ws.ws_row; r++) {
237 | cchar_t e;
238 | setcchar(&e, L"E", A_NORMAL, COLOR_PAIR(0), NULL);
239 | for (int c = 0; c < p->ws.ws_col; c++) {
240 | mvwadd_wch(p->s->w, tos + r, c, &e);
241 | }
242 | }
243 | restore_cursor(s);
244 | break;
245 | case ri:
246 | if (y == top) {
247 | wsetscrreg(win, MAX(s->scroll.top, tos), s->scroll.bot);
248 | wscrl(win, -1);
249 | wsetscrreg(win, s->scroll.top, s->scroll.bot);
250 | } else {
251 | s->c.y = MAX(tos, s->c.y - 1);
252 | }
253 | break;
254 | case sc:
255 | save_cursor(s);
256 | break;
257 | case su:
258 | wscrl(win, (w == L'T' || w == L'^') ? -p0[1] : p0[1]);
259 | break;
260 | case tab:
261 | for (i = 0; i < p0[1]; i += p->tabs[s->c.x] ? 1 : 0) {
262 | s->c.x += (w == L'Z' ? -1 : +1);
263 | }
264 | break;
265 | case tbc:
266 | switch (p0[0]) {
267 | case 0:
268 | p->tabs[s->c.x] = false;
269 | break;
270 | case 3:
271 | memset(p->tabs, 0, p->ws.ws_col * sizeof *p->tabs);
272 | }
273 | break;
274 | case vis:
275 | s->vis = iw != L'6';
276 | break;
277 | case vpa:
278 | s->c.y = tos - 1;
279 | /* Fallthru */
280 | case vpr:
281 | s->c.y = MAX(tos + top, p0[1] + s->c.y);
282 | break;
283 | case ris:
284 | ioctl(p->fd, TIOCGWINSZ, &p->ws);
285 | p->g[0] = p->g[2] = CSET_US;
286 | p->g[1] = p->g[3] = CSET_GRAPH;
287 | p->decom = s->insert = p->lnm = false;
288 | reset_sgr(s);
289 | s->decawm = p->pnm = true;
290 | for (i = 0, s = p->s = p->scr; i < 2; i++, s++) {
291 | s->c.gs = s->c.gc = CSET_US;
292 | s->vis = 1;
293 | set_scroll(s, 0, s->rows - 1);
294 | }
295 | set_tabs(p, p->tabstop);
296 | vtreset(&p->vp);
297 | break;
298 | case mode:
299 | for (i = 0; i < argc; i++) {
300 | bool set = (w == L'h');
301 | switch (argv[i]) {
302 | case 1:
303 | p->pnm = set;
304 | break;
305 | case 3:
306 | set_width(set ? "132" : "80");
307 | break;
308 | case 4:
309 | s->insert = set;
310 | break;
311 | case 6:
312 | p->decom = set;
313 | s->c.x = s->c.xenl = 0;
314 | s->c.y = dtop;
315 | break;
316 | case 7:
317 | s->decawm = set;
318 | break;
319 | case 20:
320 | p->lnm = set;
321 | break;
322 | case 25:
323 | s->vis = set ? 1 : 0;
324 | break;
325 | case 34:
326 | s->vis = set ? 1 : 2;
327 | break;
328 | case 1048:
329 | (set ? save_cursor : restore_cursor)(s);
330 | break;
331 | case 1049:
332 | (set ? save_cursor : restore_cursor)(s);
333 | /* fall thru */
334 | case 47:
335 | case 1047:
336 | /* Switch to alternate screen */
337 | if (set && p->s == p->scr) {
338 | struct screen *alt = p->scr + 1;
339 | alt->c.x = alt->c.xenl = 0;
340 | alt->c.y = dtop;
341 | wclear(alt->w);
342 | }
343 | p->s = p->scr + !!set;
344 | }
345 | }
346 | break;
347 | case sgr:
348 | {
349 | bool doc = false;
350 | if (!argc) {
351 | reset_sgr(s);
352 | } else for (i = 0; i < argc; i++) {
353 | int k = 1, a;
354 | switch (a = argv[i]) {
355 | case 0:
356 | reset_sgr(s);
357 | break;
358 | case 1:
359 | case 2:
360 | case 3:
361 | case 4:
362 | case 5:
363 | case 7:
364 | case 8:
365 | wattron(win, attrs[a]);
366 | break;
367 | case 21:
368 | case 22:
369 | case 23:
370 | case 24:
371 | case 25:
372 | case 27:
373 | wattroff(win, attrs[a - 20]);
374 | break;
375 | case 30:
376 | case 31:
377 | case 32:
378 | case 33:
379 | case 34:
380 | case 35:
381 | case 36:
382 | case 37:
383 | k = 0; /* Fallthru */
384 | case 40:
385 | case 41:
386 | case 42:
387 | case 43:
388 | case 44:
389 | case 45:
390 | case 46:
391 | case 47:
392 | s->c.color[k] = colors[a - ( k ? 40 : 30 )];
393 | doc = COLORS >= 8;
394 | break;
395 | case 38:
396 | case 48:
397 | if (argc > i + 2 && argv[i + 1] == 5){
398 | s->c.color[a == 48] = argv[i + 2];
399 | }
400 | i += 2;
401 | doc = COLORS >= 256;
402 | break;
403 | case 39:
404 | case 49:
405 | s->c.color[a == 49] = -1;
406 | doc = true;
407 | break;
408 | case 90:
409 | case 91:
410 | case 92:
411 | case 93:
412 | case 94:
413 | case 95:
414 | case 96:
415 | case 97:
416 | k = 0; /* Fallthru */
417 | case 100:
418 | case 101:
419 | case 102:
420 | case 103:
421 | case 104:
422 | case 105:
423 | case 106:
424 | case 107:
425 | s->c.color[k] = colors[a - ( k ? 100 : 90 )];
426 | doc = COLORS >= 16;
427 | }
428 | }
429 | if (doc) {
430 | #if HAVE_ALLOC_PAIR
431 | s->c.p = alloc_pair(s->c.color[0], s->c.color[1]);
432 | #endif
433 | wcolor_set(win, s->c.p, NULL);
434 | setcchar(&s->c.bkg, L" ", A_NORMAL, s->c.p, NULL);
435 | wbkgrndset(win, &s->c.bkg);
436 | }
437 | }
438 | break;
439 | case pnl:
440 | case nel:
441 | case ind:
442 | newline(s, handler == pnl ? p->lnm : handler == nel);
443 | break;
444 | case cpl:
445 | s->c.y = MAX(tos + top, s->c.y - p0[1]);
446 | s->c.x = 0;
447 | break;
448 | case cnl:
449 | s->c.y = MIN(tos + bot - 1, s->c.y + p0[1]);
450 | s->c.x = 0;
451 | break;
452 | case print:
453 | s->repc = w;
454 | /* Fallthru */
455 | case rep:
456 | if (wcwidth(w = s->repc) > 0) {
457 | for (i = 0; i < p0[1]; i++) {
458 | print_char(w, p);
459 | }
460 | }
461 | break;
462 | case scs:
463 | for (const char *s = "()*+", *c = strchr(s, iw); c; c = NULL )
464 | switch (w) {
465 | case L'A':
466 | p->g[c-s] = CSET_UK;
467 | break;
468 | case L'B':
469 | p->g[c-s] = CSET_US;
470 | break;
471 | case L'0':
472 | p->g[c-s] = CSET_GRAPH;
473 | break;
474 | case L'1':
475 | p->g[c-s] = CSET_US;
476 | break;
477 | case L'2':
478 | p->g[c-s] = CSET_GRAPH;
479 | }
480 | break;
481 | case so:
482 | for (char *s = "\x0f\x0e}|NO", *c = strchr(s, w); c; c = NULL) {
483 | switch (c - s) {
484 | case 0: /* locking shift */
485 | case 1:
486 | case 2:
487 | case 3:
488 | p->s->c.gs = p->s->c.gc = p->g[c - s];
489 | break;
490 | case 4:
491 | case 5: /* non-locking shift */
492 | p->s->c.gs = p->s->c.gc;
493 | p->s->c.gc = p->g[c - s - 2];
494 | }
495 | }
496 | }
497 | switch (handler) {
498 | case cr:
499 | case csr:
500 | case cub:
501 | case cup:
502 | case ris:
503 | p->s->c.xenl = 0; /*Fallthru */
504 | default:
505 | p->s->repc = 0;
506 | break;
507 | case sgr:
508 | case print:
509 | ;
510 | }
511 | p->s->c.x = MAX(0, MIN(p->s->c.x, p->ws.ws_col - 1));
512 | p->s->c.y = MAX(0, MIN(p->s->c.y, tos + bot - 1));
513 | p->s->maxy = MAX(p->s->c.y, p->s->maxy);
514 | p->tos = MAX(0, p->s->maxy - p->ws.ws_row + 1);
515 | wmove(p->s->w, p->s->c.y, p->s->c.x);
516 | }
517 |
518 | #define CONTROL \
519 | [0x05] = ack, \
520 | [0x07] = bell, \
521 | [0x08] = cub, \
522 | [0x09] = tab, \
523 | [0x0a] = pnl, \
524 | [0x0b] = pnl, \
525 | [0x0c] = pnl, \
526 | [0x0d] = cr, \
527 | [0x0e] = so, \
528 | [0x0f] = so
529 |
530 | int cons[0x80] = {
531 | CONTROL,
532 | };
533 |
534 | int csis[0x80] = {
535 | CONTROL,
536 | [L'A'] = cuu,
537 | [L'B'] = cud,
538 | [L'C'] = cuf,
539 | [L'D'] = cub,
540 | [L'E'] = cnl,
541 | [L'F'] = cpl,
542 | [L'G'] = hpa,
543 | [L'H'] = cup,
544 | [L'I'] = tab,
545 | [L'J'] = ed,
546 | [L'K'] = el,
547 | [L'L'] = idl,
548 | [L'M'] = idl,
549 | [L'P'] = dch,
550 | [L'S'] = su,
551 | [L'T'] = su,
552 | [L'X'] = ech,
553 | [L'Z'] = tab,
554 | [L'`'] = hpa,
555 | [L'^'] = su,
556 | [L'@'] = ich,
557 | [L'a'] = hpr,
558 | [L'b'] = rep,
559 | [L'd'] = vpa,
560 | [L'e'] = vpr,
561 | [L'f'] = cup,
562 | [L'g'] = tbc,
563 | [L'h'] = mode,
564 | [L'l'] = mode,
565 | [L'm'] = sgr,
566 | [L'r'] = csr,
567 | [L's'] = sc,
568 | [L'u'] = rc,
569 | };
570 |
571 | int escs[0x80] = {
572 | CONTROL,
573 | [L'0'] = scs,
574 | [L'1'] = scs,
575 | [L'2'] = scs,
576 | [L'7'] = sc,
577 | [L'8'] = rc,
578 | [L'A'] = scs,
579 | [L'B'] = scs,
580 | [L'D'] = ind,
581 | [L'E'] = nel,
582 | [L'H'] = hts,
583 | [L'M'] = ri,
584 | [L'N'] = so,
585 | [L'O'] = so,
586 | [L'}'] = so,
587 | [L'|'] = so,
588 | [L'c'] = ris,
589 | [L'p'] = vis,
590 | [L'='] = numkp,
591 | [L'>'] = numkp,
592 | };
593 |
594 | #pragma GCC diagnostic ignored "-Woverride-init"
595 | int oscs[0x80] = {
596 | [0 ... 0x7f] = osc,
597 | CONTROL,
598 | [0x07] = osc,
599 | [0x0a] = osc,
600 | [0x0d] = osc,
601 | };
602 |
603 | int gnds[0x80] = {
604 | [0 ... 0x7f] = print,
605 | CONTROL,
606 | };
607 |
--------------------------------------------------------------------------------
/smtx-main.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 - 2019 Rob King
3 | * Copyright 2020 - 2023 William Pursell
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | #include "smtx.h"
19 |
20 | struct state S = {
21 | .ctlkey = CTRL('g'),
22 | .rawkey = 'g',
23 | .term = "smtx",
24 | .width = 80,
25 | .binding = k1,
26 | .history = 1024,
27 | .count = -1,
28 | };
29 |
30 | static const char *
31 | getshell(void)
32 | {
33 | const char *s = getenv("SHELL");
34 | struct passwd *pwd = s ? NULL : getpwuid(geteuid());
35 | return s ? s : pwd ? pwd->pw_shell : "/bin/sh";
36 | }
37 |
38 | void
39 | set_tabs(struct pty *p, int tabstop)
40 | {
41 | typeof(*p->tabs) *n;
42 | if ((n = realloc(p->tabs, p->ws.ws_col * sizeof *n)) != NULL) {
43 | memset(p->tabs = n, 0, p->ws.ws_col * sizeof *n);
44 | for (int i = 0; i < p->ws.ws_col; i += tabstop) {
45 | p->tabs[i] = true;
46 | }
47 | }
48 | }
49 |
50 | int
51 | resize_pad(WINDOW **p, int h, int w)
52 | {
53 | int rv = 0;
54 | if (*p) {
55 | rv = wresize(*p, h, w ) == OK;
56 | } else if ((*p = newpad(h, w)) != NULL) {
57 | wtimeout(*p, 0);
58 | scrollok(*p, 1);
59 | rv = (keypad(*p, 1) == OK);
60 | }
61 | return check(rv, ENOMEM, "wresize"); /* 0 is failure */
62 | }
63 |
64 | static struct pty *
65 | get_freepty(bool allow_hidden)
66 | {
67 | struct pty *t = S.p;
68 | while (t && (!allow_hidden || t->count) && t->fd != -1) {
69 | t = t->next;
70 | }
71 | return t ? t : calloc(1, sizeof *t);
72 | }
73 |
74 | struct pty *
75 | new_pty(int rows, int cols, bool new)
76 | {
77 | struct pty *p = get_freepty(!new);
78 | if (check(p != NULL, errno = 0, "calloc")) {
79 | if (p->s == NULL) {
80 | if (resize_pad(&p->scr[0].w, rows, cols)
81 | && resize_pad(&p->scr[1].w, rows, cols)
82 | ){
83 | p->scr[0].rows = p->scr[1].rows = rows;
84 | *(S.tail ? &S.tail->next : &S.p) = p;
85 | S.tail = p;
86 | set_scroll(p->scr, 0, rows - 1);
87 | set_scroll(&p->scr[1], 0, rows - 1);
88 | } else {
89 | delwin(p->scr[0].w);
90 | delwin(p->scr[1].w);
91 | free(p);
92 | return NULL;
93 | }
94 | }
95 | if (p->fd < 1) {
96 | const char *sh = getshell();
97 | p->ws.ws_row = LINES - 1;
98 | p->ws.ws_col = cols;
99 | p->tos = rows - p->ws.ws_row;
100 | p->pid = forkpty(&p->fd, p->secondary, NULL, &p->ws);
101 | if (check(p->pid != -1, 0, "forkpty") && p->pid == 0) {
102 | setsid();
103 | signal(SIGCHLD, SIG_DFL);
104 | setenv("TERM", S.term, 1);
105 | execl(sh, sh, NULL);
106 | err(EXIT_FAILURE, "exec SHELL='%s'", sh);
107 | }
108 | set_tabs(p, p->tabstop = 8);
109 | FD_SET(p->fd, &S.fds);
110 | S.maxfd = p->fd > S.maxfd ? p->fd : S.maxfd;
111 | fcntl(p->fd, F_SETFL, O_NONBLOCK);
112 | const char *bname = strrchr(sh, '/');
113 | bname = bname ? bname + 1 : sh;
114 | strncpy(p->status, bname, sizeof p->status - 1);
115 | }
116 | if (p->s == NULL) {
117 | p->s = &p->scr[0];
118 | tput(p, 0, 0, 0, NULL, ris);
119 | p->vp.p = p;
120 | }
121 | }
122 | return p;
123 | }
124 |
125 | struct canvas *
126 | newcanvas(struct pty *p, struct canvas *parent)
127 | {
128 | struct canvas *n = S.unused;
129 | if (n != NULL) {
130 | S.unused = n->c[0];
131 | } else {
132 | check((n = calloc(1 , sizeof *n)) != NULL, 0, "calloc");
133 | }
134 | if (n) {
135 | n->c[0] = n->c[1] = NULL;
136 | n->manualscroll = n->offset.y = n->offset.x = 0;
137 | n->p = p ? p : new_pty(S.history, MAX(COLS, S.width), false);
138 | n->parent = parent;
139 | if (n->p) {
140 | n->p->count += 1;
141 | }
142 | n->split = (typeof(n->split)){1.0, 1.0};
143 | }
144 | return n;
145 | }
146 |
147 | static void
148 | draw_window(struct canvas *n)
149 | {
150 | struct point o = n->origin;
151 | struct point e = { o.y + n->extent.y - 1, o.x + n->extent.x - 1 };
152 | if (n->p && e.y > 0 && e.x > 0) {
153 | if (! n->manualscroll) {
154 | n->offset.x = MAX(0, n->p->s->c.x - n->extent.x + 1);
155 | }
156 | struct point off = n->offset;
157 | if (n->p->ws.ws_col < n->extent.x) {
158 | assert( n->offset.x == 0 );
159 | pnoutrefresh(S.wbkg, 0, 0, o.y, o.x + n->p->ws.ws_col,
160 | e.y, e.x);
161 | }
162 | pnoutrefresh(n->p->s->w, off.y, off.x, o.y, o.x, e.y, e.x);
163 | }
164 | }
165 |
166 | static void
167 | fixcursor(void) /* Move the terminal cursor to the active window. */
168 | {
169 | int y = S.f->p->s->c.y, x = S.f->p->s->c.x;
170 | int show = S.binding != ctl && S.f->extent.y
171 | && x >= S.f->offset.x && x < S.f->offset.x + S.f->extent.x
172 | && y >= S.f->offset.y && y < S.f->offset.y + S.f->extent.y;
173 | draw_window(S.f);
174 | curs_set(show ? S.f->p->s->vis : 0);
175 | }
176 |
177 | void
178 | reshape_window(struct pty *p)
179 | {
180 | check(ioctl(p->fd, TIOCSWINSZ, &p->ws) == 0, 0, "ioctl on %d", p->fd);
181 | check(kill(p->pid, SIGWINCH) == 0, 0, "send WINCH to %d", (int)p->pid);
182 | set_scroll(p->scr, 0, p->scr->rows - 1);
183 | set_scroll(p->scr + 1, p->tos, p->scr->rows - 1);
184 | }
185 |
186 | void
187 | set_scroll(struct screen *s, int top, int bottom)
188 | {
189 | wsetscrreg(s->w, s->scroll.top = top, s->scroll.bot = bottom);
190 | }
191 |
192 |
193 | void
194 | scrollbottom(struct canvas *n)
195 | {
196 | if (n && n->p && n->p->s && n->extent.y) {
197 | n->offset.y = MAX(n->p->s->maxy - n->extent.y + 1, 0);
198 | }
199 | }
200 |
201 | void
202 | reshape(struct canvas *n, int y, int x, int h, int w)
203 | {
204 | if (n) {
205 | n->origin.y = y;
206 | n->origin.x = x;
207 | int h1 = h * n->split.y;
208 | int w1 = w * n->split.x;
209 | int have_div = h1 > 0 && w1 > 0 && n->c[1];
210 |
211 | assert(n->split.y >= 0.0);
212 | assert(n->split.y <= 1.0);
213 | assert(n->split.x >= 0.0);
214 | assert(n->split.x <= 1.0);
215 | resize_pad(&n->wdiv, n->typ ? h : h1, 1);
216 | resize_pad(&n->wtit, 1, w1);
217 | reshape(n->c[0], y + h1, x, h - h1, n->typ ? w1 : w);
218 | reshape(n->c[1], y, x + w1 + have_div,
219 | n->typ ? h : h1, w - w1 - have_div);
220 | n->extent.y = h1 > 0 ? h1 - 1 : 0;
221 | n->extent.x = w1;
222 |
223 | /* If the pty is visible in multiple canvasses,
224 | set ws.ws_row to the one with biggest extent.y */
225 | if (n->p->fd >= 0 && n->extent.y > n->p->ws.ws_row) {
226 | n->p->ws.ws_row = n->extent.y;
227 | n->p->tos = n->p->scr->rows - n->extent.y;
228 | reshape_window(n->p);
229 | wrefresh(n->p->s->w);
230 | }
231 | scrollbottom(n);
232 | }
233 | S.reshape = 0;
234 | }
235 |
236 | void
237 | change_count(struct canvas * n, int count, int pop)
238 | {
239 | assert( count < 2 && count > -2 );
240 | if (n && n->p) {
241 | n->p->count += count;
242 | change_count(n->c[0], count, pop);
243 | change_count(n->c[1], count, pop);
244 | if (pop && n->p->count == 0) {
245 | freecanvas(n);
246 | }
247 | }
248 | }
249 |
250 | void
251 | freecanvas(struct canvas *n)
252 | {
253 | if (n == S.f) {
254 | S.f = n->parent;
255 | }
256 | if (n->parent) {
257 | n->parent->c[n == n->parent->c[1]] = NULL;
258 | }
259 | n->c[0] = S.unused;
260 | n->c[1] = NULL;
261 | n->parent = NULL;
262 | S.unused = n;
263 | }
264 |
265 | static void
266 | draw_pane(WINDOW *w, int y, int x)
267 | {
268 | int wy, wx;
269 | getmaxyx(w, wy, wx);
270 | pnoutrefresh(w, 0, 0, y, x, y + wy - 1, x + wx - 1);
271 | }
272 |
273 | static void
274 | draw_title(struct canvas *n, int r)
275 | {
276 | assert( n->wtit );
277 | assert( n->p );
278 | wattrset(n->wtit, r ? A_REVERSE : A_NORMAL);
279 | mvwprintw(n->wtit, 0, 0, "%d %s ",
280 | n->p->fd > 2 ? n->p->fd - 2 : n->p->pid,
281 | n->p->status);
282 | int x = n->offset.x;
283 | int w = n->p->ws.ws_col;
284 | if (x > 0 || x + n->extent.x < w) {
285 | wprintw(n->wtit, "%d-%d/%d ", x + 1, x + n->extent.x, w);
286 | }
287 | whline(n->wtit, ACS_HLINE, n->extent.x);
288 | struct point o = n->origin;
289 | draw_pane(n->wtit, o.y + n->extent.y, o.x);
290 | }
291 |
292 | static void
293 | draw_div(struct canvas *n, int rev)
294 | {
295 | wattrset(n->wdiv, rev ? A_REVERSE : A_NORMAL);
296 | mvwvline(n->wdiv, 0, 0, ACS_VLINE, INT_MAX);
297 | draw_pane(n->wdiv, n->origin.y, n->origin.x + n->extent.x);
298 | }
299 |
300 | void
301 | draw(struct canvas *n) /* Draw a canvas. */
302 | {
303 | if (n != NULL && n->extent.y > 0) {
304 | int rev = S.binding == ctl && n == S.f;
305 | draw(n->c[0]);
306 | if (n->c[1]) {
307 | draw_div(n, rev && !n->extent.x);
308 | draw(n->c[1]);
309 | }
310 | draw_window(n);
311 | draw_title(n, rev);
312 | }
313 | }
314 |
315 | static void
316 | wait_child(struct pty *p)
317 | {
318 | int status, k = 0;
319 | const char *fmt = "exited %d";
320 | if (check(waitpid(p->pid, &status, WNOHANG) != -1, 0, "waitpid")) {
321 | if (WIFEXITED(status)) {
322 | k = WEXITSTATUS(status);
323 | } else if (WIFSIGNALED(status)) {
324 | fmt = "caught signal %d";
325 | k = WTERMSIG(status);
326 | }
327 | FD_CLR(p->fd, &S.fds);
328 | check(close(p->fd) == 0, 0, "close fd %d", p->fd);
329 | snprintf(p->status, sizeof p->status, fmt, k);
330 | p->fd = -1; /* (1) */
331 | S.reshape = 1;
332 | }
333 | }
334 | /* (1) We do not free(p) because we wish to retain error messages.
335 | * The windows will persist until the user explicitly destroys them.
336 | */
337 |
338 | static void
339 | getinput(void) /* check stdin and all pty's for input. */
340 | {
341 | fd_set sfds = S.fds;
342 | if (select(S.maxfd + 1, &sfds, NULL, NULL, NULL) < 0) {
343 | check(errno == EINTR, 0, "select");
344 | return;
345 | }
346 | if (FD_ISSET(STDIN_FILENO, &sfds)) {
347 | int r;
348 | wint_t w;
349 | while (S.f && (r = wget_wch(S.f->p->s->w, &w)) != ERR) {
350 | struct handler *b = NULL;
351 | if (r == OK && w > 0 && w < 128) {
352 | b = S.binding + w;
353 | } else if (r == KEY_CODE_YES) {
354 | assert( w >= KEY_MIN && w <= KEY_MAX );
355 | b = &code_keys[w - KEY_MIN];
356 | }
357 | if (b) {
358 | b->arg ? b->act.a(b->arg) : b->act.v();
359 | if (b->act.a != digit) {
360 | S.count = -1;
361 | }
362 | }
363 | }
364 | }
365 | for (struct pty *t = S.p; t; t = t->next) {
366 | if (t->fd > 0 && FD_ISSET(t->fd, &sfds)) {
367 | FD_CLR(t->fd, &sfds);
368 | char iobuf[BUFSIZ];
369 | int oldmax = t->s->maxy;
370 | ssize_t r = read(t->fd, iobuf, sizeof iobuf);
371 | if (r > 0) {
372 | vtwrite(&t->vp, iobuf, r);
373 | t->s->delta = t->s->maxy - oldmax;
374 | } else if (errno != EINTR && errno != EWOULDBLOCK) {
375 | wait_child(t);
376 | }
377 | }
378 | }
379 | }
380 |
381 | void
382 | sendarrow(const char *k)
383 | {
384 | struct canvas *n = S.f;
385 | char buf[3] = { '\033', n->p->pnm ? 'O' : '[', *k };
386 | rewrite(n->p->fd, buf, 3);
387 | }
388 |
389 | /*
390 | * wc_lut is built from the output of wctomb, so that for k such that
391 | * k % (1 + MB_LEN_MAX) == 0, wc_lut[k] is the value returned by
392 | * wctomb( ..., k) and wc_lut[k+1 ...] is the resultant multi-byte value.
393 | */
394 | static char wc_lut[(KEY_MAX - KEY_MIN + 1) * ( 1 + MB_LEN_MAX )];
395 |
396 | static void
397 | build_bindings(void)
398 | {
399 |
400 | k1[S.ctlkey] = (struct handler){ { .a = transition }, " control" };
401 | ctl[S.ctlkey] = (struct handler){ { .a = transition }, "*enter" };
402 |
403 | for (wchar_t k = KEY_MIN; k < KEY_MAX; k++) {
404 | int idx = k - KEY_MIN;
405 | if (code_keys[idx].arg || code_keys[idx].act.v) {
406 | continue;
407 | }
408 | assert( MB_LEN_MAX < 128 );
409 | int i = idx * (1 + MB_LEN_MAX);
410 | int v = wctomb(wc_lut + i + 1, k);
411 | assert( v < 128 && v > -2 );
412 | wc_lut[ i ] = v == -1 ? 0 : v;
413 | code_keys[idx].act.a = send;
414 | code_keys[idx].arg = wc_lut + i;
415 | }
416 | }
417 |
418 | static void
419 | update_offset_r(struct canvas *n)
420 | {
421 | if (n) {
422 | if (n->p && n->p->s && n->p->s->delta) {
423 | int d = n->p->s->delta;
424 | int t = n->p->s->maxy - n->extent.y + 1;
425 | if (t > 0) {
426 | n->offset.y += MIN(d, t);
427 | }
428 | }
429 | update_offset_r(n->c[0]);
430 | update_offset_r(n->c[1]);
431 | }
432 | }
433 |
434 | static void
435 | main_loop(void)
436 | {
437 | while (S.root != NULL) {
438 | if (S.reshape) {
439 | reshape(S.root, 0, 0, LINES, COLS);
440 | wrefresh(curscr);
441 | }
442 | draw(S.root);
443 | if (*S.errmsg) {
444 | mvwprintw(S.werr, 0, 0, "%s", S.errmsg);
445 | wclrtoeol(S.werr);
446 | draw_pane(S.werr, LINES - 1, 0);
447 | }
448 | fixcursor();
449 | doupdate();
450 | getinput();
451 | update_offset_r(S.root);
452 | for (struct pty *p = S.p; p; p = p->next) {
453 | p->s->delta = 0;
454 | }
455 | }
456 | }
457 |
458 | void
459 | endwin_wrap(void)
460 | {
461 | (void)endwin();
462 | }
463 |
464 | static void
465 | init(void)
466 | {
467 | signal(SIGTERM, exit);
468 | FD_ZERO(&S.fds);
469 | FD_SET(STDIN_FILENO, &S.fds);
470 | build_bindings();
471 | atexit(endwin_wrap);
472 | initscr(); /* exits on failure */
473 | S.history = MAX(LINES, S.history);
474 | raw();
475 | noecho();
476 | nonl();
477 | timeout(0);
478 | intrflush(NULL, FALSE);
479 | start_color();
480 | use_default_colors();
481 | resize_pad(&S.werr, 1, COLS);
482 | resize_pad(&S.wbkg, LINES, COLS);
483 | wbkgd(S.wbkg, ACS_BULLET);
484 | wborder(S.wbkg, ACS_VLINE, ACS_BULLET, ACS_BULLET, ACS_BULLET,
485 | ACS_VLINE, ACS_BULLET, ACS_BULLET, ACS_BULLET);
486 | wattron(S.werr, A_REVERSE);
487 | S.f = S.root = newcanvas(NULL, NULL);
488 | if (S.root == NULL || S.werr == NULL || S.wbkg == NULL || !S.root->p) {
489 | endwin();
490 | errx(EXIT_FAILURE, "Unable to create root window");
491 | }
492 | reshape_root();
493 | }
494 |
495 | int
496 | smtx_main()
497 | {
498 | init();
499 | main_loop();
500 | return EXIT_SUCCESS;
501 | }
502 |
503 | /*
504 | Parse a string to build a layout. The layout is expected
505 | to consist of coordinates of the lower right corner of each
506 | window as a percentage of the full screen. eg:
507 |
508 | .5:.5 .25:.66 .5:.66 .5:.83 .5:1 1:.25 1:1
509 |
510 | would describe a layout that looks like:
511 | +--------------------------------------+
512 | | | | | |
513 | | | | | |
514 | | |------b | |
515 | | | | | |
516 | | | | | |
517 | -------------------a------c------d-----e
518 | | | |
519 | | | |
520 | | | |
521 | | | |
522 | +---------f----------------------------g
523 | Note that order matters.
524 | */
525 | static struct canvas *
526 | add_canvas(const char **lp, double oy, double ox, double ey, double ex,
527 | struct pty **pp, struct canvas *parent)
528 | {
529 | double y, x;
530 | int e;
531 | struct canvas *n;
532 | struct pty *p = *pp;
533 | if(
534 | (n = newcanvas(p, parent)) == NULL
535 | || n->p == NULL
536 | || ! check(2 == sscanf(*lp, "%lf%*1[,:]%lf%n", &y, &x, &e),
537 | errno = 0, "Invalid format at %s", **lp ? *lp : "end")
538 | || ! check(y > oy && y <= ey && x > ox && x <= ex,
539 | errno = 0, "Out of bounds: %s", *lp)
540 | ){
541 | goto fail;
542 | }
543 | *lp += e;
544 | *pp = p ? (( p->next ? p->next : S.p ) == S.f->p ) ? NULL : p->next : p;
545 | n->split.y = (y - oy) / (ey - oy);
546 | n->split.x = (x - ox) / (ex - ox);
547 | if (y == ey && x == ex) {
548 | return n;
549 | } else if (y == ey) {
550 | n->typ = 1;
551 | } else if (x == ex) {
552 | n->typ = 0;
553 | } else {
554 | double ny, nx;
555 | if (! check(2 == sscanf(*lp, "%lf%*1[,:]%lf", &ny, &nx),
556 | errno = 0, "Invalid format @ '%s'", *lp)
557 | || ! check(y <= ny || x <= nx, 0,
558 | "Out of bounds: %s", *lp)
559 | || (n->typ = y < ny,
560 | (n->c[!n->typ] = add_canvas(lp,
561 | n->typ ? y : oy, n->typ ? ox : x,
562 | n->typ ? ey : y, n->typ ? x : ex,
563 | pp, n)) == NULL )) {
564 | goto fail;
565 | }
566 | }
567 | if ((n->c[n->typ] = add_canvas(lp,
568 | n->typ ? oy : y, n->typ ? x : ox,
569 | ey, ex, pp, n)) == NULL) {
570 | goto fail;
571 | }
572 |
573 | return n;
574 | fail:
575 | change_count(n, -1, 1);
576 | return NULL;
577 | }
578 |
579 | int
580 | build_layout(const char *layout)
581 | {
582 | struct pty *p = S.f->p;
583 | change_count(S.root, -1, 0); /* (1) */
584 | struct canvas *n = add_canvas(&layout, 0.0, 0.0, 1.0, 1.0, &p, NULL);
585 | change_count(S.root, +1, 0);
586 | if (n) {
587 | change_count(S.root, -1, 1);
588 | S.f = S.root = n;
589 | S.reshape = 1;
590 | }
591 | return S.reshape;
592 | }
593 | /* (1) Decrement the view counts so that visible ptys will be availalbe for
594 | * the new layout.
595 | */
596 |
--------------------------------------------------------------------------------
/smtx.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 - 2023 William Pursell
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 | #include "smtx.h"
18 |
19 | static void
20 | parse_args(int argc, char *const*argv)
21 | {
22 | int c;
23 | char *name = strrchr(argv[0], '/');
24 | while ((c = getopt(argc, argv, ":c:hs:t:vw:")) != -1) {
25 | switch (c) {
26 | default:
27 | fprintf(stderr, "Unknown option: %c", optopt);
28 | exit(EXIT_FAILURE);
29 | case 'c':
30 | S.ctlkey = CTRL(S.rawkey = optarg[0]);
31 | break;
32 | case 'h':
33 | printf("usage: %s", name ? name + 1 : argv[0]);
34 | puts(
35 | " [-c ctrl-key]"
36 | " [-h]"
37 | " [-s history-size]"
38 | " [-t terminal-type]"
39 | " [-v]"
40 | " [-w width]"
41 | );
42 | exit(EXIT_SUCCESS);
43 | case 's':
44 | S.history = strtol(optarg, NULL, 10);
45 | break;
46 | case 't':
47 | S.term = optarg;
48 | break;
49 | case 'v':
50 | printf("%s-%s\n", PACKAGE_NAME, PACKAGE_VERSION);
51 | exit(EXIT_SUCCESS);
52 | case 'w':
53 | S.width = strtol(optarg, NULL, 10);
54 | }
55 | }
56 | }
57 |
58 | /* If rv is non-zero, emit the error message */
59 | int
60 | check(int rv, int err, const char *fmt, ...)
61 | {
62 | if (!rv) {
63 | int e = err ? err : errno;
64 | va_list ap;
65 | va_start(ap, fmt);
66 | size_t len = sizeof S.errmsg;
67 | int n = vsnprintf(S.errmsg, len, fmt, ap);
68 | if (e && n + 3 < (int)len) {
69 | strncat(S.errmsg, ": ", len - n);
70 | strncat(S.errmsg, strerror(e), len - n - 2);
71 | }
72 | va_end(ap);
73 | }
74 | return !!rv;
75 | }
76 |
77 | void
78 | rewrite(int fd, const char *b, size_t n)
79 | {
80 | const char *e = b + n;
81 | ssize_t s;
82 | if (n > 0 ) do {
83 | s = write(fd, b, e - b);
84 | b += s < 0 ? 0 : s;
85 | } while (b < e && check(s >= 0 || errno == EINTR, 0, "write %d", fd) );
86 | }
87 |
88 | int
89 | main(int argc, char **argv)
90 | {
91 | char buf[16];
92 | parse_args(argc, argv);
93 | snprintf(buf, sizeof buf - 1, "%d", getpid());
94 | setenv("SMTX", buf, 1);
95 | setenv("SMTX_VERSION", VERSION, 1);
96 | unsetenv("LINES");
97 | unsetenv("COLUMNS");
98 | setlocale(LC_ALL, "");
99 | return smtx_main();
100 | }
101 |
--------------------------------------------------------------------------------
/smtx.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 - 2019 Rob King
3 | * Copyright 2020 - 2023 William Pursell
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | #include "config.h"
19 | #include
20 | #include
21 | #if HAVE_CURSES_H
22 | # include
23 | #elif HAVE_NCURSESW_CURSES_H
24 | # include
25 | #endif
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #if HAVE_PTY_H
32 | # include
33 | # include
34 | #elif HAVE_LIBUTIL_H
35 | # include
36 | #elif HAVE_UTIL_H
37 | # include
38 | #endif
39 | #include
40 | #include
41 | #include
42 | #include
43 | #include
44 | #include
45 | #include
46 | #include
47 | #include
48 | #include
49 | #include
50 | #include
51 | #include
52 | #include
53 |
54 | #include "vtparser.h"
55 |
56 | extern int smtx_main(void);
57 |
58 | #define MIN(x, y) ((x) < (y) ? (x) : (y))
59 | #define MAX(x, y) ((x) > (y) ? (x) : (y))
60 | #ifndef CTRL
61 | #define CTRL(x) ((x) & 0x1f)
62 | #endif
63 |
64 | struct canvas;
65 | struct screen {
66 | int vis; /* cursor visibility */
67 | int maxy; /* highest row in which the cursor has ever been */
68 | int rows; /* number of rows in the window */
69 | int delta; /* number of lines written by a vtwrite */
70 | struct { int top; int bot; } scroll;
71 | struct {
72 | int y, x, xenl;
73 | cchar_t bkg;
74 | attr_t attr;
75 | short p; /* The color pair */
76 | short color[2]; /* [0] == foreground, [1] == background */
77 | wchar_t *gc, *gs;
78 | } c, sc; /* cursor/save cursor */
79 | bool insert;
80 | wchar_t repc; /* character to be repeated */
81 | int decawm; /* wrap-around mode */
82 | WINDOW *w;
83 | };
84 | struct pty {
85 | int fd, tabstop, count;
86 | struct winsize ws;
87 | int tos; /* top of screen */
88 | pid_t pid;
89 | bool *tabs, pnm, decom, lnm;
90 | /* DECOM: When set, cursor addressing is relative to the upper left
91 | * corner of the scrolling region instead of top of screen. */
92 | struct screen scr[2], *s; /* Primary/alternate screen */
93 | wchar_t *g[4];
94 | char status[32];
95 | struct vtp vp;
96 | char secondary[PATH_MAX];
97 | struct pty *next;
98 | };
99 |
100 | typedef void(action)(const char *arg);
101 | typedef void(action0)(void);
102 | struct handler {
103 | union {
104 | action *a;
105 | action0 *v;
106 | } act;
107 | const char *arg;
108 | };
109 |
110 | extern struct state S;
111 | struct mode {
112 | struct handler keys[128];
113 | };
114 | extern struct handler k1[128];
115 | extern struct handler k2[128];
116 | extern struct handler ctl[128];
117 | extern struct handler code_keys[KEY_MAX - KEY_MIN + 1];
118 |
119 | struct state {
120 | unsigned char ctlkey;
121 | unsigned char rawkey;
122 | int width; /* Columns in newly created ptys */
123 | int history; /* Rows in newly created windows */
124 | int count; /* User entered count in command mode */
125 | const char *term; /* Name of the terminal type */
126 | struct handler *binding; /* Current key binding */
127 | struct canvas *root; /* Root of all canvasses in use */
128 | struct canvas *f; /* Currently focused canvas */;
129 | struct pty *p; /* List of all pty in use */
130 | struct pty *tail; /* Last in the list of p */
131 | struct canvas *unused; /* Unused canvasses */
132 | fd_set fds;
133 | int maxfd;
134 | WINDOW *werr;
135 | WINDOW *wbkg;
136 | int reshape;
137 | char errmsg[256];
138 | };
139 |
140 | struct point { int y, x; };
141 | struct canvas {
142 | struct point origin; /* position of upper left corner */
143 | struct point extent; /* relative position of lower right corner */
144 | /* extent.y is the actual number of rows visible in the window */
145 | int typ; /* 0: c[0] is full width, 1: c[1] is full height */
146 | struct point offset; /* Number of lines window is scrolled */
147 | struct pty *p;
148 | struct canvas *parent;
149 | /*
150 | A canvas contains both c[0] and c[1], and shows only the upper
151 | left corner. eg: if extent = {3, 13}, typ = 0, split = { 0.5, 0.333 }
152 |
153 | |<-wdiv
154 | p->s.w | c[1]
155 | |
156 | ----wtit-----x-------c[1]->wtit-----------
157 |
158 | c[0]
159 |
160 | -------------c[0]->wtit-------------------
161 | c[0] is the window below this, c[1] is the window to the right
162 | (in a typ==1 window, c1 is full height and c0 is partial width)
163 | (Note that w.x + c1->w.x == extent.x - 1, subtracting 1 for wdiv)
164 | */
165 | struct canvas *c[2];
166 | struct { double y, x; } split;
167 | int manualscroll;
168 | WINDOW *wtit; /* Window for title */
169 | WINDOW *wdiv; /* Window for divider */
170 | };
171 |
172 | extern wchar_t CSET_US[0x7f]; /* "USASCII" */
173 | extern wchar_t CSET_UK[0x7f]; /* "United Kingdom" */
174 | extern wchar_t CSET_GRAPH[0x7f]; /* Graphics Set One */
175 | extern int tabstop;
176 | extern int id;
177 |
178 | extern struct canvas * newcanvas(struct pty *, struct canvas *);
179 | extern void draw(struct canvas *);
180 | extern void setupevents(struct pty *);
181 | extern void rewrite(int fd, const char *b, size_t n);
182 | extern void draw(struct canvas *n);
183 | extern void freecanvas(struct canvas *n);
184 | extern void scrollbottom(struct canvas *n);
185 | extern int check(int, int, const char *, ...);
186 | extern void set_tabs(struct pty *p, int tabstop);
187 | extern int resize_pad(WINDOW **, int, int);
188 | extern void reshape_window(struct pty *);
189 | extern void reshape(struct canvas *n, int y, int x, int h, int w);
190 | void set_scroll(struct screen *s, int top, int bottom);
191 | extern void change_count(struct canvas * n, int, int);
192 | extern struct pty * new_pty(int, int, bool);
193 |
194 | extern action0 attach;
195 | extern action balance;
196 | extern action create;
197 | extern action digit;
198 | extern action0 focus;
199 | extern action0 help;
200 | extern action mov;
201 | extern action0 new_tabstop;
202 | extern action0 new_shell;
203 | extern action0 next;
204 | extern action0 prune;
205 | extern action reorient;
206 | extern action0 reshape_root;
207 | extern action resize;
208 | extern action scrollh;
209 | extern action scrolln;
210 | extern action send;
211 | extern action0 send_cr;
212 | extern action sendarrow;
213 | extern action0 set_history;
214 | extern action0 set_layout;
215 | extern action set_width;
216 | extern action0 swap;
217 | extern action transition;
218 | extern action0 transpose;
219 | extern action0 vbeep;
220 |
--------------------------------------------------------------------------------
/smtx.ti:
--------------------------------------------------------------------------------
1 | smtx|Simple Modal Terminal Multiplexer,
2 | # boolean capabilities
3 | am,
4 | bce,
5 | hs,
6 | mir,
7 | msgr,
8 | xenl,
9 | # numeric capabilities
10 | colors#8,
11 | cols#80,
12 | it#8,
13 | lines#25,
14 | pairs#64,
15 | # string capabilities
16 | acsc=++\,\,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
17 | bel=\007,
18 | blink=\E[5m,
19 | bold=\E[1m,
20 | cbt=\E[Z,
21 | civis=\E[25l,
22 | clear=\E[H\E[J,
23 | cnorm=\E[25h,
24 | cr=\r,
25 | csr=\E[%i%p1%d;%p2%dr,
26 | cub1=\010,
27 | cub=\E[%p1%dD,
28 | cud1=\n,
29 | cud=\E[%p1%dB,
30 | cuf1=\E[C,
31 | cuf=\E[%p1%dC,
32 | cup=\E[%i%p1%d;%p2%dH,
33 | cuu1=\EM,
34 | cuu=\E[%p1%dA,
35 | cvvis=\E[34l,
36 | dch1=\E[P,
37 | dch=\E[%p1%dP,
38 | dim=\E[2m,
39 | dl1=\E[M,
40 | dl=\E[%p1%dM,
41 | ech=\E[%p1%dX,
42 | ed=\E[J,
43 | el1=\E[1K,
44 | el=\E[K,
45 | enacs=\E(B\E)0,
46 | fsl=\007,
47 | home=\E[H,
48 | hpa=\E[%i%p1%dG,
49 | ht=\011,
50 | hts=\EH,
51 | ich=\E[%p1%d@,
52 | il1=\E[L,
53 | il=\E[%p1%dL,
54 | ind=\n,
55 | indn=\E[%p1%dS,
56 | is2=\E)0,
57 | kbs=\177,
58 | kcbt=\E[Z,
59 | kcub1=\EOD,
60 | kcud1=\EOB,
61 | kcuf1=\EOC,
62 | kcuu1=\EOA,
63 | kdch1=\E[3~,
64 | kend=\E[4~,
65 | kf10=\E[21~,
66 | kf11=\E[23~,
67 | kf12=\E[24~,
68 | kf1=\EOP,
69 | kf2=\EOQ,
70 | kf3=\EOR,
71 | kf4=\EOS,
72 | kf5=\E[15~,
73 | kf6=\E[17~,
74 | kf7=\E[18~,
75 | kf8=\E[19~,
76 | kf9=\E[20~,
77 | khome=\E[1~,
78 | kich1=\E[2~,
79 | knp=\E[6~,
80 | kpp=\E[5~,
81 | nel=\EE,
82 | op=\E[39;49m,
83 | rc=\E8,
84 | rep=%p1%c\E[%p2%{1}%-%db,
85 | rev=\E[7m,
86 | ri=\EM,
87 | rin=\E[%p1%dT,
88 | ritm=\E[23m,
89 | rmacs=\017,
90 | rmcup=\E[1049l,
91 | rmir=\E[4l,
92 | rmkx=\E[1l\E>,
93 | rmso=\E[27m,
94 | rmul=\E[24m,
95 | rs2=\Ec,
96 | sc=\E7,
97 | setab=\E[%p1%'('%+%dm,
98 | setaf=\E[%p1%{30}%+%dm,
99 | sgr0=\E[m\017,
100 | sgr=\E[0%?%p6%t;1%;%?%p1%t;3%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;5%;%?%p5%t;2%;m%?%p9%t\016%e\017%;,
101 | sitm=\E[3m,
102 | smacs=\016,
103 | smcup=\E[1049h,
104 | smir=\E[4h,
105 | smkx=\E[1h\E=,
106 | smso=\E[7m,
107 | smul=\E[4m,
108 | tbc=\E[3g,
109 | tsl=\E]2,
110 | u8=\006,
111 | u9=\005,
112 | vpa=\E[%i%p1%dd,
113 |
114 |
115 | smtx-256color|Simple Modal Terminal Multiplexer with 256 colors,
116 | colors#0x100,
117 | pairs#0x10000,
118 | setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m,
119 | setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m,
120 | use=smtx,
121 |
--------------------------------------------------------------------------------
/smtx.txt:
--------------------------------------------------------------------------------
1 |
2 | = smtx (1)
3 | William Pursell
4 | :doctype: manpage
5 | :man manual: smtx manual
6 | :man source: smtx
7 | :man version: {version}
8 |
9 | == NAME
10 |
11 | smtx - A simple modal terminal multiplexor
12 |
13 | == SYNOPSIS
14 |
15 | *smtx* [-c ctrl-key] [-h] [-s history-size] [-t terminal-type] [-v] [-w width]
16 |
17 | == OPTIONS
18 |
19 | *-c*=ctrl-key::
20 | Use alternate key to enter control mode.
21 |
22 | *-h*::
23 | Print the usage statement and exit.
24 |
25 | *-s*=history-size::
26 | Set the number of lines in the history buffer to be used in ptys.
27 |
28 | *-t*=term::
29 | Assign TERM environment to this value is new shells (default is "smtx").
30 |
31 | *-v*::
32 | Print the version and exit.
33 |
34 | *-w*=width::
35 | Specify the minimun width in ptys (default is 80).
36 |
37 | == DESCRIPTION
38 |
39 | smtx is a terminal multiplexor which allows ptys to be larger than the physical
40 | screen. Multiple windows can be opened into the same pty at different locations,
41 | and window layouts can be generated dynamically. Each window exists as part of
42 | a "canvas", which is a rectangular portion of the physical screen. The root canvas
43 | consists of the entire physical screen and contains a window in the upper left
44 | corner and 2 child canvases which fill the remainder of the canvas, either of which
45 | may be empty. The left child occupies the space below the window, while the right
46 | child occupies the space to the right of the window. Which child extends to the
47 | lower right portion of the canvas depends on the type of the canvas, and can be
48 | modified dynamically. The window provides a view into a pty, and the pty associated
49 | with the window can be changed dynamically.
50 |
51 | When the shell running inside a pty exits, the pty is not immediately reaped
52 | and any error messages will remain available until that canvas is pruned.
53 | To immediately close all ptys and exit, use '0x' from control mode.
54 |
55 | == EXAMPLES
56 |
57 | change the current window layout:
58 |
59 | $ printf '\033]60;.33:1 .66:.5 .66:1 1:1\007'
60 |
61 | change the title of the current window:
62 |
63 | $ printf '\033]2;new title\007'
64 |
65 | == BINDINGS
66 |
67 | The following describes the default keybindings used in control mode.
68 |
69 | * a Attach the specified pty to the focused window
70 | * C Create N new canvasses to the right of the current window
71 | * c Create N new canvasses below the current window
72 | * g Goto a visible window attached to the pty with id N
73 | * i Enter one line of text to the underlying pty
74 | * j Navigate to the primary child of the focused window
75 | * h Navigate to the left child of the focused window
76 | * k Navigate to the parent of the focused window
77 | * l Navigate to the right child of the focused window
78 | * N Spawn a new shell (may create a pty, or reuse if one is available)
79 | * n Attach the next pty to the focused window
80 | * T Recursively transpose the current canvas
81 | * W Modify the width of the currently focused pty to be N
82 | * v Use pre-defined window layout N
83 | * x Recursively prune the specified canvas
84 |
85 | == COPYING
86 |
87 | Copyright \(C) 2020 {author}.
88 | Use of this software is granted under the terms of the GPLv3
89 |
--------------------------------------------------------------------------------
/test-coverage:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Copyright (c) 2020 - 2023 William Pursell
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions are met:
8 | # * Redistributions of source code must retain the above copyright
9 | # notice, this list of conditions and the following disclaimer.
10 | # * Redistributions in binary form must reproduce the above copyright
11 | # notice, this list of conditions and the following disclaimer in the
12 | # documentation and/or other materials provided with the distribution.
13 | # * Neither the name of the copyright holder nor the
14 | # names of contributors may be used to endorse or promote products
15 | # derived from this software without specific prior written permission.
16 | #
17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
18 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS,
21 | # COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24 | # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | # Check coverage. If configured without --coverage in CFLAGS,
30 | # this will likely just get skipped. (eg, configure with
31 | # CFLAGS='--coverage -g -O0' to enable this test)
32 |
33 | if ! test -f test-main.gcda; then
34 | exit 77
35 | fi
36 |
37 | cat << EOF |
38 | handler 98
39 | smtx 100
40 | smtx-main 97
41 | vtparser 100
42 | action 90
43 | EOF
44 | {
45 | RV=0
46 | while read name cover; do
47 | if test -f .libs/$name.gcno; then
48 | arg='-o .libs'
49 | else
50 | unset arg
51 | fi
52 | output=$(gcov $name $arg)
53 | echo "$output"
54 | # Fail if coverage falls below the threshold
55 | # We expect output to look like:
56 | # File '../../../smtx/smtx.c'
57 | # Lines executed:100.00% of 2
58 | # Creating 'smtx.c.gcov'
59 | if ! echo "$output" | awk 'NR==2 && /^Lines executed/ && $2 >= f{ok = 1}
60 | END {exit !ok}' f="${cover}" FS='[:%]'; then
61 | RV=1
62 | echo "FAIL: Coverage for $name below $cover" >&2
63 | fi
64 | if < "$name".c.gcov sed \
65 | -e '\@{ /\* uncovered block \*/@,'"$( :
66 | )"'\@} /\* end uncovered \*/@d' \
67 | -e '/#####/!d' \
68 | -e '\@/\* uncovered \*/@d' | grep .; then
69 | echo The above lines of $name.c were not executed during testing
70 | fi >&2
71 | done
72 | exit $RV
73 | }
74 | # Careful. There is some shell "magic" here. RV is being assigned
75 | # and returned from a subshell. We rely on this subshell being the last
76 | # command executed, so that its value is returned by the main shell.
77 | # Also note that this relies on a specific output from gcov that happens
78 | # to work in the version I'm running. Probably need to skip this test
79 | # until it is robustified.
80 |
--------------------------------------------------------------------------------
/test-describe.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 - 2023 William Pursell
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | /* This file contains descripive functions that should only ever be
19 | * used by the test harness. Note that this is not really true: it might
20 | * be convenient to have the ability to display state in the terminal,
21 | * so we should clean this code up and expect it to remain in production.
22 | */
23 | #include "smtx.h"
24 |
25 | static size_t
26 | describe_layout(char *d, ptrdiff_t siz, const struct canvas *c, unsigned flags,
27 | int tab)
28 | {
29 | /* Describe a layout. */
30 | const char * const e = d + siz;
31 | int recurse = flags & 0x1;
32 | int show_id = flags & 0x4;
33 | int show_pos = flags & 0x10;
34 | int show_2nd = flags & 0x40;
35 | int human = flags & 0x80;
36 | int show_pty = flags & 0x100;
37 |
38 | char *isfocus = recurse && c == S.f ? "*" : "";
39 | if (human) {
40 | d += snprintf(d, e - d, "%c", c->typ ? '|' : '-');
41 | }
42 | d += snprintf(d, e - d, "%s%dx%d", isfocus, c->extent.y, c->extent.x);
43 | if (show_pos){
44 | d += snprintf(d, e - d, "@%d,%d", c->origin.y, c->origin.x);
45 | }
46 | if (show_id && c->p) {
47 | d += snprintf(d, e - d, "(id=%d)", c->p->fd - 2);
48 | }
49 | if (c->p->s && ! c->p->s->vis) {
50 | d += snprintf(d, e - d, "!"); /* Cursor hidden */
51 | }
52 | if (! c->p->pnm) {
53 | d += snprintf(d, e - d, "#"); /* Numeric keypad */
54 | }
55 | if (show_2nd && c->p && c->p->fd != -1) {
56 | d += snprintf(d, e - d, "(2nd=%s)", c->p->secondary );
57 | }
58 | if (show_pty) {
59 | d += snprintf(d, e - d, "(pri %d,%d)(2nd %d,%d)",
60 | c->p->scr[0].rows,
61 | c->p->scr[0].maxy,
62 | c->p->scr[1].rows,
63 | c->p->scr[1].maxy
64 | );
65 | }
66 | for (int i = 0; recurse && i < 2; i ++) {
67 | if (e - d > 3 + tab && c->c[i]) {
68 | *d++ = human ? '\r' : ';';
69 | *d++ = human ? '\n' : ' ';
70 | for (int i = 0; human && i < tab; i++) {
71 | *d++ = '\t';
72 | }
73 | d += describe_layout(d, e - d, c->c[i], flags, tab + 1);
74 | }
75 | }
76 | return siz - ( e - d );
77 | }
78 |
79 | static void
80 | check_attr(unsigned f, unsigned *flags, char **d, char *e, char *msg, int set,
81 | int star)
82 | {
83 | char *dest = *d;
84 | size_t len = strlen(msg);
85 | if (((set && !(*flags & f)) || (!set && (*flags & f)))
86 | && len > 0 && dest + len + 3 < e) {
87 | *dest++ = '<';
88 | if (!set) {
89 | *dest++ = '/';
90 | }
91 | memcpy(dest, msg, len);
92 | dest += len;
93 | if (star) {
94 | *dest++ = '*';
95 | }
96 | *dest++ = '>';
97 | }
98 | if (set) {
99 | *flags |= f;
100 | } else {
101 | *flags &= ~f;
102 | }
103 | *d = dest;
104 | }
105 |
106 | static char *
107 | color_name(short k)
108 | {
109 | char *n;
110 | switch (k) {
111 | case COLOR_BLACK: n = "black"; break;
112 | case COLOR_RED: n = "red"; break;
113 | case COLOR_GREEN: n = "green"; break;
114 | case COLOR_YELLOW: n = "yellow"; break;
115 | case COLOR_BLUE: n = "blue"; break;
116 | case COLOR_MAGENTA: n = "magenta"; break;
117 | case COLOR_CYAN: n = "cyan"; break;
118 | case COLOR_WHITE: n = "white"; break;
119 | default: n = "";
120 | }
121 | return n;
122 | }
123 |
124 | static size_t
125 | describe_row(char *desc, size_t siz, int row)
126 | {
127 | int y, x;
128 | size_t i = 0;
129 | unsigned attrs = 0;
130 | unsigned fgflag = 0;
131 | unsigned bgflag = 0;
132 | int last_color_pair = 0;
133 | int offset = 0;
134 | const struct canvas *c = S.root;
135 | unsigned width = c->p->ws.ws_col;
136 | char *end = desc + siz;
137 | WINDOW *w = c->p->s->w;
138 | struct {
139 | unsigned attr;
140 | unsigned flag;
141 | char *name;
142 | } *atrp, atrs[] = {
143 | { A_BOLD, 0x1, "bold" },
144 | { A_DIM, 0x2, "dim" },
145 | #if HAVE_A_ITALIC
146 | { A_ITALIC, 0x4, "italic" },
147 | #endif
148 | { A_UNDERLINE, 0x8, "ul" },
149 | { A_BLINK, 0x10, "blink" },
150 | { A_REVERSE, 0x20, "rev" },
151 | { A_INVIS, 0x40, "inv" },
152 | { 0, 0, NULL }
153 | };
154 |
155 | if (row < c->extent.y) {
156 | row += c->offset.y;
157 | offset = c->offset.x;
158 | } else if (row == c->extent.y) {
159 | w = c->wtit;
160 | row = 0;
161 | }
162 | getyx(w, y, x);
163 | for (i = 0; i < (size_t)c->extent.x && i < width && desc < end; i++) {
164 | int p;
165 | chtype k = mvwinch(w, row, i + offset);
166 | for (atrp = atrs; atrp->flag; atrp += 1) {
167 | check_attr(atrp->flag, &attrs, &desc, end, atrp->name,
168 | ( (k & A_ATTRIBUTES) & atrp->attr ) ? 1 : 0, 0
169 | );
170 | }
171 | p = PAIR_NUMBER(k);
172 | if (p != last_color_pair) {
173 | int put = p ? p : last_color_pair;
174 | short fg, bg;
175 | if (pair_content((short)put, &fg, &bg) == OK
176 | && bg < (short)sizeof(1u) * CHAR_BIT
177 | && fg < (short)sizeof(1u) * CHAR_BIT) {
178 | check_attr(1u << fg, &fgflag, &desc, end,
179 | color_name(fg), p, 0
180 | );
181 | check_attr(1u << bg, &bgflag, &desc, end,
182 | color_name(bg), p, 1
183 | );
184 | }
185 | last_color_pair = p;
186 | }
187 | *desc++ = k & A_CHARTEXT;
188 | }
189 | wmove(w, y, x);
190 | return siz - ( end - desc );
191 | }
192 |
193 | static size_t
194 | describe_state(char *desc, size_t siz)
195 | {
196 | size_t len = 0;
197 |
198 | len += snprintf(desc, siz, "history=%d, ", S.history);
199 | if (len < siz - 1) {
200 | len += snprintf(desc + len, siz - len, "y=%d, x=%d, ", LINES,
201 | COLS);
202 | }
203 | if (len < siz - 1) {
204 | len += snprintf(desc + len, siz - len, "w=%d", S.width);
205 | }
206 | if (len > siz - 3) {
207 | len = siz - 3;
208 | }
209 | desc[len++] = '\r';
210 | desc[len++] = '\n';
211 | desc[len++] = '\0';
212 | return len;
213 | }
214 |
215 | static void
216 | show_layout(const char *arg)
217 | {
218 | char buf[1024] = "layout: ";
219 | int flag = strtol(*arg == ';' ? arg + 1 : arg, NULL, 16);
220 | struct canvas *c = flag & 0x20 ? S.f : S.root;
221 | if (flag & 0x80) {
222 | strcat(buf, "\r\n");
223 | }
224 | int w = strlen(buf);
225 | size_t s = describe_layout(buf + w, sizeof buf - w - 3, c, flag, 1);
226 | for (size_t i = w; i < w + s; i++) {
227 | assert( buf[i] != ':' );
228 | }
229 | buf[w + s++] = ':';
230 | buf[w + s++] = '\r';
231 | buf[w + s++] = '\n';
232 | rewrite(STDOUT_FILENO, buf, s + w);
233 | }
234 |
235 | static void
236 | show_row(const char *arg)
237 | {
238 | int row = strtol(arg, NULL, 10);
239 | char buf[1024];
240 | char val[1024];
241 | size_t s = describe_row(val, sizeof val, row);
242 | int k = snprintf(buf, sizeof buf, "row %d:(%zd)%s", row, s, val);
243 | rewrite(1, buf, k < (int)sizeof buf ? k : (int)sizeof buf);
244 | }
245 |
246 | static void
247 | show_procs(void)
248 | {
249 | char buf[1024] = "procs:\tid\tpid\tcount\ttitle\r\n";
250 | rewrite(1, buf, strlen(buf));
251 | for (struct pty *p = S.p; p; p = p->next) {
252 | int k = snprintf(buf, sizeof buf, "\t%d\t%d\t%d\t%s\r\n",
253 | p->fd - 2, p->pid, p->count, p->status);
254 | rewrite(1, buf, k);
255 | }
256 | }
257 |
258 | static void
259 | show_state(void)
260 | {
261 | char buf[1024];
262 | int k = sprintf(buf, "state: ");
263 | size_t s = describe_state(buf + k, sizeof buf - k);
264 | rewrite(1, buf, s + k);
265 | }
266 |
267 | void
268 | show_status(const char *arg)
269 | {
270 | if (S.count == -1 && *arg == 'x'){
271 | for (int i = 0; i < LINES * COLS; i++) {
272 | putchar(' ');
273 | }
274 | putchar('\r');
275 | putchar('\n');
276 | fflush(stdout);
277 | }
278 | if (*arg == 'x') {
279 | arg += 1;
280 | }
281 | if (*arg == 'r') {
282 | show_row(arg + 1);
283 | } else {
284 | show_procs();
285 | show_state();
286 | show_layout(*arg ? arg : "0x1d5");
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/test-main.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 - 2023 William Pursell
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 | #include "config.h"
18 | #include "test-unit.h"
19 | #include
20 | #include
21 |
22 | #define SKIP_TEST 77
23 |
24 | static int read_timeout = 1; /* Set to 0 when interactively debugging */
25 | static int main_timeout = 10;
26 | static int check_test_status(int rv, int status, int pty, const char *name);
27 | static int get_secondary_fd(int fd);
28 | int ctlkey = CTRL('g');
29 |
30 | union param {
31 | struct { unsigned flag; } hup;
32 | struct { int row; unsigned flag; } usr1;
33 | };
34 |
35 | static void
36 | write_pty(int fd, unsigned flags, const char *wait, const char *fmt, va_list ap)
37 | {
38 | char cmd[1024];
39 | size_t n;
40 | char *b = cmd;
41 | if (flags & 0x1) {
42 | *b++ = ctlkey;
43 | }
44 | n = vsnprintf(b, sizeof cmd - (b - cmd), fmt, ap);
45 | if (n > sizeof cmd - 4) {
46 | err(EXIT_FAILURE, "Invalid string in write_pty");
47 | }
48 | assert( b[n] == '\0' );
49 | if (flags & 0x2) {
50 | b[n++] = '\r';
51 | b[n] = '\0';
52 | }
53 | const char *e = b + n;
54 | b = cmd;
55 | while (b < e) {
56 | ssize_t s = write(fd, b, e - b);
57 | if (s < 0 && errno != EINTR) {
58 | err(EXIT_FAILURE, "write to pty");
59 | }
60 | b += s < 0 ? 0 : s;
61 | }
62 | if (wait != NULL) {
63 | grep(fd, wait);
64 | }
65 | }
66 |
67 | void __attribute__((format(printf,3,4)))
68 | send_cmd(int fd, const char *wait, const char *fmt, ...)
69 | {
70 | va_list ap;
71 | va_start(ap, fmt);
72 | write_pty(fd, 0x3, wait, fmt, ap);
73 | va_end(ap);
74 | }
75 |
76 | /* Write text to the pty and wait until the string wait is seen */
77 | void __attribute__((format(printf,3,4)))
78 | send_txt(int fd, const char *wait, const char *fmt, ...)
79 | {
80 | va_list ap;
81 | va_start(ap, fmt);
82 | write_pty(fd, 0x2, wait, fmt, ap);
83 | va_end(ap);
84 | }
85 |
86 | void __attribute__((format(printf,3,4)))
87 | send_raw(int fd, const char *wait, const char *fmt, ...)
88 | {
89 | va_list ap;
90 | va_start(ap, fmt);
91 | write_pty(fd, 0, wait, fmt, ap);
92 | va_end(ap);
93 | }
94 | ssize_t timed_read(int, void *, size_t, const char *);
95 |
96 | int
97 | get_layout(int fd, int flag, char *buf, size_t siz)
98 | {
99 | ssize_t s = 0;
100 | const char *end = buf + siz;
101 | int fd2 = get_secondary_fd(fd);
102 | int len = snprintf(buf, siz, "\033]62;%x\007", flag);
103 | write(fd2, buf, len);
104 | close(fd2);
105 |
106 | grep(fd, "layout: ");
107 | while (buf < end && s != -1) {
108 | s = timed_read(fd, buf, 1, "layout");
109 | if (buf[0] == ':') {
110 | buf[0] = '\0';
111 | break;
112 | }
113 | buf += s;
114 | }
115 | if (s == -1) {
116 | fprintf(stderr, "reading from child: %s\n", strerror(errno));
117 | return -1;
118 | }
119 | return 0;
120 | }
121 |
122 | int
123 | get_state(int fd, char *state, size_t siz)
124 | {
125 | char buf[64];
126 | ssize_t s;
127 | int fd2 = get_secondary_fd(fd);
128 | int len = snprintf(buf, siz, "\033]62;\007");
129 | write(fd2, buf, len);
130 | close(fd2);
131 | grep(fd, "state: ");
132 | do s = timed_read(fd, state, siz, "state"); while (s == 0 );
133 | if (s == -1) {
134 | fprintf(stderr, "reading from child: %s\n", strerror(errno));
135 | return -1;
136 | }
137 | return 0;
138 | }
139 |
140 | int __attribute__((format(printf,3,4)))
141 | check_layout(int fd, int flag, const char *fmt, ...)
142 | {
143 | char buf[1024];
144 | va_list ap;
145 | char expect[1024];
146 | int rv = -1;
147 |
148 | va_start(ap, fmt);
149 | (void)vsnprintf(expect, sizeof expect, fmt, ap);
150 | va_end(ap);
151 |
152 | if (get_layout(fd, flag, buf, sizeof buf) == 0) {
153 | if (strcmp( buf, expect )) {
154 | fputs("unexpected layout:\nreceived: ", stderr);
155 | for (const char *b = buf; *b; b++) {
156 | fputc(isprint(*b) ? *b : '?', stderr);
157 | }
158 | fprintf(stderr, "\nexpected: %s\n", expect);
159 | } else {
160 | rv = 0;
161 | }
162 | }
163 | return rv;
164 | }
165 |
166 | static void noop(int s){ (void)s; }
167 | ssize_t
168 | timed_read(int fd, void *buf, size_t count, const char *n)
169 | {
170 | fd_set set;
171 | struct timeval t = { .tv_sec = read_timeout, .tv_usec = 0 };
172 | struct timeval *timeout = read_timeout ? &t : NULL;
173 | struct sigaction sa;
174 | memset(&sa, 0, sizeof sa);
175 | sa.sa_flags = 0;
176 | sigemptyset(&sa.sa_mask);
177 | sa.sa_handler = noop;
178 | sigaction(SIGINT, &sa, NULL);
179 |
180 | FD_ZERO(&set);
181 | FD_SET(fd, &set);
182 |
183 | switch (select(fd + 1, &set, NULL, NULL, timeout)) {
184 | case -1:
185 | err(EXIT_FAILURE, "select %d waiting for %s", fd, n);
186 | case 0:
187 | errx(EXIT_FAILURE, "timedout waiting for %s", n);
188 | }
189 | sa.sa_handler = SIG_DFL;
190 | sigaction(SIGINT, &sa, NULL);
191 | return read(fd, buf, count);
192 | }
193 |
194 | /*
195 | * Read from fd until needle is seen or end of file. This is not
196 | * intended to check anything, but is merely a synchronization device
197 | * to delay the test until data is seen to verify that the underlying
198 | * shell has processed input.
199 | */
200 | void
201 | grep(int fd, const char *needle)
202 | {
203 | assert( needle != NULL );
204 |
205 | ssize_t rc;
206 | char c;
207 | const char *n = needle;
208 | while (*n != '\0') {
209 | do rc = timed_read(fd, &c, 1, needle); while (rc == 0 );
210 | if (rc == -1) {
211 | err(EXIT_FAILURE, "read from pty");
212 | }
213 | if (c != *n++) {
214 | n = c == *needle ? needle + 1 : needle;
215 | }
216 | }
217 | }
218 |
219 | static int
220 | get_secondary_fd(int fd)
221 | {
222 | int fd2;
223 | char path[PATH_MAX];
224 | char *p = path;
225 | const char *end = path + sizeof path;
226 | send_cmd(fd, NULL, "1Q");
227 | grep(fd, "layout:");
228 | grep(fd, "2nd=");
229 | do {
230 | if (timed_read(fd, p, 1, "2nd path") != 1) {
231 | err(1, "Invalid read getting 2ndary");
232 | }
233 | } while (*p != ')' && ++p < end -1 );
234 | *p = '\0';
235 | if ((fd2 = open(path, O_WRONLY)) == -1) {
236 | err(1, "%s", path);
237 | }
238 | return fd2;
239 | }
240 |
241 | int
242 | get_row(int fd, int row, char *buf, size_t siz)
243 | {
244 | size_t count = 0;
245 | char *r = buf;
246 | int fd2;
247 |
248 | fd2 = get_secondary_fd(fd);
249 | int len = snprintf(buf, siz, "\033]62;r%d\007", row - 1);
250 | write(fd2, buf, len);
251 | close(fd2);
252 |
253 | sprintf(buf, "row %d:(", row - 1);
254 | grep(fd, buf);
255 | /* We expect to see "row N:(len)" on the fd, where len is the width of
256 | * the row. The above grep discards "row N:(". Now read the length. */
257 |
258 | while (*r != ')') {
259 | if (timed_read(fd, r, 1, "count in row validation") != 1) {
260 | err(1, "Invalid read in row validation");
261 | } else if (*r == ')') {
262 | ;
263 | } else if (! isdigit(*r)) {
264 | err(1, "Expected count, got '%c'\n", *r);
265 | } else {
266 | count = 10 * count + *r - '0';
267 | }
268 | }
269 | if (count > siz - 1) {
270 | err(1, "Row is too long");
271 | }
272 | ssize_t s = timed_read(fd, buf, count, "Reading row");
273 | if (s == -1) {
274 | fprintf(stderr, "reading from child: %s\n", strerror(errno));
275 | return -1;
276 | }
277 | buf[s] = 0;
278 | return 0;
279 | }
280 |
281 | /* TODO: allow alternatives. That is, sometimes there is a race, and
282 | * a row has one of two values (depending on if the shell has printed
283 | * a prompt). It might be nice to check that.
284 | */
285 | int
286 | validate_row(int fd, int row, const char *fmt, ... )
287 | {
288 | char buf[1024];
289 | if (get_row(fd, row, buf, sizeof buf) == -1) {
290 | return -1;
291 | }
292 | char expect[1024];
293 | va_list ap;
294 | va_start(ap, fmt);
295 | (void)vsnprintf(expect, sizeof expect, fmt, ap);
296 | va_end(ap);
297 |
298 | int status = 0;
299 | if (strcmp( buf, expect )) {
300 | fprintf(stderr, "unexpected content in row %d\n", row);
301 | fputs("received: '", stderr);
302 | for (const char *b = buf; *b; b++) {
303 | fputc(isprint(*b) ? *b : '?', stderr);
304 | }
305 | fprintf(stderr, "'\nexpected: '%s'\n", expect);
306 | status = 1;
307 | }
308 | return status;
309 | }
310 |
311 |
312 | static void *
313 | xrealloc(void *buf, size_t num, size_t siz)
314 | {
315 | buf = realloc(buf, num * siz);
316 | if (buf == NULL) {
317 | perror("realloc");
318 | exit(EXIT_FAILURE);
319 | }
320 | return buf;
321 | }
322 |
323 | struct st {
324 | char *name;
325 | test *f;
326 | int envc;
327 | char ** env;
328 | int argc;
329 | char ** argv;
330 | struct st *next;
331 | };
332 | static int execute_test(struct st *, const char *);
333 | static void spawn_test(struct st *v, const char *argv0);
334 | static void
335 | new_test(char *name, test *f, struct st **h, ...)
336 | {
337 | char *arg;
338 | struct st *tmp = *h;
339 | struct st *a = *h = xrealloc(NULL, 1, sizeof *a);
340 | struct {
341 | int *c;
342 | char ***v;
343 | } cv;
344 | a->next = tmp;
345 | a->name = name;
346 | a->f = f;
347 | a->envc = 0;
348 | a->argc = 1;
349 | a->env = xrealloc(NULL, 1, sizeof *a->env);
350 | a->argv = xrealloc(NULL, 2, sizeof *a->argv);
351 | a->argv[0] = "smtx";
352 | cv.c = &a->envc;
353 | cv.v = &a->env;
354 | va_list ap;
355 | va_start(ap, h);
356 | while ((arg = va_arg(ap, char *)) != NULL) {
357 | if (strcmp(arg, "args") == 0) {
358 | cv.c = &a->argc;
359 | cv.v = &a->argv;
360 | continue;
361 | }
362 | *cv.v = xrealloc(*cv.v, *cv.c + 2, sizeof **cv.v);
363 | (*cv.v)[(*cv.c)++] = arg;
364 | }
365 | va_end(ap);
366 | a->env[a->envc] = NULL;
367 | a->argv[a->argc] = NULL;
368 | }
369 |
370 | static int
371 | match_name(const char *a, const char *b)
372 | {
373 | const char *under = strchr(a, '_');
374 | return strcmp(a, b) && (!under || strcmp(under + 1, b));
375 | }
376 |
377 | static int
378 | exit_status(int status)
379 | {
380 | return WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE;
381 | }
382 |
383 | static struct st * initialize_tests(char const **);
384 |
385 | int
386 | main(int argc, char *const argv[])
387 | {
388 | int fail_count = 0;
389 | int skip_count = 0;
390 | int total_count = 0;
391 | char const *argv0 = argv[0];
392 | struct st *tab, *v;
393 |
394 | tab = initialize_tests(&argv0);
395 |
396 | for (v = tab; v && ( argc < 2 || *++argv ); v = v ? v->next : NULL) {
397 | const char *name = *argv;
398 | if (argc > 1) {
399 | for (v = tab; v && match_name(v->name, name); )
400 | v = v->next;
401 | }
402 | if (v && v->f) {
403 | total_count += 1;
404 | if (strcmp(v->name, argv0)) {
405 | spawn_test(v, argv0);
406 | } else {
407 | return execute_test(v, argv0);
408 | }
409 | } else {
410 | fprintf(stderr, "unknown function: %s\n", name);
411 | fail_count += 1;
412 | }
413 | }
414 | for (int status = 0, i = 0; i < total_count; i++) {
415 | waitpid(-1, &status, 0);
416 | switch (exit_status(status)) {
417 | case EXIT_FAILURE:
418 | fail_count += 1;
419 | break;
420 | case SKIP_TEST:
421 | skip_count += 1;
422 | }
423 | }
424 | if (fail_count > 0 || skip_count > 0) {
425 | fprintf(
426 | stderr,
427 | "%d test%s total: %d skipped, %d failed\n",
428 | total_count,
429 | total_count > 1 ? "s" : "",
430 | skip_count,
431 | fail_count
432 | );
433 | }
434 | return fail_count ? EXIT_FAILURE : EXIT_SUCCESS;
435 | }
436 |
437 | /* Initialize a child to run a test. We re-exec to force argv[0] to
438 | * the name of the test so any error messages generated using err()
439 | * will have the test name, and to make it easier to pick them out
440 | * in the output of ps.
441 | */
442 | static void
443 | spawn_test(struct st *v, const char *argv0)
444 | {
445 | pid_t pid[3];
446 | int status;
447 | int fd[2];
448 | char *const args[] = { v->name, v->name, NULL };
449 | switch (pid[0] = fork()) {
450 | case -1:
451 | err(EXIT_FAILURE, "fork");
452 | case 0:
453 | if (pipe(fd)){
454 | err(EXIT_FAILURE, "pipe");
455 | }
456 | switch (pid[1] = fork()) {
457 | case -1:
458 | err(EXIT_FAILURE, "fork");
459 | case 0:
460 | dup2(fd[1], STDERR_FILENO);
461 | close(fd[0]);
462 | close(fd[1]);
463 | execv(argv0, args);
464 | err(EXIT_FAILURE, "%s", argv0);
465 | }
466 | switch (pid[2] = fork()) {
467 | case -1:
468 | err(EXIT_FAILURE, "fork");
469 | case 0:
470 | {
471 | int c, nl = 0;
472 | FILE *fp = fdopen(fd[0], "r");
473 | close(fd[1]);
474 | alarm(main_timeout);
475 | while (( c = fgetc(fp)) != EOF) {
476 | if (nl) {
477 | fputs(v->name, stderr);
478 | fputs(": ", stderr);
479 | nl = 0;
480 | }
481 | fputc(c, stderr);
482 | nl = c == '\n';
483 |
484 | }
485 | _exit(0);
486 | }
487 | }
488 | close(fd[0]);
489 | close(fd[1]);
490 |
491 | pid_t died = wait(&status);
492 | if (died == pid[1]) {
493 | if (kill(pid[2], SIGKILL) ) {
494 | perror("kill");
495 | }
496 | wait(NULL);
497 | } else {
498 | if (kill(pid[1], SIGKILL) ) {
499 | perror("kill");
500 | }
501 | wait(&status);
502 | if (exit_status(status)) {
503 | fprintf(stderr, "FAIL(timeout): %s\n", v->name);
504 | }
505 | }
506 | _exit(exit_status(status));
507 | }
508 | }
509 |
510 | static void
511 | set_window_size(struct winsize *ws)
512 | {
513 | char *t = getenv("LINES");
514 | char *c = getenv("COLUMNS");
515 | ws->ws_row = t ? strtol(t, NULL, 10) : 24;
516 | ws->ws_col = c ? strtol(c, NULL, 10) : 80;
517 | unsetenv("LINES");
518 | unsetenv("COLUMNS");
519 | }
520 |
521 | static int
522 | execute_test(struct st *v, const char *name)
523 | {
524 | int fd[2]; /* primary/secondary fd of pty */
525 | int status;
526 | struct winsize ws;
527 | int rv = 1;
528 |
529 | (void)name;
530 | assert( strcmp(name, v->name) == 0 );
531 | unsetenv("ENV"); /* Suppress all shell initializtion */
532 | setenv("SHELL", "/bin/sh", 1);
533 | setenv("PS1", PROMPT, 1);
534 | unsetenv("LINES");
535 | unsetenv("COLUMNS");
536 | for (char **a = v->env; a && *a; a += 2) {
537 | setenv(a[0], a[1], 1);
538 | }
539 | set_window_size(&ws);
540 | if (openpty(fd, fd + 1, NULL, NULL, &ws)) {
541 | err(EXIT_FAILURE, "openpty");
542 | }
543 | switch (fork()) {
544 | case -1:
545 | err(1, "fork");
546 | break;
547 | case 0:
548 | if (close(fd[0])) {
549 | err(EXIT_FAILURE, "close");
550 | }
551 | if (login_tty(fd[1])) {
552 | err(EXIT_FAILURE, "login_tty");
553 | }
554 | rv = 0;
555 | execv("./smtx", v->argv);
556 | err(EXIT_FAILURE, "execv");
557 | default:
558 | if (close(fd[1])) {
559 | err(EXIT_FAILURE, "close secondary");
560 | }
561 | if (getenv("XFAIL") == NULL) {
562 | grep(fd[0], PROMPT); /* Wait for shell to initialize */
563 | }
564 | rv = v->f(fd[0]);
565 | send_cmd(fd[0], NULL, "0x");
566 | wait(&status);
567 | }
568 | status = check_test_status(rv, status, fd[0], v->name);
569 |
570 | char *verbosity = getenv("V");
571 | if (verbosity && strtol(verbosity, NULL, 10) > 0) {
572 | printf("%20s: %s\n", v->name, status == 77 ? "SKIP" :
573 | status != 0 ? "FAIL" : "pass" );
574 | }
575 | return status;
576 | }
577 |
578 | static int
579 | check_test_status(int rv, int status, int pty, const char *name)
580 | {
581 | char *expect = getenv("XFAIL");
582 | int expect_stat = expect ? strtol(expect, NULL, 10) : 0;
583 | int expected = WIFEXITED(status) && WEXITSTATUS(status) == expect_stat;
584 |
585 | if (! expected) {
586 | char iobuf[BUFSIZ];
587 | ssize_t r = read(pty, iobuf, sizeof iobuf - 1);
588 | if (r > 0) {
589 | iobuf[r] = '\0';
590 | for (char *s = iobuf; *s; s++) {
591 | if (isprint(*s) || *s == '\n') {
592 | fputc(*s, stderr);
593 | }
594 | }
595 | }
596 | } else if (WIFSIGNALED(status)) {
597 | fprintf(stderr, "test %s caught signal %d\n",
598 | name, WTERMSIG(status));
599 | }
600 | switch (rv) {
601 | case SKIP_TEST:
602 | fputs("SKIPPED\n", stderr);
603 | return SKIP_TEST;
604 | case 0:
605 | return expected ? EXIT_SUCCESS : EXIT_FAILURE;
606 | default:
607 | return EXIT_FAILURE;
608 | }
609 | }
610 |
611 | char bigint[128];
612 |
613 | static struct st *
614 | initialize_tests(char const **argv0)
615 | {
616 | struct st *tab = NULL;
617 | char *base;
618 |
619 | if ((base = strrchr(*argv0, '/')) != NULL) {
620 | *argv0 = base + 1;
621 | chdir(*argv0);
622 | }
623 | snprintf(bigint, sizeof bigint, "%d", INT_MAX);
624 |
625 | #define F(x, ...) new_test(#x, x, &tab, ##__VA_ARGS__, NULL)
626 | F(test_ack);
627 | F(test_alt);
628 | F(test_attach);
629 | F(test_bighist, "XFAIL", "1", "STATUS", "1", "args", "-s", bigint);
630 | F(test_changehist, "args", "-s", "128");
631 | F(test_cols, "COLUMNS", "92", "args", "-w", "97");
632 | F(test_csr);
633 | F(test_cup);
634 | F(test_cursor);
635 | F(test_dashc, "args", "-c", "l");
636 | F(test_dasht, "args", "-t", "uninstalled_terminal_type");
637 | F(test_dch);
638 | F(test_decaln);
639 | F(test_ech);
640 | F(test_ed);
641 | F(test_el);
642 | F(test_equalize);
643 | F(test_hpr);
644 | F(test_ich);
645 | F(test_insert);
646 | F(test_layout);
647 | F(test_layout2);
648 | F(test_layout3);
649 | F(test_layout4);
650 | F(test_lnm);
651 | F(test_mode, "COLUMNS", "140");
652 | F(test_navigate);
653 | F(test_nel, "TERM", "smtx");
654 | F(test_pager ,"MORE", "");
655 | F(test_pnm);
656 | F(test_prune);
657 | F(test_repc);
658 | F(test_resend);
659 | F(test_resize);
660 | F(test_resizepty, "args", "-s", "10");
661 | F(test_ri);
662 | F(test_row);
663 | F(test_scrollback);
664 | F(test_scrollh, "COLUMNS", "26", "args", "-w", "78");
665 | F(test_scs);
666 | F(test_sgr);
667 | F(test_su);
668 | F(test_swap);
669 | F(test_tabstop);
670 | F(test_title);
671 | F(test_tput);
672 | F(test_transpose);
673 | F(test_utf);
674 | F(test_vis);
675 | F(test_wait);
676 | F(test_width);
677 | return tab;
678 | }
679 |
--------------------------------------------------------------------------------
/test-shell:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | export SHELL=/bin/sh
4 | die() { echo "$*"; exit 1; } >&2
5 | skip() { echo "$*"; exit 77; } >&2
6 |
7 | ./smtx -z && exit 1 # test invalid option
8 | ./smtx -v || exit
9 | ./smtx -h | grep -q usage: || exit
10 | toe | grep -q '^smtx ' || skip "Tests are not reliable unless smtx.ti is installed"
11 |
--------------------------------------------------------------------------------
/test-unit.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 - 2023 William Pursell
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #if HAVE_PTY_H
33 | # include
34 | # include
35 | #elif HAVE_LIBUTIL_H
36 | # include
37 | #elif HAVE_UTIL_H
38 | # include
39 | #endif
40 |
41 | #ifndef CTRL
42 | #define CTRL(x) ((x) & 0x1f)
43 | #endif
44 | #define PROMPT "ps1>"
45 |
46 | extern int ctlkey;
47 |
48 | void __attribute__((format(printf,3,4)))
49 | send_txt(int fd, const char *wait, const char *fmt, ...);
50 |
51 | void __attribute__((format(printf,3,4)))
52 | send_cmd(int fd, const char *wait, const char *fmt, ...);
53 |
54 | void __attribute__((format(printf,3,4)))
55 | send_raw(int fd, const char *wait, const char *fmt, ...);
56 |
57 | int __attribute__((format(printf,3,4)))
58 | validate_row(int fd, int row, const char *fmt, ... );
59 |
60 | int get_layout(int fd, int flag, char *layout, size_t siz);
61 | int get_state(int fd, char *state, size_t siz);
62 | int get_row(int fd, int row, char *buf, size_t siz);
63 | void grep(int fd, const char *needle);
64 |
65 | int __attribute__((format(printf,3,4)))
66 | check_layout(int fd, int flag, const char *fmt, ...);
67 |
68 | typedef int(test)(int fd);
69 | test test_ack;
70 | test test_alt;
71 | test test_attach;
72 | test test_bighist;
73 | test test_changehist;
74 | test test_cols;
75 | test test_csr;
76 | test test_cup;
77 | test test_cursor;
78 | test test_dashc;
79 | test test_dasht;
80 | test test_dch;
81 | test test_decaln;
82 | test test_ech;
83 | test test_ed;
84 | test test_el;
85 | test test_equalize;
86 | test test_hpr;
87 | test test_ich;
88 | test test_insert;
89 | test test_layout;
90 | test test_layout2;
91 | test test_layout3;
92 | test test_layout4;
93 | test test_lnm;
94 | test test_mode;
95 | test test_navigate;
96 | test test_nel;
97 | test test_pager;
98 | test test_pnm;
99 | test test_prune;
100 | test test_repc;
101 | test test_resend;
102 | test test_resize;
103 | test test_resizepty;
104 | test test_ri;
105 | test test_row;
106 | test test_scrollback;
107 | test test_scrollh;
108 | test test_scs;
109 | test test_sgr;
110 | test test_su;
111 | test test_swap;
112 | test test_transpose;
113 | test test_title;
114 | test test_tput;
115 | test test_tabstop;
116 | test test_utf;
117 | test test_vis;
118 | test test_wait;
119 | test test_width;
120 |
--------------------------------------------------------------------------------
/vtparser.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 - 2019 Rob King
3 | * Copyright 2020 - 2023 William Pursell
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | #include
19 | #include
20 | #include
21 | #include "vtparser.h"
22 |
23 | struct action {
24 | void (*cb)(struct vtp *p, wchar_t w);
25 | struct state *next;
26 | };
27 | struct state {
28 | int reset;
29 | int *lut;
30 | struct action act[0x80];
31 | };
32 |
33 | static struct state ground, esc_entry, esc_collect,
34 | csi_entry, csi_ignore, csi_param, csi_collect,
35 | osc_param, osc_string;
36 |
37 | static void
38 | collect(struct vtp *v, wchar_t w)
39 | {
40 | v->inter = w;
41 | }
42 |
43 | static void
44 | collect_osc(struct vtp *v, wchar_t w)
45 | {
46 | if (v->osc < v->oscbuf + sizeof v->oscbuf - 1) {
47 | *v->osc++ = wctob(w);
48 | }
49 | }
50 |
51 | static void
52 | param(struct vtp *v, wchar_t w)
53 | {
54 | v->argc = v->argc ? v->argc : 1;
55 | int *a = v->args + v->argc - 1;
56 | if (w == L';') {
57 | v->argc += 1;
58 | } else if (v->argc < MAXPARAM && *a < 9999) {
59 | *a = *a * 10 + w - '0';
60 | }
61 | }
62 |
63 | static void
64 | handle_osc(struct vtp *v, wchar_t unused)
65 | {
66 | (void)unused;
67 | switch (v->args[0]) {
68 | case 2: set_status(v->p, v->oscbuf); break;
69 | case 60: build_layout(v->oscbuf); break;
70 | #ifndef NDEBUG
71 | case 62: show_status(v->oscbuf); break;
72 | #endif
73 | }
74 | }
75 |
76 | static void
77 | send(struct vtp *v, wchar_t w)
78 | {
79 | tput(v->p, w, v->inter, v->argc, v->args, v->s->lut[w]);
80 | }
81 |
82 | /*
83 | * State definitions built by consulting the excellent state chart created by
84 | * Paul Flo Williams: http://vt100.net/emu/dec_ansi_parser
85 | * Please note that Williams does not (AFAIK) endorse this work.
86 | */
87 | #define LOWBITS \
88 | [0] = {NULL, NULL}, \
89 | [0x01 ... 0x17] = {send, NULL}, \
90 | [0x18] = {send, &ground}, \
91 | [0x19] = {send, NULL}, \
92 | [0x1a] = {send, &ground}, \
93 | [0x1b] = {NULL, &esc_entry}, \
94 | [0x1c ... 0x1f] = {send, NULL}
95 |
96 | static struct state ground = {
97 | .reset = 1,
98 | .lut = gnds,
99 | .act = {
100 | LOWBITS,
101 | [0x20 ... 0x7f] = {send, NULL},
102 | }
103 | };
104 |
105 | static struct state esc_entry = {
106 | .reset = 1,
107 | .lut = escs,
108 | .act = {
109 | LOWBITS,
110 | [0x20] = {collect, &esc_collect}, /* sp */
111 | [0x21] = {NULL, &osc_string}, /* ! */
112 | [0x22 ... 0x2f] = {collect, &esc_collect}, /* "#$%&'()*+,-./ */
113 | [0x30 ... 0x4f] = {send, &ground},
114 | [0x50] = {NULL, &osc_string}, /* P */
115 | [0x51 ... 0x57] = {send, &ground},
116 | [0x58] = {NULL, NULL},
117 | [0x59 ... 0x5a] = {send, &ground},
118 | [0x5b] = {NULL, &csi_entry}, /* [ */
119 | [0x5c] = {send, &ground}, /* \ */
120 | [0x5d] = {NULL, &osc_param}, /* ] */
121 | [0x5e] = {NULL, &osc_string}, /* ^ */
122 | [0x5f] = {NULL, &osc_string}, /* _ */
123 | [0x60 ... 0x6a] = {send, &ground}, /* `a-j */
124 | [0x6b] = {NULL, &osc_string}, /* k */
125 | [0x6c ... 0x7e] = {send, &ground}, /* l-z{|}~ */
126 | [0x7f] = {NULL, NULL},
127 | }
128 | };
129 |
130 | static struct state esc_collect = {
131 | .reset = 0,
132 | .lut = escs,
133 | .act = {
134 | LOWBITS,
135 | [0x20 ... 0x2f] = {collect, NULL}, /* sp!"#$%&'()*+,-./ */
136 | [0x30 ... 0x7e] = {send, &ground}, /* 0-9a-zA-z ... */
137 | [0x7f] = {NULL, NULL},
138 | }
139 | };
140 |
141 | static struct state csi_entry = {
142 | .reset = 0,
143 | .lut = csis,
144 | .act = {
145 | LOWBITS,
146 | [0x20 ... 0x2f] = {collect, &csi_collect}, /* !"#$%&'()*+,-./ */
147 | [0x30 ... 0x39] = {param, &csi_param}, /* 0 - 9 */
148 | [0x3a] = {NULL, &csi_ignore}, /* : */
149 | [0x3b] = {param, &csi_param}, /* ; */
150 | [0x3c ... 0x3f] = {collect, &csi_param}, /* <=>? */
151 | [0x40 ... 0x7e] = {send, &ground}, /* @A-Za-z[\]^_`{|}~ */
152 | [0x7f] = {NULL, NULL},
153 | }
154 | };
155 |
156 | static struct state csi_ignore = {
157 | .reset = 0,
158 | .lut = csis,
159 | .act = {
160 | LOWBITS,
161 | [0x20 ... 0x3f] = {NULL, NULL}, /* !"#$%&'()*+,-./0-9... */
162 | [0x40 ... 0x7e] = {NULL, &ground}, /* @A-Za-z[\]^_`{|}~ */
163 | [0x7f] = {NULL, NULL},
164 | }
165 | };
166 |
167 | static struct state csi_param = {
168 | .reset = 0,
169 | .lut = csis,
170 | .act = {
171 | LOWBITS,
172 | [0x20 ... 0x2f] = {collect, &csi_collect},
173 | [0x30 ... 0x39] = {param, NULL}, /* 0 - 9 */
174 | [0x3a] = {NULL, &csi_ignore},
175 | [0x3b] = {param, NULL}, /* ; */
176 | [0x3c ... 0x3f] = {NULL, &csi_ignore},
177 | [0x40 ... 0x7e] = {send, &ground},
178 | [0x7f] = {NULL, NULL},
179 | }
180 | };
181 |
182 | static struct state csi_collect = {
183 | .reset = 0,
184 | .lut = csis,
185 | .act = {
186 | LOWBITS,
187 | [0x20 ... 0x2f] = {collect, NULL}, /* !"#$%&'()*+,-./ */
188 | [0x30 ... 0x3f] = {NULL, &csi_ignore}, /* 0-9 :;<=>? */
189 | [0x40 ... 0x7e] = {send, &ground},
190 | [0x7f] = {NULL, NULL},
191 | }
192 | };
193 |
194 | #pragma GCC diagnostic ignored "-Woverride-init"
195 | static struct state osc_param = {
196 | .reset = 0,
197 | .lut = oscs,
198 | .act = {
199 | LOWBITS,
200 | [0x07] = {handle_osc, &ground},
201 | [0x20 ... 0x7f] = {collect_osc, NULL},
202 | [0x30 ... 0x39] = {param, NULL}, /* 0 - 9 */
203 | [0x3b] = {param, &osc_string}, /* ; */
204 | }
205 | };
206 |
207 | static struct state osc_string = {
208 | .reset = 0,
209 | .lut = oscs,
210 | .act = {
211 | LOWBITS,
212 | [0x07] = {handle_osc, &ground},
213 | [0x0a] = {handle_osc, &ground}, /* \n */
214 | [0x0d] = {handle_osc, &ground}, /* \r */
215 | [0x20 ... 0x7f] = {collect_osc, NULL},
216 | }
217 | };
218 |
219 | void
220 | vtreset(struct vtp *v)
221 | {
222 | memset(&v->inter, 0, sizeof *v - offsetof(struct vtp, inter));
223 | v->s = &ground;
224 | v->osc = v->oscbuf;
225 | }
226 |
227 | void
228 | vtwrite(struct vtp *vp, const char *s, size_t n)
229 | {
230 | size_t r;
231 | for (const char *e = s + n; s < e; s += r) {
232 | wchar_t w;
233 | switch (r = mbrtowc(&w, s, e - s, &vp->ms)) {
234 | case -1: /* invalid character, skip it */
235 | case -2: /* incomplete character, skip it */
236 | w = VTPARSER_BAD_CHAR;
237 | memset(&vp->ms, 0, sizeof vp->ms); /* Fallthru */
238 | case 0: /* literal zero, write it and advance */
239 | r = 1;
240 | }
241 | if (w >= 0 && w < 0x80) {
242 | struct action *a = vp->s->act + w;
243 | if (a->cb) {
244 | a->cb(vp, w);
245 | }
246 | if (a->next) {
247 | if (a->next->reset) {
248 | vtreset(vp);
249 | }
250 | vp->s = a->next;
251 | }
252 | } else {
253 | tput(vp->p, w, 0, 0, NULL, print);
254 | }
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/vtparser.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 - 2019 Rob King
3 | * Copyright 2020 - 2023 William Pursell
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | #ifndef VTC_H
19 | #define VTC_H
20 |
21 | #include
22 | #include
23 |
24 | /* The names for handlers come from their ANSI/ECMA/DEC mnemonics. */
25 | enum cmd {
26 | ack=1, /* Acknowledge Enquiry */
27 | bell, /* Terminal bell. */
28 | cnl, /* Cursor Next Line */
29 | cpl, /* Cursor Previous Line */
30 | cr, /* Carriage Return */
31 | csr, /* Change Scrolling Region */
32 | cub, /* Cursor Backward */
33 | cud, /* Cursor Down */
34 | cuf, /* Cursor Forward */
35 | cup, /* Cursor Position */
36 | cuu, /* Cursor Up */
37 | dch, /* Delete Character */
38 | ech, /* Erase Character */
39 | ed, /* Erase in Display */
40 | el, /* Erase in Line */
41 | hpa, /* Cursor Horizontal Absolute */
42 | hpr, /* Cursor Horizontal Relative */
43 | hts, /* Horizontal Tab Set */
44 | ich, /* Insert Character */
45 | idl, /* Insert/Delete Line */
46 | ind, /* Index */
47 | mode, /* Set or Reset Mode */
48 | nel, /* Next Line */
49 | numkp, /* Application/Numeric Keypad Mode */
50 | osc, /* Operating System Command */
51 | pnl, /* Newline */
52 | print, /* Print a character to the terminal */
53 | rc, /* Restore Cursor */
54 | rep, /* Repeat Character */
55 | ri, /* Reverse Index (scroll back) */
56 | ris, /* Reset to Initial State */
57 | sc, /* Save Cursor */
58 | scs, /* Select Character Set */
59 | sgr, /* SGR - Select Graphic Rendition */
60 | so, /* Switch Out/In Character Set */
61 | su, /* Scroll Up/Down */
62 | tab, /* Tab forwards or backwards */
63 | tbc, /* Tabulation Clear */
64 | vis, /* Cursor visibility */
65 | vpa, /* Cursor Vertical Absolute */
66 | vpr, /* Cursor Vertical Relative */
67 | #if 0
68 |
69 | The following are unimplemented:
70 | decid, /* Send Terminal Identification */
71 | decreqtparm, /* Request Device Parameters */
72 | dsr, /* Device Status Report */
73 | #endif
74 | };
75 |
76 | /*
77 | * VTPARSER_BAD_CHAR is the character that will be displayed when
78 | * an application sends an invalid multibyte sequence to the terminal.
79 | */
80 | #ifndef VTPARSER_BAD_CHAR
81 | #ifdef __STDC_ISO_10646__
82 | #define VTPARSER_BAD_CHAR ((wchar_t)0xfffd)
83 | #else
84 | #define VTPARSER_BAD_CHAR L'?'
85 | #endif
86 | #endif
87 |
88 | #define MAXPARAM 16
89 | #define MAXOSC 511
90 |
91 | struct pty;
92 | struct vtp {
93 | struct pty *p;
94 | struct state *s;
95 | wchar_t inter;
96 | int argc;
97 | int args[MAXPARAM];
98 | char oscbuf[MAXOSC + 1];
99 | char *osc;
100 | mbstate_t ms;
101 | };
102 | extern int cons[0x80];
103 | extern int csis[0x80];
104 | extern int escs[0x80];
105 | extern int oscs[0x80];
106 | extern int gnds[0x80];
107 |
108 | extern void set_status(struct pty *p, const char *arg);
109 | extern void tput(struct pty *, wchar_t, wchar_t, int, int *, int);
110 | extern void vtreset(struct vtp *v);
111 | extern void vtwrite(struct vtp *vp, const char *s, size_t n);
112 | extern int build_layout(const char *);
113 |
114 | /* Debugging/test harness */
115 | #ifndef NDEBUG
116 | extern void show_status(const char *);
117 | #endif
118 | #endif
119 |
--------------------------------------------------------------------------------