1 ;;; termio.lisp --- Terminal-related utilities
3 ;; Copyright (C) 2012 Didier Verna.
5 ;; Author: Didier Verna <didier@lrde.epita.fr>
6 ;; Maintainer: Didier Verna <didier@lrde.epita.fr>
8 ;; This file is part of Clon.
10 ;; Permission to use, copy, modify, and distribute this software for any
11 ;; purpose with or without fee is hereby granted, provided that the above
12 ;; copyright notice and this permission notice appear in all copies.
14 ;; THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 ;; WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 ;; MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 ;; ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 ;; WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 ;; ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 ;; OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 ;; Contents management by FCM version 0.1.
27 ;; Basically, this whole stream manipulation stuff is a big non-standard mess.
28 ;; Terminal streams are sometimes implemented on top of file streams,
29 ;; sometimes not. Functions that retreive file descriptors sometimes just
30 ;; return nil when it's not possible, or something else, or throw errors. It
31 ;; is not always possible to know whether a stream has file descriptors or
32 ;; not. Finally, there are cases where streams have an implicit hard-wired
33 ;; file descriptor of 1, without any function to get it...
35 ;; Here's the information that I've gathered so far. This information is used
36 ;; in the implementation of STREAM-IOCTL-OUTPUT-HANDLE below.
38 ;; SBCL: SB-SYS:FD-STREAM for both terminals and files.
39 ;; CMUCL: SYSTEM:FD-STREAM for both terminals and files.
40 ;; CCL: CCL::BASIC-CHARACTER-[INPUT|OUTPUT]-STREAM for terminals,
41 ;; CCL::BASIC-FILE-CHARACTER-[INPUT|OUTPUT]-STREAM for files, with
42 ;; the above somewhere in the superclasses. I'm not sure if any
43 ;; class guarantees the existence of file descriptors, but it
44 ;; doesn't matter because CCL:STREAM-DEVICE returns nil if there are
45 ;; none, so we can safely use it on all classes.
46 ;; ECL: FILE-STREAM for both terminals and files.
47 ;; CLISP: STREAM for terminals, FILE-STREAM for files. It is impossible (at
48 ;; the Lisp level) to detect whether a stream has file descriptors
49 ;; or not. The only workaround available right now is to catch a
50 ;; STREAM-ERROR potentially thrown by EXT:STREAM-HANDLES.
51 ;; ACL: EXCL:TERMINAL-SIMPLE-STREAM for terminals,
52 ;; EXCL:FILE-SIMPLE-STREAM for files. I'm not sure if any class
53 ;; guarantees the existence of file descriptors, but it doesn't
54 ;; matter because EXCL::STREAM-[INPUT|OUTPUT]-HANDLE returns
55 ;; something which is not a number when there are none. The return
56 ;; value seems to be either NIL or some modified version of the
58 ;; LW: SYSTEM::TERMINAL-STREAM for terminals, STREAM::[ENC]-FILE-STREAM
59 ;; for files. Terminal streams always have file descriptors 0
60 ;; (input) and 1 (output) hard wired. File streams are subclasses of
61 ;; STREAM::OS-FILE-HANDLE-STREAM on which we can retrieve file
67 (in-package :com.dvlsoft.clon
)
68 (in-readtable :com.dvlsoft.clon
)
71 ;; Preamble C code needed for ECL's FD-LINE-WIDTH function.
75 #include <sys/ioctl.h>")
78 (defgeneric stream-ioctl-output-handle
(stream)
79 (:documentation
"Return STREAM's ioctl output handle or NIL.")
80 ;; Standard canonicalization methods:
81 (:method
((stream synonym-stream
))
82 (stream-ioctl-output-handle (symbol-value (synonym-stream-symbol stream
))))
83 (:method
((stream two-way-stream
))
84 (stream-ioctl-output-handle (two-way-stream-output-stream stream
)))
86 ;; Compiler-specific implementations:
88 (:method
((stream sb-sys
:fd-stream
))
89 ;; sb-posix's IOCTL function takes a stream directly
92 (:method
((stream system
:fd-stream
))
93 (system:fd-stream-fd stream
))
95 (:method
((stream file-stream
))
96 (ext:file-stream-fd stream
))
98 (:method
((stream system
::terminal-stream
))
101 (:method
((stream stream
::os-file-handle-stream
))
102 (stream::os-file-handle-stream-file-handle stream
))
104 #+ecl
(declare (ignore stream
))
106 (ccl:stream-device stream
:output
)
108 (multiple-value-bind (input-handle output-handle
)
109 (when (or (sys::built-in-stream-p stream
)
110 (eq (type-of stream
) 'socket
:socket-server
))
111 (handler-case (ext:stream-handles stream
)
114 (declare (ignore input-handle
))
117 (let ((handle (excl::stream-output-handle stream
)))
118 (when (numberp handle
)
120 #-
(or ccl clisp allegro
)
124 (defun fd-line-width (fd)
125 "Get the line width for FD (file descriptor).
127 - the line width, or -1 if it can't be computed
128 (typically when FD does not denote a tty),
129 - an error message if the operation failed."
130 (ffi:c-inline
(fd) (:int
) (values :int
:cstring
) "{
136 struct winsize window;
137 if (ioctl (fd, TIOCGWINSZ, &window) == -1)
140 msg = strerror (errno);
143 cols = (int) window.ws_col;
149 ;; #### NOTE: ABCL doesn't appear below because this module (termio) is never
151 (defun stream-line-width
152 (stream &aux
(handle (stream-ioctl-output-handle stream
)))
153 "Get STREAM's line width.
155 - the stream's line width, or nil if it can't be computed
156 (typically when the stream does not denote a tty),
157 - an error message if the operation failed."
159 ;; #### NOTE: doing a TIOCGWINSZ ioctl here is a convenient way to both
160 ;; know whether we're connected to a tty, and getting the terminal width
161 ;; at the same time. In case the ioctl fails, we need to distinguish
162 ;; between and ENOTTY error, which simply means that we're not connected
163 ;; to a terminal, and the other which are real errors and need to be
167 (locally (declare (sb-ext:muffle-conditions sb-ext
:compiler-note
))
169 (with-winsize winsize
()
170 (sb-posix:ioctl handle
+tiocgwinsz
+ winsize
)
171 (winsize-ws-col winsize
))
172 (sb-posix:syscall-error
(error)
173 (unless (= (sb-posix:syscall-errno error
) sb-posix
:enotty
)
174 (values nil error
)))))
176 (locally (declare (optimize (ext:inhibit-warnings
3)))
177 (alien:with-alien
((winsize (alien:struct unix
:winsize
)))
178 (multiple-value-bind (success error-number
)
179 (unix:unix-ioctl handle unix
:tiocgwinsz winsize
)
181 (alien:slot winsize
'unix
:ws-col
)
182 (unless (= error-number unix
:enotty
)
183 (values nil
(unix:get-unix-error-msg error-number
)))))))
185 (ccl:rlet
((winsize :winsize
))
186 (let ((result (ccl::int-errno-call
187 (#_ioctl handle
#$TIOCGWINSZ
:address winsize
))))
189 (ccl:pref winsize
:winsize.ws_col
)
190 (unless (= result
(- #$ENOTTY
))
191 (values nil
(ccl::%strerror
(- result
)))))))
193 (multiple-value-bind (cols msg
)
194 (fd-line-width handle
)
195 (values (unless (= cols -
1) cols
) msg
))
196 #+(or clisp allegro lispworks
)
197 (cffi:with-foreign-object
(winsize 'winsize
)
198 (let ((result (cffi:foreign-funcall
"ioctl"
204 (unless (= +errno
+ +enotty
+)
206 (cffi:foreign-funcall
"strerror"
207 :int
+errno
+ :string
)))
208 (cffi:with-foreign-slots
((ws-col) winsize winsize
)
211 ;;; termio.lisp ends here