Ruby: ext/io/console/console.c Source File

Ruby 3.0.5p211 (2022-11-24 revision ba5cf0f7c52d4d35cc6a173c89eda98ceffa2dcf)

console.c
Go to the documentation of this file.
1/* -*- c-file-style: "ruby"; indent-tabs-mode: t -*- */
2/*
3 * console IO module
4 */
5#include "ruby.h"
6#include "ruby/io.h"
7#include "ruby/thread.h"
8
9#ifdef HAVE_UNISTD_H
10#include <unistd.h>
11#endif
12#ifdef HAVE_FCNTL_H
13#include <fcntl.h>
14#endif
15#ifdef HAVE_SYS_IOCTL_H
16#include <sys/ioctl.h>
17#endif
18
19#if defined HAVE_TERMIOS_H
20# include <termios.h>
21typedef struct termios conmode;
22
23static int
24setattr(int fd, conmode *t)
25{
26 while (tcsetattr(fd, TCSANOW, t)) {
27 if (errno != EINTR) return 0;
28 }
29 return 1;
30}
31# define getattr(fd, t) (tcgetattr(fd, t) == 0)
32#elif defined HAVE_TERMIO_H
33# include <termio.h>
34typedef struct termio conmode;
35# define setattr(fd, t) (ioctl(fd, TCSETAF, t) == 0)
36# define getattr(fd, t) (ioctl(fd, TCGETA, t) == 0)
37#elif defined HAVE_SGTTY_H
38# include <sgtty.h>
39typedef struct sgttyb conmode;
40# ifdef HAVE_STTY
41# define setattr(fd, t) (stty(fd, t) == 0)
42# else
43# define setattr(fd, t) (ioctl((fd), TIOCSETP, (t)) == 0)
44# endif
45# ifdef HAVE_GTTY
46# define getattr(fd, t) (gtty(fd, t) == 0)
47# else
48# define getattr(fd, t) (ioctl((fd), TIOCGETP, (t)) == 0)
49# endif
50#elif defined _WIN32
51#include <winioctl.h>
52#include <conio.h>
53typedef DWORD conmode;
54
55#define LAST_ERROR rb_w32_map_errno(GetLastError())
56#define SET_LAST_ERROR (errno = LAST_ERROR, 0)
57
58static int
59setattr(int fd, conmode *t)
60{
61 int x = SetConsoleMode((HANDLE)rb_w32_get_osfhandle(fd), *t);
62 if (!x) errno = LAST_ERROR;
63 return x;
64}
65
66static int
67getattr(int fd, conmode *t)
68{
69 int x = GetConsoleMode((HANDLE)rb_w32_get_osfhandle(fd), t);
70 if (!x) errno = LAST_ERROR;
71 return x;
72}
73#endif
74#ifndef SET_LAST_ERROR
75#define SET_LAST_ERROR (0)
76#endif
77
78static ID id_getc, id_console, id_close, id_min, id_time, id_intr;
79#if ENABLE_IO_GETPASS
80static ID id_gets, id_chomp_bang;
81#endif
82
83#ifdef HAVE_RB_SCHEDULER_TIMEOUT
84extern VALUE rb_scheduler_timeout(struct timeval *timeout);
85#endif
86
87#define sys_fail_fptr(fptr) rb_sys_fail_str((fptr)->pathv)
88
89#ifndef HAVE_RB_F_SEND
90static ID id___send__;
91
92static VALUE
93rb_f_send(int argc, VALUE *argv, VALUE recv)
94{
95 VALUE sym = argv[0];
96 ID vid = rb_check_id(&sym);
97 if (vid) {
98 --argc;
99 ++argv;
100 }
101 else {
102 vid = id___send__;
103 }
104 return rb_funcallv(recv, vid, argc, argv);
105}
106#endif
107
108 typedef struct {
109 int vmin;
110 int vtime;
111 int intr;
112} rawmode_arg_t;
113
114static rawmode_arg_t *
115rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t *opts)
116{
117 int argc = *argcp;
118 rawmode_arg_t *optp = NULL;
119 VALUE vopts = Qnil;
120#ifdef RB_SCAN_ARGS_PASS_CALLED_KEYWORDS
121 argc = rb_scan_args(argc, argv, "*:", NULL, &vopts);
122#else
123 if (argc > min_argc) {
124 vopts = rb_check_hash_type(argv[argc-1]);
125 if (!NIL_P(vopts)) {
126 argv[argc-1] = vopts;
127 vopts = rb_extract_keywords(&argv[argc-1]);
128 if (!argv[argc-1]) *argcp = --argc;
129 if (!vopts) vopts = Qnil;
130 }
131 }
132#endif
133 rb_check_arity(argc, min_argc, max_argc);
134 if (!NIL_P(vopts)) {
135 VALUE vmin = rb_hash_aref(vopts, ID2SYM(id_min));
136 VALUE vtime = rb_hash_aref(vopts, ID2SYM(id_time));
137 VALUE intr = rb_hash_aref(vopts, ID2SYM(id_intr));
138 /* default values by `stty raw` */
139 opts->vmin = 1;
140 opts->vtime = 0;
141 opts->intr = 0;
142 if (!NIL_P(vmin)) {
143 opts->vmin = NUM2INT(vmin);
144 optp = opts;
145 }
146 if (!NIL_P(vtime)) {
147 VALUE v10 = INT2FIX(10);
148 vtime = rb_funcall3(vtime, '*', 1, &v10);
149 opts->vtime = NUM2INT(vtime);
150 optp = opts;
151 }
152 switch (intr) {
153 case Qtrue:
154 opts->intr = 1;
155 optp = opts;
156 break;
157 case Qfalse:
158 opts->intr = 0;
159 optp = opts;
160 break;
161 case Qnil:
162 break;
163 default:
164 rb_raise(rb_eArgError, "true or false expected as intr: %"PRIsVALUE,
165 intr);
166 }
167 }
168 return optp;
169}
170
171static void
172set_rawmode(conmode *t, void *arg)
173{
174#ifdef HAVE_CFMAKERAW
175 cfmakeraw(t);
176 t->c_lflag &= ~(ECHOE|ECHOK);
177#elif defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H
178 t->c_iflag &= ~(IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF|IXANY|IMAXBEL);
179 t->c_oflag &= ~OPOST;
180 t->c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON|ISIG|IEXTEN|XCASE);
181 t->c_cflag &= ~(CSIZE|PARENB);
182 t->c_cflag |= CS8;
183 t->c_cc[VMIN] = 1;
184 t->c_cc[VTIME] = 0;
185#elif defined HAVE_SGTTY_H
186 t->sg_flags &= ~ECHO;
187 t->sg_flags |= RAW;
188#elif defined _WIN32
189 *t = 0;
190#endif
191 if (arg) {
192 const rawmode_arg_t *r = arg;
193#ifdef VMIN
194 if (r->vmin >= 0) t->c_cc[VMIN] = r->vmin;
195#endif
196#ifdef VTIME
197 if (r->vtime >= 0) t->c_cc[VTIME] = r->vtime;
198#endif
199#ifdef ISIG
200 if (r->intr) {
201 t->c_iflag |= BRKINT;
202 t->c_lflag |= ISIG;
203 t->c_oflag |= OPOST;
204 }
205#endif
206 (void)r;
207 }
208}
209
210static void
211set_cookedmode(conmode *t, void *arg)
212{
213#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H
214 t->c_iflag |= (BRKINT|ISTRIP|ICRNL|IXON);
215 t->c_oflag |= OPOST;
216 t->c_lflag |= (ECHO|ECHOE|ECHOK|ECHONL|ICANON|ISIG|IEXTEN);
217#elif defined HAVE_SGTTY_H
218 t->sg_flags |= ECHO;
219 t->sg_flags &= ~RAW;
220#elif defined _WIN32
221 *t |= ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT;
222#endif
223}
224
225static void
226set_noecho(conmode *t, void *arg)
227{
228#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H
229 t->c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
230#elif defined HAVE_SGTTY_H
231 t->sg_flags &= ~ECHO;
232#elif defined _WIN32
233 *t &= ~ENABLE_ECHO_INPUT;
234#endif
235}
236
237static void
238set_echo(conmode *t, void *arg)
239{
240#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H
241 t->c_lflag |= (ECHO | ECHOE | ECHOK | ECHONL);
242#elif defined HAVE_SGTTY_H
243 t->sg_flags |= ECHO;
244#elif defined _WIN32
245 *t |= ENABLE_ECHO_INPUT;
246#endif
247}
248
249static int
250echo_p(conmode *t)
251{
252#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H
253 return (t->c_lflag & (ECHO | ECHONL)) != 0;
254#elif defined HAVE_SGTTY_H
255 return (t->sg_flags & ECHO) != 0;
256#elif defined _WIN32
257 return (*t & ENABLE_ECHO_INPUT) != 0;
258#endif
259}
260
261static int
262set_ttymode(int fd, conmode *t, void (*setter)(conmode *, void *), void *arg)
263{
264 conmode r;
265 if (!getattr(fd, t)) return 0;
266 r = *t;
267 setter(&r, arg);
268 return setattr(fd, &r);
269}
270
271#define GetReadFD(fptr) ((fptr)->fd)
272
273static inline int
274get_write_fd(const rb_io_t *fptr)
275{
276 VALUE wio = fptr->tied_io_for_writing;
277 rb_io_t *ofptr;
278 if (!wio) return fptr->fd;
279 GetOpenFile(wio, ofptr);
280 return ofptr->fd;
281}
282#define GetWriteFD(fptr) get_write_fd(fptr)
283
284#define FD_PER_IO 2
285
286static VALUE
287ttymode(VALUE io, VALUE (*func)(VALUE), VALUE farg, void (*setter)(conmode *, void *), void *arg)
288{
289 rb_io_t *fptr;
290 int status = -1;
291 int error = 0;
292 int fd[FD_PER_IO];
293 conmode t[FD_PER_IO];
294 VALUE result = Qnil;
295
296 GetOpenFile(io, fptr);
297 fd[0] = GetReadFD(fptr);
298 if (fd[0] != -1) {
299 if (set_ttymode(fd[0], t+0, setter, arg)) {
300 status = 0;
301 }
302 else {
303 error = errno;
304 fd[0] = -1;
305 }
306 }
307 fd[1] = GetWriteFD(fptr);
308 if (fd[1] != -1 && fd[1] != fd[0]) {
309 if (set_ttymode(fd[1], t+1, setter, arg)) {
310 status = 0;
311 }
312 else {
313 error = errno;
314 fd[1] = -1;
315 }
316 }
317 if (status == 0) {
318 result = rb_protect(func, farg, &status);
319 }
320 GetOpenFile(io, fptr);
321 if (fd[0] != -1 && fd[0] == GetReadFD(fptr)) {
322 if (!setattr(fd[0], t+0)) {
323 error = errno;
324 status = -1;
325 }
326 }
327 if (fd[1] != -1 && fd[1] != fd[0] && fd[1] == GetWriteFD(fptr)) {
328 if (!setattr(fd[1], t+1)) {
329 error = errno;
330 status = -1;
331 }
332 }
333 if (status) {
334 if (status == -1) {
335 rb_syserr_fail(error, 0);
336 }
337 rb_jump_tag(status);
338 }
339 return result;
340}
341
342#if !defined _WIN32
343 struct ttymode_callback_args {
344 VALUE (*func)(VALUE, VALUE);
345 VALUE io;
346 VALUE farg;
347};
348
349static VALUE
350ttymode_callback(VALUE args)
351{
352 struct ttymode_callback_args *argp = (struct ttymode_callback_args *)args;
353 return argp->func(argp->io, argp->farg);
354}
355
356static VALUE
357ttymode_with_io(VALUE io, VALUE (*func)(VALUE, VALUE), VALUE farg, void (*setter)(conmode *, void *), void *arg)
358{
359 struct ttymode_callback_args cargs;
360 cargs.func = func;
361 cargs.io = io;
362 cargs.farg = farg;
363 return ttymode(io, ttymode_callback, (VALUE)&cargs, setter, arg);
364}
365#endif
366
367/*
368 * call-seq:
369 * io.raw(min: nil, time: nil, intr: nil) {|io| }
370 *
371 * Yields +self+ within raw mode, and returns the result of the block.
372 *
373 * STDIN.raw(&:gets)
374 *
375 * will read and return a line without echo back and line editing.
376 *
377 * The parameter +min+ specifies the minimum number of bytes that
378 * should be received when a read operation is performed. (default: 1)
379 *
380 * The parameter +time+ specifies the timeout in _seconds_ with a
381 * precision of 1/10 of a second. (default: 0)
382 *
383 * If the parameter +intr+ is +true+, enables break, interrupt, quit,
384 * and suspend special characters.
385 *
386 * Refer to the manual page of termios for further details.
387 *
388 * You must require 'io/console' to use this method.
389 */
390static VALUE
391console_raw(int argc, VALUE *argv, VALUE io)
392{
393 rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts);
394 return ttymode(io, rb_yield, io, set_rawmode, optp);
395}
396
397/*
398 * call-seq:
399 * io.raw!(min: nil, time: nil, intr: nil) -> io
400 *
401 * Enables raw mode, and returns +io+.
402 *
403 * If the terminal mode needs to be back, use <code>io.raw { ... }</code>.
404 *
405 * See IO#raw for details on the parameters.
406 *
407 * You must require 'io/console' to use this method.
408 */
409static VALUE
410console_set_raw(int argc, VALUE *argv, VALUE io)
411{
412 conmode t;
413 rb_io_t *fptr;
414 int fd;
415 rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts);
416
417 GetOpenFile(io, fptr);
418 fd = GetReadFD(fptr);
419 if (!getattr(fd, &t)) sys_fail_fptr(fptr);
420 set_rawmode(&t, optp);
421 if (!setattr(fd, &t)) sys_fail_fptr(fptr);
422 return io;
423}
424
425/*
426 * call-seq:
427 * io.cooked {|io| }
428 *
429 * Yields +self+ within cooked mode.
430 *
431 * STDIN.cooked(&:gets)
432 *
433 * will read and return a line with echo back and line editing.
434 *
435 * You must require 'io/console' to use this method.
436 */
437static VALUE
438console_cooked(VALUE io)
439{
440 return ttymode(io, rb_yield, io, set_cookedmode, NULL);
441}
442
443/*
444 * call-seq:
445 * io.cooked!
446 *
447 * Enables cooked mode.
448 *
449 * If the terminal mode needs to be back, use io.cooked { ... }.
450 *
451 * You must require 'io/console' to use this method.
452 */
453static VALUE
454console_set_cooked(VALUE io)
455{
456 conmode t;
457 rb_io_t *fptr;
458 int fd;
459
460 GetOpenFile(io, fptr);
461 fd = GetReadFD(fptr);
462 if (!getattr(fd, &t)) sys_fail_fptr(fptr);
463 set_cookedmode(&t, NULL);
464 if (!setattr(fd, &t)) sys_fail_fptr(fptr);
465 return io;
466}
467
468#ifndef _WIN32
469static VALUE
470getc_call(VALUE io)
471{
472 return rb_funcallv(io, id_getc, 0, 0);
473}
474#else
475static void *
476nogvl_getch(void *p)
477{
478 int len = 0;
479 wint_t *buf = p, c = _getwch();
480
481 switch (c) {
482 case WEOF:
483 break;
484 case 0x00:
485 case 0xe0:
486 buf[len++] = c;
487 c = _getwch();
488 /* fall through */
489 default:
490 buf[len++] = c;
491 break;
492 }
493 return (void *)(VALUE)len;
494}
495#endif
496
497/*
498 * call-seq:
499 * io.getch(min: nil, time: nil, intr: nil) -> char
500 *
501 * Reads and returns a character in raw mode.
502 *
503 * See IO#raw for details on the parameters.
504 *
505 * You must require 'io/console' to use this method.
506 */
507static VALUE
508console_getch(int argc, VALUE *argv, VALUE io)
509{
510 rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts);
511#ifndef _WIN32
512 return ttymode(io, getc_call, io, set_rawmode, optp);
513#else
514 rb_io_t *fptr;
515 VALUE str;
516 wint_t c;
517 int len;
518 char buf[8];
519 wint_t wbuf[2];
520# ifndef HAVE_RB_IO_WAIT
521 struct timeval *to = NULL, tv;
522# else
523 VALUE timeout = Qnil;
524# endif
525
526 GetOpenFile(io, fptr);
527 if (optp) {
528 if (optp->vtime) {
529# ifndef HAVE_RB_IO_WAIT
530 to = &tv;
531# else
532 struct timeval tv;
533# endif
534 tv.tv_sec = optp->vtime / 10;
535 tv.tv_usec = (optp->vtime % 10) * 100000;
536# ifdef HAVE_RB_IO_WAIT
537 timeout = rb_scheduler_timeout(&tv);
538# endif
539 }
540 switch (optp->vmin) {
541 case 1: /* default */
542 break;
543 case 0: /* return nil when timed out */
544 if (optp->vtime) break;
545 /* fallthru */
546 default:
547 rb_warning("min option larger than 1 ignored");
548 }
549 if (optp->intr) {
550# ifndef HAVE_RB_IO_WAIT
551 int w = rb_wait_for_single_fd(fptr->fd, RB_WAITFD_IN, to);
552 if (w < 0) rb_eof_error();
553 if (!(w & RB_WAITFD_IN)) return Qnil;
554# else
555 VALUE result = rb_io_wait(io, RUBY_IO_READABLE, timeout);
556 if (result == Qfalse) return Qnil;
557# endif
558 }
559 else if (optp->vtime) {
560 rb_warning("Non-zero vtime option ignored if intr flag is unset");
561 }
562 }
563 len = (int)(VALUE)rb_thread_call_without_gvl(nogvl_getch, wbuf, RUBY_UBF_IO, 0);
564 switch (len) {
565 case 0:
566 return Qnil;
567 case 2:
568 buf[0] = (char)wbuf[0];
569 c = wbuf[1];
570 len = 1;
571 do {
572 buf[len++] = (unsigned char)c;
573 } while ((c >>= CHAR_BIT) && len < (int)sizeof(buf));
574 return rb_str_new(buf, len);
575 default:
576 c = wbuf[0];
577 len = rb_uv_to_utf8(buf, c);
578 str = rb_utf8_str_new(buf, len);
579 return rb_str_conv_enc(str, NULL, rb_default_external_encoding());
580 }
581#endif
582}
583
584/*
585 * call-seq:
586 * io.noecho {|io| }
587 *
588 * Yields +self+ with disabling echo back.
589 *
590 * STDIN.noecho(&:gets)
591 *
592 * will read and return a line without echo back.
593 *
594 * You must require 'io/console' to use this method.
595 */
596static VALUE
597console_noecho(VALUE io)
598{
599 return ttymode(io, rb_yield, io, set_noecho, NULL);
600}
601
602/*
603 * call-seq:
604 * io.echo = flag
605 *
606 * Enables/disables echo back.
607 * On some platforms, all combinations of this flags and raw/cooked
608 * mode may not be valid.
609 *
610 * You must require 'io/console' to use this method.
611 */
612static VALUE
613console_set_echo(VALUE io, VALUE f)
614{
615 conmode t;
616 rb_io_t *fptr;
617 int fd;
618
619 GetOpenFile(io, fptr);
620 fd = GetReadFD(fptr);
621 if (!getattr(fd, &t)) sys_fail_fptr(fptr);
622 if (RTEST(f))
623 set_echo(&t, NULL);
624 else
625 set_noecho(&t, NULL);
626 if (!setattr(fd, &t)) sys_fail_fptr(fptr);
627 return io;
628}
629
630/*
631 * call-seq:
632 * io.echo? -> true or false
633 *
634 * Returns +true+ if echo back is enabled.
635 *
636 * You must require 'io/console' to use this method.
637 */
638static VALUE
639console_echo_p(VALUE io)
640{
641 conmode t;
642 rb_io_t *fptr;
643 int fd;
644
645 GetOpenFile(io, fptr);
646 fd = GetReadFD(fptr);
647 if (!getattr(fd, &t)) sys_fail_fptr(fptr);
648 return echo_p(&t) ? Qtrue : Qfalse;
649}
650
651static const rb_data_type_t conmode_type = {
652 "console-mode",
653 {0, RUBY_TYPED_DEFAULT_FREE,},
654 0, 0,
655 RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
656};
657static VALUE cConmode;
658
659static VALUE
660conmode_alloc(VALUE klass)
661{
662 return rb_data_typed_object_zalloc(klass, sizeof(conmode), &conmode_type);
663}
664
665static VALUE
666conmode_new(VALUE klass, const conmode *t)
667{
668 VALUE obj = conmode_alloc(klass);
669 *(conmode *)DATA_PTR(obj) = *t;
670 return obj;
671}
672
673static VALUE
674conmode_init_copy(VALUE obj, VALUE obj2)
675{
676 conmode *t = rb_check_typeddata(obj, &conmode_type);
677 conmode *t2 = rb_check_typeddata(obj2, &conmode_type);
678 *t = *t2;
679 return obj;
680}
681
682static VALUE
683conmode_set_echo(VALUE obj, VALUE f)
684{
685 conmode *t = rb_check_typeddata(obj, &conmode_type);
686 if (RTEST(f))
687 set_echo(t, NULL);
688 else
689 set_noecho(t, NULL);
690 return obj;
691}
692
693static VALUE
694conmode_set_raw(int argc, VALUE *argv, VALUE obj)
695{
696 conmode *t = rb_check_typeddata(obj, &conmode_type);
697 rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts);
698
699 set_rawmode(t, optp);
700 return obj;
701}
702
703static VALUE
704conmode_raw_new(int argc, VALUE *argv, VALUE obj)
705{
706 conmode *r = rb_check_typeddata(obj, &conmode_type);
707 conmode t = *r;
708 rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts);
709
710 set_rawmode(&t, optp);
711 return conmode_new(rb_obj_class(obj), &t);
712}
713
714/*
715 * call-seq:
716 * io.console_mode -> mode
717 *
718 * Returns a data represents the current console mode.
719 *
720 * You must require 'io/console' to use this method.
721 */
722static VALUE
723console_conmode_get(VALUE io)
724{
725 conmode t;
726 rb_io_t *fptr;
727 int fd;
728
729 GetOpenFile(io, fptr);
730 fd = GetReadFD(fptr);
731 if (!getattr(fd, &t)) sys_fail_fptr(fptr);
732
733 return conmode_new(cConmode, &t);
734}
735
736/*
737 * call-seq:
738 * io.console_mode = mode
739 *
740 * Sets the console mode to +mode+.
741 *
742 * You must require 'io/console' to use this method.
743 */
744static VALUE
745console_conmode_set(VALUE io, VALUE mode)
746{
747 conmode *t, r;
748 rb_io_t *fptr;
749 int fd;
750
751 TypedData_Get_Struct(mode, conmode, &conmode_type, t);
752 r = *t;
753 GetOpenFile(io, fptr);
754 fd = GetReadFD(fptr);
755 if (!setattr(fd, &r)) sys_fail_fptr(fptr);
756
757 return mode;
758}
759
760#if defined TIOCGWINSZ
761typedef struct winsize rb_console_size_t;
762#define getwinsize(fd, buf) (ioctl((fd), TIOCGWINSZ, (buf)) == 0)
763#define setwinsize(fd, buf) (ioctl((fd), TIOCSWINSZ, (buf)) == 0)
764#define winsize_row(buf) (buf)->ws_row
765#define winsize_col(buf) (buf)->ws_col
766#elif defined _WIN32
767typedef CONSOLE_SCREEN_BUFFER_INFO rb_console_size_t;
768#define getwinsize(fd, buf) ( \
769 GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), (buf)) || \
770 SET_LAST_ERROR)
771#define winsize_row(buf) ((buf)->srWindow.Bottom - (buf)->srWindow.Top + 1)
772#define winsize_col(buf) (buf)->dwSize.X
773#endif
774
775#if defined TIOCGWINSZ || defined _WIN32
776#define USE_CONSOLE_GETSIZE 1
777#endif
778
779#ifdef USE_CONSOLE_GETSIZE
780/*
781 * call-seq:
782 * io.winsize -> [rows, columns]
783 *
784 * Returns console size.
785 *
786 * You must require 'io/console' to use this method.
787 */
788static VALUE
789console_winsize(VALUE io)
790{
791 rb_io_t *fptr;
792 int fd;
793 rb_console_size_t ws;
794
795 GetOpenFile(io, fptr);
796 fd = GetWriteFD(fptr);
797 if (!getwinsize(fd, &ws)) sys_fail_fptr(fptr);
798 return rb_assoc_new(INT2NUM(winsize_row(&ws)), INT2NUM(winsize_col(&ws)));
799}
800
801/*
802 * call-seq:
803 * io.winsize = [rows, columns]
804 *
805 * Tries to set console size. The effect depends on the platform and
806 * the running environment.
807 *
808 * You must require 'io/console' to use this method.
809 */
810static VALUE
811console_set_winsize(VALUE io, VALUE size)
812{
813 rb_io_t *fptr;
814 rb_console_size_t ws;
815#if defined _WIN32
816 HANDLE wh;
817 int newrow, newcol;
818 BOOL ret;
819#endif
820 VALUE row, col, xpixel, ypixel;
821 const VALUE *sz;
822 int fd;
823 long sizelen;
824
825 GetOpenFile(io, fptr);
826 size = rb_Array(size);
827 if ((sizelen = RARRAY_LEN(size)) != 2 && sizelen != 4) {
828 rb_raise(rb_eArgError,
829 "wrong number of arguments (given %ld, expected 2 or 4)",
830 sizelen);
831 }
832 sz = RARRAY_CONST_PTR(size);
833 row = sz[0], col = sz[1], xpixel = ypixel = Qnil;
834 if (sizelen == 4) xpixel = sz[2], ypixel = sz[3];
835 fd = GetWriteFD(fptr);
836#if defined TIOCSWINSZ
837 ws.ws_row = ws.ws_col = ws.ws_xpixel = ws.ws_ypixel = 0;
838#define SET(m) ws.ws_##m = NIL_P(m) ? 0 : (unsigned short)NUM2UINT(m)
839 SET(row);
840 SET(col);
841 SET(xpixel);
842 SET(ypixel);
843#undef SET
844 if (!setwinsize(fd, &ws)) sys_fail_fptr(fptr);
845#elif defined _WIN32
846 wh = (HANDLE)rb_w32_get_osfhandle(fd);
847#define SET(m) new##m = NIL_P(m) ? 0 : (unsigned short)NUM2UINT(m)
848 SET(row);
849 SET(col);
850#undef SET
851 if (!NIL_P(xpixel)) (void)NUM2UINT(xpixel);
852 if (!NIL_P(ypixel)) (void)NUM2UINT(ypixel);
853 if (!GetConsoleScreenBufferInfo(wh, &ws)) {
854 rb_syserr_fail(LAST_ERROR, "GetConsoleScreenBufferInfo");
855 }
856 ws.dwSize.X = newcol;
857 ret = SetConsoleScreenBufferSize(wh, ws.dwSize);
858 ws.srWindow.Left = 0;
859 ws.srWindow.Top = 0;
860 ws.srWindow.Right = newcol-1;
861 ws.srWindow.Bottom = newrow-1;
862 if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) {
863 rb_syserr_fail(LAST_ERROR, "SetConsoleWindowInfo");
864 }
865 /* retry when shrinking buffer after shrunk window */
866 if (!ret && !SetConsoleScreenBufferSize(wh, ws.dwSize)) {
867 rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo");
868 }
869 /* remove scrollbar if possible */
870 if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) {
871 rb_syserr_fail(LAST_ERROR, "SetConsoleWindowInfo");
872 }
873#endif
874 return io;
875}
876#endif
877
878#ifdef _WIN32
879static VALUE
880 console_check_winsize_changed(VALUE io)
881{
882 rb_io_t *fptr;
883 HANDLE h;
884 DWORD num;
885
886 GetOpenFile(io, fptr);
887 h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(fptr));
888 while (GetNumberOfConsoleInputEvents(h, &num) && num > 0) {
889 INPUT_RECORD rec;
890 if (ReadConsoleInput(h, &rec, 1, &num)) {
891 if (rec.EventType == WINDOW_BUFFER_SIZE_EVENT) {
892 rb_yield(Qnil);
893 }
894 }
895 }
896 return io;
897}
898#else
899#define console_check_winsize_changed rb_f_notimplement
900#endif
901
902/*
903 * call-seq:
904 * io.iflush
905 *
906 * Flushes input buffer in kernel.
907 *
908 * You must require 'io/console' to use this method.
909 */
910static VALUE
911console_iflush(VALUE io)
912{
913 rb_io_t *fptr;
914 int fd;
915
916 GetOpenFile(io, fptr);
917 fd = GetReadFD(fptr);
918#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H
919 if (tcflush(fd, TCIFLUSH)) sys_fail_fptr(fptr);
920#endif
921 (void)fd;
922 return io;
923}
924
925/*
926 * call-seq:
927 * io.oflush
928 *
929 * Flushes output buffer in kernel.
930 *
931 * You must require 'io/console' to use this method.
932 */
933static VALUE
934console_oflush(VALUE io)
935{
936 rb_io_t *fptr;
937 int fd;
938
939 GetOpenFile(io, fptr);
940 fd = GetWriteFD(fptr);
941#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H
942 if (tcflush(fd, TCOFLUSH)) sys_fail_fptr(fptr);
943#endif
944 (void)fd;
945 return io;
946}
947
948/*
949 * call-seq:
950 * io.ioflush
951 *
952 * Flushes input and output buffers in kernel.
953 *
954 * You must require 'io/console' to use this method.
955 */
956static VALUE
957console_ioflush(VALUE io)
958{
959 rb_io_t *fptr;
960#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H
961 int fd1, fd2;
962#endif
963
964 GetOpenFile(io, fptr);
965#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H
966 fd1 = GetReadFD(fptr);
967 fd2 = GetWriteFD(fptr);
968 if (fd2 != -1 && fd1 != fd2) {
969 if (tcflush(fd1, TCIFLUSH)) sys_fail_fptr(fptr);
970 if (tcflush(fd2, TCOFLUSH)) sys_fail_fptr(fptr);
971 }
972 else {
973 if (tcflush(fd1, TCIOFLUSH)) sys_fail_fptr(fptr);
974 }
975#endif
976 return io;
977}
978
979static VALUE
980console_beep(VALUE io)
981{
982 rb_io_t *fptr;
983 int fd;
984
985 GetOpenFile(io, fptr);
986 fd = GetWriteFD(fptr);
987#ifdef _WIN32
988 (void)fd;
989 MessageBeep(0);
990#else
991 if (write(fd, "\a", 1) < 0)
992 sys_fail_fptr(fptr);
993#endif
994 return io;
995}
996
997static int
998mode_in_range(VALUE val, int high, const char *modename)
999{
1000 int mode;
1001 if (NIL_P(val)) return 0;
1002 if (!RB_INTEGER_TYPE_P(val)) {
1003 wrong_value:
1004 rb_raise(rb_eArgError, "wrong %s mode: %"PRIsVALUE, modename, val);
1005 }
1006 if ((mode = NUM2INT(val)) < 0 || mode > high) {
1007 goto wrong_value;
1008 }
1009 return mode;
1010}
1011
1012#if defined _WIN32
1013static VALUE
1014console_goto(VALUE io, VALUE y, VALUE x)
1015{
1016 rb_io_t *fptr;
1017 int fd;
1018 COORD pos;
1019
1020 GetOpenFile(io, fptr);
1021 fd = GetWriteFD(fptr);
1022 pos.X = NUM2UINT(x);
1023 pos.Y = NUM2UINT(y);
1024 if (!SetConsoleCursorPosition((HANDLE)rb_w32_get_osfhandle(fd), pos)) {
1025 rb_syserr_fail(LAST_ERROR, 0);
1026 }
1027 return io;
1028}
1029
1030static VALUE
1031console_cursor_pos(VALUE io)
1032{
1033 rb_io_t *fptr;
1034 int fd;
1035 rb_console_size_t ws;
1036
1037 GetOpenFile(io, fptr);
1038 fd = GetWriteFD(fptr);
1039 if (!GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), &ws)) {
1040 rb_syserr_fail(LAST_ERROR, 0);
1041 }
1042 return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y), UINT2NUM(ws.dwCursorPosition.X));
1043}
1044
1045static VALUE
1046console_move(VALUE io, int y, int x)
1047{
1048 rb_io_t *fptr;
1049 HANDLE h;
1050 rb_console_size_t ws;
1051 COORD *pos = &ws.dwCursorPosition;
1052
1053 GetOpenFile(io, fptr);
1054 h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr));
1055 if (!GetConsoleScreenBufferInfo(h, &ws)) {
1056 rb_syserr_fail(LAST_ERROR, 0);
1057 }
1058 pos->X += x;
1059 pos->Y += y;
1060 if (!SetConsoleCursorPosition(h, *pos)) {
1061 rb_syserr_fail(LAST_ERROR, 0);
1062 }
1063 return io;
1064}
1065
1066static VALUE
1067console_goto_column(VALUE io, VALUE val)
1068{
1069 rb_io_t *fptr;
1070 HANDLE h;
1071 rb_console_size_t ws;
1072 COORD *pos = &ws.dwCursorPosition;
1073
1074 GetOpenFile(io, fptr);
1075 h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr));
1076 if (!GetConsoleScreenBufferInfo(h, &ws)) {
1077 rb_syserr_fail(LAST_ERROR, 0);
1078 }
1079 pos->X = NUM2INT(val);
1080 if (!SetConsoleCursorPosition(h, *pos)) {
1081 rb_syserr_fail(LAST_ERROR, 0);
1082 }
1083 return io;
1084}
1085
1086static void
1087constat_clear(HANDLE handle, WORD attr, DWORD len, COORD pos)
1088{
1089 DWORD written;
1090
1091 FillConsoleOutputAttribute(handle, attr, len, pos, &written);
1092 FillConsoleOutputCharacterW(handle, L' ', len, pos, &written);
1093}
1094
1095static VALUE
1096console_erase_line(VALUE io, VALUE val)
1097{
1098 rb_io_t *fptr;
1099 HANDLE h;
1100 rb_console_size_t ws;
1101 COORD *pos = &ws.dwCursorPosition;
1102 DWORD w;
1103 int mode = mode_in_range(val, 2, "line erase");
1104
1105 GetOpenFile(io, fptr);
1106 h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr));
1107 if (!GetConsoleScreenBufferInfo(h, &ws)) {
1108 rb_syserr_fail(LAST_ERROR, 0);
1109 }
1110 w = winsize_col(&ws);
1111 switch (mode) {
1112 case 0: /* after cursor */
1113 w -= pos->X;
1114 break;
1115 case 1: /* before *and* cursor */
1116 w = pos->X + 1;
1117 pos->X = 0;
1118 break;
1119 case 2: /* entire line */
1120 pos->X = 0;
1121 break;
1122 }
1123 constat_clear(h, ws.wAttributes, w, *pos);
1124 return io;
1125}
1126
1127static VALUE
1128console_erase_screen(VALUE io, VALUE val)
1129{
1130 rb_io_t *fptr;
1131 HANDLE h;
1132 rb_console_size_t ws;
1133 COORD *pos = &ws.dwCursorPosition;
1134 DWORD w;
1135 int mode = mode_in_range(val, 3, "screen erase");
1136
1137 GetOpenFile(io, fptr);
1138 h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr));
1139 if (!GetConsoleScreenBufferInfo(h, &ws)) {
1140 rb_syserr_fail(LAST_ERROR, 0);
1141 }
1142 w = winsize_col(&ws);
1143 switch (mode) {
1144 case 0: /* erase after cursor */
1145 w = (w * (ws.srWindow.Bottom - pos->Y + 1) - pos->X);
1146 break;
1147 case 1: /* erase before *and* cursor */
1148 w = (w * (pos->Y - ws.srWindow.Top) + pos->X + 1);
1149 pos->X = 0;
1150 pos->Y = ws.srWindow.Top;
1151 break;
1152 case 2: /* erase entire screen */
1153 w = (w * winsize_row(&ws));
1154 pos->X = 0;
1155 pos->Y = ws.srWindow.Top;
1156 break;
1157 case 3: /* erase entire screen */
1158 w = (w * ws.dwSize.Y);
1159 pos->X = 0;
1160 pos->Y = 0;
1161 break;
1162 }
1163 constat_clear(h, ws.wAttributes, w, *pos);
1164 return io;
1165}
1166
1167static VALUE
1168console_scroll(VALUE io, int line)
1169{
1170 rb_io_t *fptr;
1171 HANDLE h;
1172 rb_console_size_t ws;
1173
1174 GetOpenFile(io, fptr);
1175 h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr));
1176 if (!GetConsoleScreenBufferInfo(h, &ws)) {
1177 rb_syserr_fail(LAST_ERROR, 0);
1178 }
1179 if (line) {
1180 SMALL_RECT scroll;
1181 COORD destination;
1182 CHAR_INFO fill;
1183 scroll.Left = 0;
1184 scroll.Top = line > 0 ? line : 0;
1185 scroll.Right = winsize_col(&ws) - 1;
1186 scroll.Bottom = winsize_row(&ws) - 1 + (line < 0 ? line : 0);
1187 destination.X = 0;
1188 destination.Y = line < 0 ? -line : 0;
1189 fill.Char.UnicodeChar = L' ';
1190 fill.Attributes = ws.wAttributes;
1191
1192 ScrollConsoleScreenBuffer(h, &scroll, NULL, destination, &fill);
1193 }
1194 return io;
1195}
1196
1197#include "win32_vk.inc"
1198
1199static VALUE
1200 console_key_pressed_p(VALUE io, VALUE k)
1201{
1202 int vk = -1;
1203
1204 if (FIXNUM_P(k)) {
1205 vk = NUM2UINT(k);
1206 }
1207 else {
1208 const struct vktable *t;
1209 const char *kn;
1210 if (SYMBOL_P(k)) {
1211 k = rb_sym2str(k);
1212 kn = RSTRING_PTR(k);
1213 }
1214 else {
1215 kn = StringValuePtr(k);
1216 }
1217 t = console_win32_vk(kn, RSTRING_LEN(k));
1218 if (!t || (vk = (short)t->vk) == -1) {
1219 rb_raise(rb_eArgError, "unknown virtual key code: % "PRIsVALUE, k);
1220 }
1221 }
1222 return GetKeyState(vk) & 0x80 ? Qtrue : Qfalse;
1223}
1224#else
1225 struct query_args {
1226 char qstr[6];
1227 unsigned char opt;
1228};
1229
1230static int
1231direct_query(VALUE io, const struct query_args *query)
1232{
1233 if (RB_TYPE_P(io, T_FILE)) {
1234 rb_io_t *fptr;
1235 VALUE wio;
1236 GetOpenFile(io, fptr);
1237 wio = fptr->tied_io_for_writing;
1238 if (wio) {
1239 VALUE s = rb_str_new_cstr(query->qstr);
1240 rb_io_write(wio, s);
1241 rb_io_flush(wio);
1242 return 1;
1243 }
1244 if (write(fptr->fd, query->qstr, strlen(query->qstr)) != -1) {
1245 return 1;
1246 }
1247 if (fptr->fd == 0 &&
1248 write(1, query->qstr, strlen(query->qstr)) != -1) {
1249 return 1;
1250 }
1251 }
1252 return 0;
1253}
1254
1255static VALUE
1256read_vt_response(VALUE io, VALUE query)
1257{
1258 struct query_args *qargs = (struct query_args *)query;
1259 VALUE result, b;
1260 int opt = 0;
1261 int num = 0;
1262 if (qargs) {
1263 opt = qargs->opt;
1264 if (!direct_query(io, qargs)) return Qnil;
1265 }
1266 if (rb_io_getbyte(io) != INT2FIX(0x1b)) return Qnil;
1267 if (rb_io_getbyte(io) != INT2FIX('[')) return Qnil;
1268 result = rb_ary_new();
1269 while (!NIL_P(b = rb_io_getbyte(io))) {
1270 int c = NUM2UINT(b);
1271 if (c == ';') {
1272 rb_ary_push(result, INT2NUM(num));
1273 num = 0;
1274 }
1275 else if (ISDIGIT(c)) {
1276 num = num * 10 + c - '0';
1277 }
1278 else if (opt && c == opt) {
1279 opt = 0;
1280 }
1281 else {
1282 char last = (char)c;
1283 rb_ary_push(result, INT2NUM(num));
1284 b = rb_str_new(&last, 1);
1285 break;
1286 }
1287 }
1288 return rb_ary_push(result, b);
1289}
1290
1291static VALUE
1292console_vt_response(int argc, VALUE *argv, VALUE io, const struct query_args *qargs)
1293{
1294 rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 1, &opts);
1295 VALUE query = (VALUE)qargs;
1296 VALUE ret = ttymode_with_io(io, read_vt_response, query, set_rawmode, optp);
1297 return ret;
1298}
1299
1300static VALUE
1301console_cursor_pos(VALUE io)
1302{
1303 static const struct query_args query = {"\033[6n", 0};
1304 VALUE resp = console_vt_response(0, 0, io, &query);
1305 VALUE row, column, term;
1306 unsigned int r, c;
1307 if (!RB_TYPE_P(resp, T_ARRAY) || RARRAY_LEN(resp) != 3) return Qnil;
1308 term = RARRAY_AREF(resp, 2);
1309 if (!RB_TYPE_P(term, T_STRING) || RSTRING_LEN(term) != 1) return Qnil;
1310 if (RSTRING_PTR(term)[0] != 'R') return Qnil;
1311 row = RARRAY_AREF(resp, 0);
1312 column = RARRAY_AREF(resp, 1);
1313 rb_ary_resize(resp, 2);
1314 r = NUM2UINT(row) - 1;
1315 c = NUM2UINT(column) - 1;
1316 RARRAY_ASET(resp, 0, INT2NUM(r));
1317 RARRAY_ASET(resp, 1, INT2NUM(c));
1318 return resp;
1319}
1320
1321static VALUE
1322console_goto(VALUE io, VALUE y, VALUE x)
1323{
1324 rb_io_write(io, rb_sprintf("\x1b[%d;%dH", NUM2UINT(y)+1, NUM2UINT(x)+1));
1325 return io;
1326}
1327
1328static VALUE
1329console_move(VALUE io, int y, int x)
1330{
1331 if (x || y) {
1332 VALUE s = rb_str_new_cstr("");
1333 if (y) rb_str_catf(s, "\x1b[%d%c", y < 0 ? -y : y, y < 0 ? 'A' : 'B');
1334 if (x) rb_str_catf(s, "\x1b[%d%c", x < 0 ? -x : x, x < 0 ? 'D' : 'C');
1335 rb_io_write(io, s);
1336 rb_io_flush(io);
1337 }
1338 return io;
1339}
1340
1341static VALUE
1342console_goto_column(VALUE io, VALUE val)
1343{
1344 rb_io_write(io, rb_sprintf("\x1b[%dG", NUM2UINT(val)+1));
1345 return io;
1346}
1347
1348static VALUE
1349console_erase_line(VALUE io, VALUE val)
1350{
1351 int mode = mode_in_range(val, 2, "line erase");
1352 rb_io_write(io, rb_sprintf("\x1b[%dK", mode));
1353 return io;
1354}
1355
1356static VALUE
1357console_erase_screen(VALUE io, VALUE val)
1358{
1359 int mode = mode_in_range(val, 3, "screen erase");
1360 rb_io_write(io, rb_sprintf("\x1b[%dJ", mode));
1361 return io;
1362}
1363
1364static VALUE
1365console_scroll(VALUE io, int line)
1366{
1367 if (line) {
1368 VALUE s = rb_sprintf("\x1b[%d%c", line < 0 ? -line : line,
1369 line < 0 ? 'T' : 'S');
1370 rb_io_write(io, s);
1371 }
1372 return io;
1373}
1374# define console_key_pressed_p rb_f_notimplement
1375#endif
1376
1377static VALUE
1378console_cursor_set(VALUE io, VALUE cpos)
1379{
1380 cpos = rb_convert_type(cpos, T_ARRAY, "Array", "to_ary");
1381 if (RARRAY_LEN(cpos) != 2) rb_raise(rb_eArgError, "expected 2D coordinate");
1382 return console_goto(io, RARRAY_AREF(cpos, 0), RARRAY_AREF(cpos, 1));
1383}
1384
1385static VALUE
1386console_cursor_up(VALUE io, VALUE val)
1387{
1388 return console_move(io, -NUM2INT(val), 0);
1389}
1390
1391static VALUE
1392console_cursor_down(VALUE io, VALUE val)
1393{
1394 return console_move(io, +NUM2INT(val), 0);
1395}
1396
1397static VALUE
1398console_cursor_left(VALUE io, VALUE val)
1399{
1400 return console_move(io, 0, -NUM2INT(val));
1401}
1402
1403static VALUE
1404console_cursor_right(VALUE io, VALUE val)
1405{
1406 return console_move(io, 0, +NUM2INT(val));
1407}
1408
1409static VALUE
1410console_scroll_forward(VALUE io, VALUE val)
1411{
1412 return console_scroll(io, +NUM2INT(val));
1413}
1414
1415static VALUE
1416console_scroll_backward(VALUE io, VALUE val)
1417{
1418 return console_scroll(io, -NUM2INT(val));
1419}
1420
1421static VALUE
1422console_clear_screen(VALUE io)
1423{
1424 console_erase_screen(io, INT2FIX(2));
1425 console_goto(io, INT2FIX(0), INT2FIX(0));
1426 return io;
1427}
1428
1429/*
1430 * call-seq:
1431 * IO.console -> #<File:/dev/tty>
1432 * IO.console(sym, *args)
1433 *
1434 * Returns an File instance opened console.
1435 *
1436 * If +sym+ is given, it will be sent to the opened console with
1437 * +args+ and the result will be returned instead of the console IO
1438 * itself.
1439 *
1440 * You must require 'io/console' to use this method.
1441 */
1442static VALUE
1443console_dev(int argc, VALUE *argv, VALUE klass)
1444{
1445 VALUE con = 0;
1446 rb_io_t *fptr;
1447 VALUE sym = 0;
1448
1449 rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS);
1450 if (argc) {
1451 Check_Type(sym = argv[0], T_SYMBOL);
1452 }
1453 if (klass == rb_cIO) klass = rb_cFile;
1454 if (rb_const_defined(klass, id_console)) {
1455 con = rb_const_get(klass, id_console);
1456 if (!RB_TYPE_P(con, T_FILE) ||
1457 (!(fptr = RFILE(con)->fptr) || GetReadFD(fptr) == -1)) {
1458 rb_const_remove(klass, id_console);
1459 con = 0;
1460 }
1461 }
1462 if (sym) {
1463 if (sym == ID2SYM(id_close) && argc == 1) {
1464 if (con) {
1465 rb_io_close(con);
1466 rb_const_remove(klass, id_console);
1467 con = 0;
1468 }
1469 return Qnil;
1470 }
1471 }
1472 if (!con) {
1473 VALUE args[2];
1474#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H || defined HAVE_SGTTY_H
1475# define CONSOLE_DEVICE "/dev/tty"
1476#elif defined _WIN32
1477# define CONSOLE_DEVICE "con$"
1478# define CONSOLE_DEVICE_FOR_READING "conin$"
1479# define CONSOLE_DEVICE_FOR_WRITING "conout$"
1480#endif
1481#ifndef CONSOLE_DEVICE_FOR_READING
1482# define CONSOLE_DEVICE_FOR_READING CONSOLE_DEVICE
1483#endif
1484#ifdef CONSOLE_DEVICE_FOR_WRITING
1485 VALUE out;
1486 rb_io_t *ofptr;
1487#endif
1488 int fd;
1489
1490#ifdef CONSOLE_DEVICE_FOR_WRITING
1491 fd = rb_cloexec_open(CONSOLE_DEVICE_FOR_WRITING, O_RDWR, 0);
1492 if (fd < 0) return Qnil;
1493 rb_update_max_fd(fd);
1494 args[1] = INT2FIX(O_WRONLY);
1495 args[0] = INT2NUM(fd);
1496 out = rb_class_new_instance(2, args, klass);
1497#endif
1498 fd = rb_cloexec_open(CONSOLE_DEVICE_FOR_READING, O_RDWR, 0);
1499 if (fd < 0) {
1500#ifdef CONSOLE_DEVICE_FOR_WRITING
1501 rb_io_close(out);
1502#endif
1503 return Qnil;
1504 }
1505 rb_update_max_fd(fd);
1506 args[1] = INT2FIX(O_RDWR);
1507 args[0] = INT2NUM(fd);
1508 con = rb_class_new_instance(2, args, klass);
1509 GetOpenFile(con, fptr);
1510 fptr->pathv = rb_obj_freeze(rb_str_new2(CONSOLE_DEVICE));
1511#ifdef CONSOLE_DEVICE_FOR_WRITING
1512 GetOpenFile(out, ofptr);
1513 ofptr->pathv = fptr->pathv;
1514 fptr->tied_io_for_writing = out;
1515 ofptr->mode |= FMODE_SYNC;
1516#endif
1517 fptr->mode |= FMODE_SYNC;
1518 rb_const_set(klass, id_console, con);
1519 }
1520 if (sym) {
1521 return rb_f_send(argc, argv, con);
1522 }
1523 return con;
1524}
1525
1526/*
1527 * call-seq:
1528 * io.getch(min: nil, time: nil, intr: nil) -> char
1529 *
1530 * See IO#getch.
1531 */
1532static VALUE
1533io_getch(int argc, VALUE *argv, VALUE io)
1534{
1535 return rb_funcallv(io, id_getc, argc, argv);
1536}
1537
1538#if ENABLE_IO_GETPASS
1539static VALUE
1540puts_call(VALUE io)
1541{
1542 return rb_io_write(io, rb_default_rs);
1543}
1544
1545static VALUE
1546getpass_call(VALUE io)
1547{
1548 return ttymode(io, rb_io_gets, io, set_noecho, NULL);
1549}
1550
1551static void
1552prompt(int argc, VALUE *argv, VALUE io)
1553{
1554 if (argc > 0 && !NIL_P(argv[0])) {
1555 VALUE str = argv[0];
1556 StringValueCStr(str);
1557 rb_io_write(io, str);
1558 }
1559}
1560
1561static VALUE
1562str_chomp(VALUE str)
1563{
1564 if (!NIL_P(str)) {
1565 rb_funcallv(str, id_chomp_bang, 0, 0);
1566 }
1567 return str;
1568}
1569
1570/*
1571 * call-seq:
1572 * io.getpass(prompt=nil) -> string
1573 *
1574 * Reads and returns a line without echo back.
1575 * Prints +prompt+ unless it is +nil+.
1576 *
1577 * The newline character that terminates the
1578 * read line is removed from the returned string,
1579 * see String#chomp!.
1580 *
1581 * You must require 'io/console' to use this method.
1582 */
1583static VALUE
1584console_getpass(int argc, VALUE *argv, VALUE io)
1585{
1586 VALUE str, wio;
1587
1588 rb_check_arity(argc, 0, 1);
1589 wio = rb_io_get_write_io(io);
1590 if (wio == io && io == rb_stdin) wio = rb_stderr;
1591 prompt(argc, argv, wio);
1592 str = rb_ensure(getpass_call, io, puts_call, wio);
1593 return str_chomp(str);
1594}
1595
1596/*
1597 * call-seq:
1598 * io.getpass(prompt=nil) -> string
1599 *
1600 * See IO#getpass.
1601 */
1602static VALUE
1603io_getpass(int argc, VALUE *argv, VALUE io)
1604{
1605 VALUE str;
1606
1607 rb_check_arity(argc, 0, 1);
1608 prompt(argc, argv, io);
1609 str = str_chomp(rb_funcallv(io, id_gets, 0, 0));
1610 puts_call(io);
1611 return str;
1612}
1613#endif
1614
1615/*
1616 * IO console methods
1617 */
1618void
1619Init_console(void)
1620{
1621#undef rb_intern
1622 id_getc = rb_intern("getc");
1623#if ENABLE_IO_GETPASS
1624 id_gets = rb_intern("gets");
1625 id_chomp_bang = rb_intern("chomp!");
1626#endif
1627 id_console = rb_intern("console");
1628 id_close = rb_intern("close");
1629 id_min = rb_intern("min");
1630 id_time = rb_intern("time");
1631 id_intr = rb_intern("intr");
1632#ifndef HAVE_RB_F_SEND
1633 id___send__ = rb_intern("__send__");
1634#endif
1635 InitVM(console);
1636}
1637
1638void
1639InitVM_console(void)
1640{
1641 rb_define_method(rb_cIO, "raw", console_raw, -1);
1642 rb_define_method(rb_cIO, "raw!", console_set_raw, -1);
1643 rb_define_method(rb_cIO, "cooked", console_cooked, 0);
1644 rb_define_method(rb_cIO, "cooked!", console_set_cooked, 0);
1645 rb_define_method(rb_cIO, "getch", console_getch, -1);
1646 rb_define_method(rb_cIO, "echo=", console_set_echo, 1);
1647 rb_define_method(rb_cIO, "echo?", console_echo_p, 0);
1648 rb_define_method(rb_cIO, "console_mode", console_conmode_get, 0);
1649 rb_define_method(rb_cIO, "console_mode=", console_conmode_set, 1);
1650 rb_define_method(rb_cIO, "noecho", console_noecho, 0);
1651 rb_define_method(rb_cIO, "winsize", console_winsize, 0);
1652 rb_define_method(rb_cIO, "winsize=", console_set_winsize, 1);
1653 rb_define_method(rb_cIO, "iflush", console_iflush, 0);
1654 rb_define_method(rb_cIO, "oflush", console_oflush, 0);
1655 rb_define_method(rb_cIO, "ioflush", console_ioflush, 0);
1656 rb_define_method(rb_cIO, "beep", console_beep, 0);
1657 rb_define_method(rb_cIO, "goto", console_goto, 2);
1658 rb_define_method(rb_cIO, "cursor", console_cursor_pos, 0);
1659 rb_define_method(rb_cIO, "cursor=", console_cursor_set, 1);
1660 rb_define_method(rb_cIO, "cursor_up", console_cursor_up, 1);
1661 rb_define_method(rb_cIO, "cursor_down", console_cursor_down, 1);
1662 rb_define_method(rb_cIO, "cursor_left", console_cursor_left, 1);
1663 rb_define_method(rb_cIO, "cursor_right", console_cursor_right, 1);
1664 rb_define_method(rb_cIO, "goto_column", console_goto_column, 1);
1665 rb_define_method(rb_cIO, "erase_line", console_erase_line, 1);
1666 rb_define_method(rb_cIO, "erase_screen", console_erase_screen, 1);
1667 rb_define_method(rb_cIO, "scroll_forward", console_scroll_forward, 1);
1668 rb_define_method(rb_cIO, "scroll_backward", console_scroll_backward, 1);
1669 rb_define_method(rb_cIO, "clear_screen", console_clear_screen, 0);
1670 rb_define_method(rb_cIO, "pressed?", console_key_pressed_p, 1);
1671 rb_define_method(rb_cIO, "check_winsize_changed", console_check_winsize_changed, 0);
1672#if ENABLE_IO_GETPASS
1673 rb_define_method(rb_cIO, "getpass", console_getpass, -1);
1674#endif
1675 rb_define_singleton_method(rb_cIO, "console", console_dev, -1);
1676 {
1677 VALUE mReadable = rb_define_module_under(rb_cIO, "generic_readable");
1678 rb_define_method(mReadable, "getch", io_getch, -1);
1679#if ENABLE_IO_GETPASS
1680 rb_define_method(mReadable, "getpass", io_getpass, -1);
1681#endif
1682 }
1683 {
1684 /* :stopdoc: */
1685 cConmode = rb_define_class_under(rb_cIO, "ConsoleMode", rb_cObject);
1686 rb_define_alloc_func(cConmode, conmode_alloc);
1687 rb_undef_method(cConmode, "initialize");
1688 rb_define_method(cConmode, "initialize_copy", conmode_init_copy, 1);
1689 rb_define_method(cConmode, "echo=", conmode_set_echo, 1);
1690 rb_define_method(cConmode, "raw!", conmode_set_raw, -1);
1691 rb_define_method(cConmode, "raw", conmode_raw_new, -1);
1692 /* :startdoc: */
1693 }
1694}
rb_ary_push
VALUE rb_ary_push(VALUE ary, VALUE item)
Definition: array.c:1301
rb_ary_new
VALUE rb_ary_new(void)
Definition: array.c:749
rb_ary_resize
VALUE rb_ary_resize(VALUE ary, long len)
expands or shrinks ary to len elements.
Definition: array.c:2235
rb_assoc_new
VALUE rb_assoc_new(VALUE car, VALUE cdr)
Definition: array.c:975
L
#define L(x)
Definition: asm.h:125
console_key_pressed_p
#define console_key_pressed_p
Definition: console.c:1374
Init_console
void Init_console(void)
Definition: console.c:1619
FD_PER_IO
#define FD_PER_IO
Definition: console.c:284
GetReadFD
#define GetReadFD(fptr)
Definition: console.c:271
sys_fail_fptr
#define sys_fail_fptr(fptr)
Definition: console.c:87
CONSOLE_DEVICE_FOR_READING
#define CONSOLE_DEVICE_FOR_READING
GetWriteFD
#define GetWriteFD(fptr)
Definition: console.c:282
console_check_winsize_changed
#define console_check_winsize_changed
Definition: console.c:899
InitVM_console
void InitVM_console(void)
Definition: console.c:1639
ISDIGIT
#define ISDIGIT
Definition: ctype.h:43
rb_define_method
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
Definition: cxxanyargs.hpp:653
rb_define_singleton_method
#define rb_define_singleton_method(klass, mid, func, arity)
Defines klass.mid.
Definition: cxxanyargs.hpp:668
rb_default_external_encoding
rb_encoding * rb_default_external_encoding(void)
Definition: encoding.c:1647
num
big_t * num
Definition: enough.c:232
out
string_t out
Definition: enough.c:230
sym
#define sym(name)
Definition: enumerator.c:4007
len
uint8_t len
Definition: escape.c:17
str
char str[HTML_ESCAPE_MAX_LEN+1]
Definition: escape.c:18
RSTRING_LEN
#define RSTRING_LEN(string)
Definition: fbuffer.h:22
RSTRING_PTR
#define RSTRING_PTR(string)
Definition: fbuffer.h:19
rb_cFile
VALUE rb_cFile
Definition: file.c:174
PRIsVALUE
#define PRIsVALUE
Definition: function.c:10
rb_data_typed_object_zalloc
VALUE rb_data_typed_object_zalloc(VALUE klass, size_t size, const rb_data_type_t *type)
Definition: gc.c:2513
rb_cIO
VALUE rb_cIO
Definition: io.c:183
rb_stdin
VALUE rb_stdin
Definition: io.c:196
rb_stderr
VALUE rb_stderr
Definition: globals.h:118
rb_define_class_under
VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super)
Defines a class under the namespace of outer.
Definition: class.c:797
rb_define_module_under
VALUE rb_define_module_under(VALUE outer, const char *name)
Definition: class.c:895
rb_extract_keywords
VALUE rb_extract_keywords(VALUE *orighash)
Definition: class.c:2067
rb_undef_method
void rb_undef_method(VALUE klass, const char *name)
Definition: class.c:1777
rb_scan_args
int rb_scan_args(int argc, const VALUE *argv, const char *fmt,...)
Definition: class.c:2296
rb_syserr_fail
void rb_syserr_fail(int e, const char *mesg)
Definition: error.c:3029
rb_raise
void rb_raise(VALUE exc, const char *fmt,...)
Definition: error.c:2917
rb_protect
VALUE rb_protect(VALUE(*proc)(VALUE), VALUE data, int *pstate)
Protects a function call from potential global escapes from the function.
Definition: eval.c:1105
rb_check_typeddata
void * rb_check_typeddata(VALUE obj, const rb_data_type_t *data_type)
Definition: error.c:1024
rb_eArgError
VALUE rb_eArgError
Definition: error.c:1058
rb_ensure
VALUE rb_ensure(VALUE(*b_proc)(VALUE), VALUE data1, VALUE(*e_proc)(VALUE), VALUE data2)
An equivalent to ensure clause.
Definition: eval.c:1148
rb_jump_tag
void rb_jump_tag(int tag)
Continues the exception caught by rb_protect() and rb_eval_string_protect().
Definition: eval.c:921
rb_warning
void rb_warning(const char *fmt,...)
Definition: error.c:439
rb_convert_type
VALUE rb_convert_type(VALUE, int, const char *, const char *)
Converts an object into another type.
Definition: object.c:2930
rb_cObject
VALUE rb_cObject
Object class.
Definition: object.c:49
rb_class_new_instance
VALUE rb_class_new_instance(int, const VALUE *, VALUE)
Allocates and initializes an instance of klass.
Definition: object.c:1953
rb_Array
VALUE rb_Array(VALUE)
Equivalent to Kernel#Array in Ruby.
Definition: object.c:3705
rb_obj_class
VALUE rb_obj_class(VALUE)
Definition: object.c:245
rb_obj_freeze
VALUE rb_obj_freeze(VALUE)
Make the object unmodifiable.
Definition: object.c:1101
rb_check_hash_type
VALUE rb_check_hash_type(VALUE hash)
Definition: hash.c:1860
rb_hash_aref
VALUE rb_hash_aref(VALUE hash, VALUE key)
Definition: hash.c:2046
term
const char term
Definition: id.c:37
rb_str_conv_enc
VALUE rb_str_conv_enc(VALUE str, rb_encoding *from, rb_encoding *to)
Definition: string.c:1100
rb_funcall3
#define rb_funcall3
Definition: eval.h:37
rb_uv_to_utf8
int rb_uv_to_utf8(char[6], unsigned long)
Definition: pack.c:1660
UNLIMITED_ARGUMENTS
#define UNLIMITED_ARGUMENTS
Definition: error.h:29
rb_check_arity
#define rb_check_arity
Definition: error.h:34
rb_io_getbyte
VALUE rb_io_getbyte(VALUE)
Definition: io.c:4331
rb_io_close
VALUE rb_io_close(VALUE)
Definition: io.c:4935
rb_io_gets
VALUE rb_io_gets(VALUE)
Definition: io.c:3738
rb_io_write
VALUE rb_io_write(VALUE, VALUE)
Definition: io.c:1953
rb_update_max_fd
void rb_update_max_fd(int fd)
Definition: io.c:233
rb_cloexec_open
int rb_cloexec_open(const char *pathname, int flags, mode_t mode)
Definition: io.c:307
rb_io_flush
VALUE rb_io_flush(VALUE)
Definition: io.c:2052
rb_default_rs
VALUE rb_default_rs
Definition: io.c:202
rb_str_new2
#define rb_str_new2
Definition: string.h:276
rb_str_new
#define rb_str_new(str, len)
Definition: string.h:213
rb_utf8_str_new
#define rb_utf8_str_new(str, len)
Definition: string.h:230
rb_str_new_cstr
#define rb_str_new_cstr(str)
Definition: string.h:219
RUBY_UBF_IO
#define RUBY_UBF_IO
Definition: thread.h:64
rb_const_get
VALUE rb_const_get(VALUE, ID)
Definition: variable.c:2624
rb_const_set
void rb_const_set(VALUE, ID, VALUE)
Definition: variable.c:3003
rb_const_remove
VALUE rb_const_remove(VALUE, ID)
Definition: variable.c:2727
rb_const_defined
int rb_const_defined(VALUE, ID)
Definition: variable.c:2928
rb_define_alloc_func
void rb_define_alloc_func(VALUE, rb_alloc_func_t)
ID2SYM
#define ID2SYM
Definition: symbol.h:44
rb_sym2str
VALUE rb_sym2str(VALUE)
Definition: symbol.c:927
rb_intern
ID rb_intern(const char *)
Definition: symbol.c:785
rb_check_id
ID rb_check_id(volatile VALUE *)
Returns ID for the given name if it is interned already, or 0.
Definition: symbol.c:1069
io.h
GetOpenFile
#define GetOpenFile
Definition: io.h:125
rb_wait_for_single_fd
int rb_wait_for_single_fd(int fd, int events, struct timeval *tv)
Definition: io.c:1384
RB_WAITFD_IN
#define RB_WAITFD_IN
Definition: io.h:39
FMODE_SYNC
#define FMODE_SYNC
Definition: io.h:109
rb_io_get_write_io
VALUE rb_io_get_write_io(VALUE io)
Definition: io.c:802
rb_eof_error
void rb_eof_error(void)
Definition: io.c:754
RUBY_IO_READABLE
@ RUBY_IO_READABLE
Definition: io.h:45
rb_io_wait
VALUE rb_io_wait(VALUE io, VALUE events, VALUE timeout)
Definition: io.c:1265
thread.h
rb_thread_call_without_gvl
void * rb_thread_call_without_gvl(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void *data2)
NUM2UINT
#define NUM2UINT
Definition: int.h:45
NUM2INT
#define NUM2INT
Definition: int.h:44
INT2NUM
#define INT2NUM
Definition: int.h:43
UINT2NUM
#define UINT2NUM
Definition: int.h:46
rb_funcallv
#define rb_funcallv(...)
Definition: internal.h:77
size
voidpf void uLong size
Definition: ioapi.h:138
int
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
mode
const char int mode
Definition: ioapi.h:137
buf
voidpf void * buf
Definition: ioapi.h:138
rb_yield
VALUE rb_yield(VALUE)
Definition: vm_eval.c:1341
CHAR_BIT
#define CHAR_BIT
Definition: limits.h:44
INT2FIX
#define INT2FIX
Definition: long.h:48
SET
#define SET(a, b, c, d, k, s, Ti)
last
unsigned int last
Definition: nkf.c:4324
TRUE
#define TRUE
Definition: nkf.h:175
RARRAY_CONST_PTR
#define RARRAY_CONST_PTR(s)
Definition: psych_emitter.c:4
RARRAY_AREF
#define RARRAY_AREF(a, i)
Definition: psych_emitter.c:7
id_min
#define id_min
Definition: range.c:35
RARRAY_LEN
#define RARRAY_LEN
Definition: rarray.h:52
DATA_PTR
#define DATA_PTR(obj)
Definition: rdata.h:56
NULL
#define NULL
Definition: regenc.h:69
RFILE
#define RFILE(obj)
Definition: rfile.h:35
StringValuePtr
#define StringValuePtr(v)
Definition: rstring.h:51
StringValueCStr
#define StringValueCStr(v)
Definition: rstring.h:52
RUBY_TYPED_DEFAULT_FREE
#define RUBY_TYPED_DEFAULT_FREE
Definition: rtypeddata.h:44
TypedData_Get_Struct
#define TypedData_Get_Struct(obj, type, data_type, sval)
Definition: rtypeddata.h:130
RUBY_TYPED_FREE_IMMEDIATELY
@ RUBY_TYPED_FREE_IMMEDIATELY
Definition: rtypeddata.h:62
RUBY_TYPED_WB_PROTECTED
@ RUBY_TYPED_WB_PROTECTED
Definition: rtypeddata.h:64
ruby.h
InitVM
#define InitVM(ext)
Definition: ruby.h:112
argc
int argc
Definition: ruby.c:240
argv
char ** argv
Definition: ruby.c:241
RB_INTEGER_TYPE_P
#define RB_INTEGER_TYPE_P(obj)
Definition: ruby_missing.h:15
rb_scheduler_timeout
VALUE rb_scheduler_timeout(struct timeval *timeout)
Definition: scheduler.c:126
Qtrue
#define Qtrue
Definition: special_consts.h:52
RTEST
#define RTEST
Definition: special_consts.h:42
Qnil
#define Qnil
Definition: special_consts.h:51
Qfalse
#define Qfalse
Definition: special_consts.h:50
NIL_P
#define NIL_P
Definition: special_consts.h:46
FIXNUM_P
#define FIXNUM_P
Definition: special_consts.h:44
f
#define f
rb_str_catf
VALUE rb_str_catf(VALUE, const char *,...)
Definition: sprintf.c:1243
rb_sprintf
VALUE rb_sprintf(const char *,...)
Definition: sprintf.c:1203
strlen
size_t strlen(const char *)
query_args
Definition: console.c:1225
query_args::opt
unsigned char opt
Definition: console.c:1227
query_args::qstr
char qstr[6]
Definition: console.c:1226
rawmode_arg_t
Definition: console.c:108
rawmode_arg_t::vmin
int vmin
Definition: console.c:109
rawmode_arg_t::vtime
int vtime
Definition: console.c:110
rawmode_arg_t::intr
int intr
Definition: console.c:111
rb_data_type_struct
Definition: rtypeddata.h:70
rb_io_t
Definition: io.h:61
rb_io_t::fd
int fd
Definition: io.h:65
rb_io_t::pathv
VALUE pathv
Definition: io.h:69
rb_io_t::mode
int mode
Definition: io.h:66
rb_io_t::tied_io_for_writing
VALUE tied_io_for_writing
Definition: io.h:74
timeval
Definition: missing.h:51
timeval::tv_sec
time_t tv_sec
Definition: missing.h:52
ttymode_callback_args
Definition: console.c:343
ttymode_callback_args::func
VALUE(* func)(VALUE, VALUE)
Definition: console.c:344
ttymode_callback_args::farg
VALUE farg
Definition: console.c:346
ttymode_callback_args::io
VALUE io
Definition: console.c:345
t
#define t
Definition: symbol.c:253
error
void error(const char *msg)
Definition: untgz.c:593
VALUE
unsigned long VALUE
Definition: value.h:38
ID
unsigned long ID
Definition: value.h:39
T_FILE
#define T_FILE
Definition: value_type.h:61
T_STRING
#define T_STRING
Definition: value_type.h:77
T_ARRAY
#define T_ARRAY
Definition: value_type.h:55
T_SYMBOL
#define T_SYMBOL
Definition: value_type.h:79
SYMBOL_P
#define SYMBOL_P
Definition: value_type.h:87
rb_w32_get_osfhandle
SOCKET rb_w32_get_osfhandle(int)
Definition: win32.c:1115
DWORD
IUnknown DWORD
Definition: win32ole.c:33
write
int write(ozstream &zs, const T *x, Items items)
Definition: zstream.h:264