io-console 0.4.6 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@
4
4
  */
5
5
  #include "ruby.h"
6
6
  #include "ruby/io.h"
7
+ #include "ruby/thread.h"
7
8
 
8
9
  #ifdef HAVE_UNISTD_H
9
10
  #include <unistd.h>
@@ -14,12 +15,6 @@
14
15
  #ifdef HAVE_SYS_IOCTL_H
15
16
  #include <sys/ioctl.h>
16
17
  #endif
17
- #ifndef RARRAY_CONST_PTR
18
- # define RARRAY_CONST_PTR(ary) RARRAY_PTR(ary)
19
- #endif
20
- #ifndef HAVE_RB_FUNCALLV
21
- # define rb_funcallv rb_funcall2
22
- #endif
23
18
 
24
19
  #if defined HAVE_TERMIOS_H
25
20
  # include <termios.h>
@@ -28,7 +23,7 @@ typedef struct termios conmode;
28
23
  static int
29
24
  setattr(int fd, conmode *t)
30
25
  {
31
- while (tcsetattr(fd, TCSAFLUSH, t)) {
26
+ while (tcsetattr(fd, TCSANOW, t)) {
32
27
  if (errno != EINTR) return 0;
33
28
  }
34
29
  return 1;
@@ -54,6 +49,7 @@ typedef struct sgttyb conmode;
54
49
  # endif
55
50
  #elif defined _WIN32
56
51
  #include <winioctl.h>
52
+ #include <conio.h>
57
53
  typedef DWORD conmode;
58
54
 
59
55
  #define LAST_ERROR rb_w32_map_errno(GetLastError())
@@ -79,7 +75,7 @@ getattr(int fd, conmode *t)
79
75
  #define SET_LAST_ERROR (0)
80
76
  #endif
81
77
 
82
- static ID id_getc, id_console, id_close, id_min, id_time;
78
+ static ID id_getc, id_console, id_close, id_min, id_time, id_intr;
83
79
  #if ENABLE_IO_GETPASS
84
80
  static ID id_gets;
85
81
  #endif
@@ -103,27 +99,40 @@ rb_f_send(int argc, VALUE *argv, VALUE recv)
103
99
  }
104
100
  #endif
105
101
 
106
- #ifndef HAVE_RB_SYM2STR
107
- # define rb_sym2str(sym) rb_id2str(SYM2ID(sym))
108
- #endif
109
-
110
102
  typedef struct {
111
103
  int vmin;
112
104
  int vtime;
105
+ int intr;
113
106
  } rawmode_arg_t;
114
107
 
115
108
  static rawmode_arg_t *
116
- rawmode_opt(int argc, VALUE *argv, rawmode_arg_t *opts)
109
+ rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t *opts)
117
110
  {
111
+ int argc = *argcp;
118
112
  rawmode_arg_t *optp = NULL;
119
- VALUE vopts;
120
- rb_scan_args(argc, argv, "0:", &vopts);
113
+ VALUE vopts = Qnil;
114
+ #ifdef RB_SCAN_ARGS_PASS_CALLED_KEYWORDS
115
+ argc = rb_scan_args(argc, argv, "*:", NULL, &vopts);
116
+ #else
117
+ if (argc > min_argc) {
118
+ vopts = rb_check_hash_type(argv[argc-1]);
119
+ if (!NIL_P(vopts)) {
120
+ argv[argc-1] = vopts;
121
+ vopts = rb_extract_keywords(&argv[argc-1]);
122
+ if (!argv[argc-1]) *argcp = --argc;
123
+ if (!vopts) vopts = Qnil;
124
+ }
125
+ }
126
+ #endif
127
+ rb_check_arity(argc, min_argc, max_argc);
121
128
  if (!NIL_P(vopts)) {
122
129
  VALUE vmin = rb_hash_aref(vopts, ID2SYM(id_min));
123
130
  VALUE vtime = rb_hash_aref(vopts, ID2SYM(id_time));
131
+ VALUE intr = rb_hash_aref(vopts, ID2SYM(id_intr));
124
132
  /* default values by `stty raw` */
125
133
  opts->vmin = 1;
126
134
  opts->vtime = 0;
135
+ opts->intr = 0;
127
136
  if (!NIL_P(vmin)) {
128
137
  opts->vmin = NUM2INT(vmin);
129
138
  optp = opts;
@@ -134,6 +143,21 @@ rawmode_opt(int argc, VALUE *argv, rawmode_arg_t *opts)
134
143
  opts->vtime = NUM2INT(vtime);
135
144
  optp = opts;
136
145
  }
146
+ switch (intr) {
147
+ case Qtrue:
148
+ opts->intr = 1;
149
+ optp = opts;
150
+ break;
151
+ case Qfalse:
152
+ opts->intr = 0;
153
+ optp = opts;
154
+ break;
155
+ case Qnil:
156
+ break;
157
+ default:
158
+ rb_raise(rb_eArgError, "true or false expected as intr: %"PRIsVALUE,
159
+ intr);
160
+ }
137
161
  }
138
162
  return optp;
139
163
  }
@@ -145,24 +169,36 @@ set_rawmode(conmode *t, void *arg)
145
169
  cfmakeraw(t);
146
170
  t->c_lflag &= ~(ECHOE|ECHOK);
147
171
  #elif defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H
148
- t->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
172
+ t->c_iflag &= ~(IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF|IXANY|IMAXBEL);
149
173
  t->c_oflag &= ~OPOST;
150
- t->c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON|ISIG|IEXTEN);
174
+ t->c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON|ISIG|IEXTEN|XCASE);
151
175
  t->c_cflag &= ~(CSIZE|PARENB);
152
176
  t->c_cflag |= CS8;
177
+ t->c_cc[VMIN] = 1;
178
+ t->c_cc[VTIME] = 0;
153
179
  #elif defined HAVE_SGTTY_H
154
180
  t->sg_flags &= ~ECHO;
155
181
  t->sg_flags |= RAW;
156
182
  #elif defined _WIN32
157
183
  *t = 0;
158
184
  #endif
159
- #if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H
160
185
  if (arg) {
161
186
  const rawmode_arg_t *r = arg;
187
+ #ifdef VMIN
162
188
  if (r->vmin >= 0) t->c_cc[VMIN] = r->vmin;
189
+ #endif
190
+ #ifdef VTIME
163
191
  if (r->vtime >= 0) t->c_cc[VTIME] = r->vtime;
164
- }
165
192
  #endif
193
+ #ifdef ISIG
194
+ if (r->intr) {
195
+ t->c_iflag |= BRKINT;
196
+ t->c_lflag |= ISIG;
197
+ t->c_oflag |= OPOST;
198
+ }
199
+ #endif
200
+ (void)r;
201
+ }
166
202
  }
167
203
 
168
204
  static void
@@ -242,7 +278,7 @@ get_write_fd(const rb_io_t *fptr)
242
278
  #define FD_PER_IO 2
243
279
 
244
280
  static VALUE
245
- ttymode(VALUE io, VALUE (*func)(VALUE), void (*setter)(conmode *, void *), void *arg)
281
+ ttymode(VALUE io, VALUE (*func)(VALUE), VALUE farg, void (*setter)(conmode *, void *), void *arg)
246
282
  {
247
283
  rb_io_t *fptr;
248
284
  int status = -1;
@@ -273,7 +309,7 @@ ttymode(VALUE io, VALUE (*func)(VALUE), void (*setter)(conmode *, void *), void
273
309
  }
274
310
  }
275
311
  if (status == 0) {
276
- result = rb_protect(func, io, &status);
312
+ result = rb_protect(func, farg, &status);
277
313
  }
278
314
  GetOpenFile(io, fptr);
279
315
  if (fd[0] != -1 && fd[0] == GetReadFD(fptr)) {
@@ -297,32 +333,70 @@ ttymode(VALUE io, VALUE (*func)(VALUE), void (*setter)(conmode *, void *), void
297
333
  return result;
298
334
  }
299
335
 
336
+ #if !defined _WIN32
337
+ struct ttymode_callback_args {
338
+ VALUE (*func)(VALUE, VALUE);
339
+ VALUE io;
340
+ VALUE farg;
341
+ };
342
+
343
+ static VALUE
344
+ ttymode_callback(VALUE args)
345
+ {
346
+ struct ttymode_callback_args *argp = (struct ttymode_callback_args *)args;
347
+ return argp->func(argp->io, argp->farg);
348
+ }
349
+
350
+ static VALUE
351
+ ttymode_with_io(VALUE io, VALUE (*func)(VALUE, VALUE), VALUE farg, void (*setter)(conmode *, void *), void *arg)
352
+ {
353
+ struct ttymode_callback_args cargs;
354
+ cargs.func = func;
355
+ cargs.io = io;
356
+ cargs.farg = farg;
357
+ return ttymode(io, ttymode_callback, (VALUE)&cargs, setter, arg);
358
+ }
359
+ #endif
360
+
300
361
  /*
301
362
  * call-seq:
302
- * io.raw(min: nil, time: nil) {|io| }
363
+ * io.raw(min: nil, time: nil, intr: nil) {|io| }
303
364
  *
304
- * Yields +self+ within raw mode.
365
+ * Yields +self+ within raw mode, and returns the result of the block.
305
366
  *
306
367
  * STDIN.raw(&:gets)
307
368
  *
308
369
  * will read and return a line without echo back and line editing.
309
370
  *
371
+ * The parameter +min+ specifies the minimum number of bytes that
372
+ * should be received when a read operation is performed. (default: 1)
373
+ *
374
+ * The parameter +time+ specifies the timeout in _seconds_ with a
375
+ * precision of 1/10 of a second. (default: 0)
376
+ *
377
+ * If the parameter +intr+ is +true+, enables break, interrupt, quit,
378
+ * and suspend special characters.
379
+ *
380
+ * Refer to the manual page of termios for further details.
381
+ *
310
382
  * You must require 'io/console' to use this method.
311
383
  */
312
384
  static VALUE
313
385
  console_raw(int argc, VALUE *argv, VALUE io)
314
386
  {
315
- rawmode_arg_t opts, *optp = rawmode_opt(argc, argv, &opts);
316
- return ttymode(io, rb_yield, set_rawmode, optp);
387
+ rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts);
388
+ return ttymode(io, rb_yield, io, set_rawmode, optp);
317
389
  }
318
390
 
319
391
  /*
320
392
  * call-seq:
321
- * io.raw!(min: nil, time: nil)
393
+ * io.raw!(min: nil, time: nil, intr: nil) -> io
394
+ *
395
+ * Enables raw mode, and returns +io+.
322
396
  *
323
- * Enables raw mode.
397
+ * If the terminal mode needs to be back, use <code>io.raw { ... }</code>.
324
398
  *
325
- * If the terminal mode needs to be back, use io.raw { ... }.
399
+ * See IO#raw for details on the parameters.
326
400
  *
327
401
  * You must require 'io/console' to use this method.
328
402
  */
@@ -332,7 +406,7 @@ console_set_raw(int argc, VALUE *argv, VALUE io)
332
406
  conmode t;
333
407
  rb_io_t *fptr;
334
408
  int fd;
335
- rawmode_arg_t opts, *optp = rawmode_opt(argc, argv, &opts);
409
+ rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts);
336
410
 
337
411
  GetOpenFile(io, fptr);
338
412
  fd = GetReadFD(fptr);
@@ -357,7 +431,7 @@ console_set_raw(int argc, VALUE *argv, VALUE io)
357
431
  static VALUE
358
432
  console_cooked(VALUE io)
359
433
  {
360
- return ttymode(io, rb_yield, set_cookedmode, NULL);
434
+ return ttymode(io, rb_yield, io, set_cookedmode, NULL);
361
435
  }
362
436
 
363
437
  /*
@@ -385,25 +459,98 @@ console_set_cooked(VALUE io)
385
459
  return io;
386
460
  }
387
461
 
462
+ #ifndef _WIN32
388
463
  static VALUE
389
464
  getc_call(VALUE io)
390
465
  {
391
466
  return rb_funcallv(io, id_getc, 0, 0);
392
467
  }
468
+ #else
469
+ static void *
470
+ nogvl_getch(void *p)
471
+ {
472
+ int len = 0;
473
+ wint_t *buf = p, c = _getwch();
474
+
475
+ switch (c) {
476
+ case WEOF:
477
+ break;
478
+ case 0x00:
479
+ case 0xe0:
480
+ buf[len++] = c;
481
+ c = _getwch();
482
+ /* fall through */
483
+ default:
484
+ buf[len++] = c;
485
+ break;
486
+ }
487
+ return (void *)(VALUE)len;
488
+ }
489
+ #endif
393
490
 
394
491
  /*
395
492
  * call-seq:
396
- * io.getch(min: nil, time: nil) -> char
493
+ * io.getch(min: nil, time: nil, intr: nil) -> char
397
494
  *
398
495
  * Reads and returns a character in raw mode.
399
496
  *
497
+ * See IO#raw for details on the parameters.
498
+ *
400
499
  * You must require 'io/console' to use this method.
401
500
  */
402
501
  static VALUE
403
502
  console_getch(int argc, VALUE *argv, VALUE io)
404
503
  {
405
- rawmode_arg_t opts, *optp = rawmode_opt(argc, argv, &opts);
406
- return ttymode(io, getc_call, set_rawmode, optp);
504
+ rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts);
505
+ #ifndef _WIN32
506
+ return ttymode(io, getc_call, io, set_rawmode, optp);
507
+ #else
508
+ rb_io_t *fptr;
509
+ VALUE str;
510
+ wint_t c;
511
+ int w, len;
512
+ char buf[8];
513
+ wint_t wbuf[2];
514
+ struct timeval *to = NULL, tv;
515
+
516
+ GetOpenFile(io, fptr);
517
+ if (optp) {
518
+ if (optp->vtime) {
519
+ to = &tv;
520
+ tv.tv_sec = optp->vtime / 10;
521
+ tv.tv_usec = (optp->vtime % 10) * 100000;
522
+ }
523
+ if (optp->vmin != 1) {
524
+ rb_warning("min option ignored");
525
+ }
526
+ if (optp->intr) {
527
+ w = rb_wait_for_single_fd(fptr->fd, RB_WAITFD_IN, to);
528
+ if (w < 0) rb_eof_error();
529
+ if (!(w & RB_WAITFD_IN)) return Qnil;
530
+ }
531
+ else {
532
+ rb_warning("vtime option ignored if intr flag is unset");
533
+ }
534
+ }
535
+ len = (int)(VALUE)rb_thread_call_without_gvl(nogvl_getch, wbuf, RUBY_UBF_IO, 0);
536
+ switch (len) {
537
+ case 0:
538
+ return Qnil;
539
+ case 2:
540
+ buf[0] = (char)wbuf[0];
541
+ c = wbuf[1];
542
+ len = 1;
543
+ do {
544
+ buf[len++] = (unsigned char)c;
545
+ } while ((c >>= CHAR_BIT) && len < (int)sizeof(buf));
546
+ return rb_str_new(buf, len);
547
+ default:
548
+ c = wbuf[0];
549
+ len = rb_uv_to_utf8(buf, c);
550
+ str = rb_utf8_str_new(buf, len);
551
+ return rb_str_conv_enc(str, NULL, rb_default_external_encoding());
552
+ }
553
+ #endif
407
554
  }
408
555
 
409
556
  /*
@@ -421,7 +568,7 @@ console_getch(int argc, VALUE *argv, VALUE io)
421
568
  static VALUE
422
569
  console_noecho(VALUE io)
423
570
  {
424
- return ttymode(io, rb_yield, set_noecho, NULL);
571
+ return ttymode(io, rb_yield, io, set_noecho, NULL);
425
572
  }
426
573
 
427
574
  /*
@@ -473,6 +620,115 @@ console_echo_p(VALUE io)
473
620
  return echo_p(&t) ? Qtrue : Qfalse;
474
621
  }
475
622
 
623
+ static const rb_data_type_t conmode_type = {
624
+ "console-mode",
625
+ {0, RUBY_TYPED_DEFAULT_FREE,},
626
+ 0, 0,
627
+ RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
628
+ };
629
+ static VALUE cConmode;
630
+
631
+ static VALUE
632
+ conmode_alloc(VALUE klass)
633
+ {
634
+ return rb_data_typed_object_zalloc(klass, sizeof(conmode), &conmode_type);
635
+ }
636
+
637
+ static VALUE
638
+ conmode_new(VALUE klass, const conmode *t)
639
+ {
640
+ VALUE obj = conmode_alloc(klass);
641
+ *(conmode *)DATA_PTR(obj) = *t;
642
+ return obj;
643
+ }
644
+
645
+ static VALUE
646
+ conmode_init_copy(VALUE obj, VALUE obj2)
647
+ {
648
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
649
+ conmode *t2 = rb_check_typeddata(obj2, &conmode_type);
650
+ *t = *t2;
651
+ return obj;
652
+ }
653
+
654
+ static VALUE
655
+ conmode_set_echo(VALUE obj, VALUE f)
656
+ {
657
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
658
+ if (RTEST(f))
659
+ set_echo(t, NULL);
660
+ else
661
+ set_noecho(t, NULL);
662
+ return obj;
663
+ }
664
+
665
+ static VALUE
666
+ conmode_set_raw(int argc, VALUE *argv, VALUE obj)
667
+ {
668
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
669
+ rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts);
670
+
671
+ set_rawmode(t, optp);
672
+ return obj;
673
+ }
674
+
675
+ static VALUE
676
+ conmode_raw_new(int argc, VALUE *argv, VALUE obj)
677
+ {
678
+ conmode *r = rb_check_typeddata(obj, &conmode_type);
679
+ conmode t = *r;
680
+ rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts);
681
+
682
+ set_rawmode(&t, optp);
683
+ return conmode_new(rb_obj_class(obj), &t);
684
+ }
685
+
686
+ /*
687
+ * call-seq:
688
+ * io.console_mode -> mode
689
+ *
690
+ * Returns a data represents the current console mode.
691
+ *
692
+ * You must require 'io/console' to use this method.
693
+ */
694
+ static VALUE
695
+ console_conmode_get(VALUE io)
696
+ {
697
+ conmode t;
698
+ rb_io_t *fptr;
699
+ int fd;
700
+
701
+ GetOpenFile(io, fptr);
702
+ fd = GetReadFD(fptr);
703
+ if (!getattr(fd, &t)) rb_sys_fail(0);
704
+
705
+ return conmode_new(cConmode, &t);
706
+ }
707
+
708
+ /*
709
+ * call-seq:
710
+ * io.console_mode = mode
711
+ *
712
+ * Sets the console mode to +mode+.
713
+ *
714
+ * You must require 'io/console' to use this method.
715
+ */
716
+ static VALUE
717
+ console_conmode_set(VALUE io, VALUE mode)
718
+ {
719
+ conmode *t, r;
720
+ rb_io_t *fptr;
721
+ int fd;
722
+
723
+ TypedData_Get_Struct(mode, conmode, &conmode_type, t);
724
+ r = *t;
725
+ GetOpenFile(io, fptr);
726
+ fd = GetReadFD(fptr);
727
+ if (!setattr(fd, &r)) rb_sys_fail(0);
728
+
729
+ return mode;
730
+ }
731
+
476
732
  #if defined TIOCGWINSZ
477
733
  typedef struct winsize rb_console_size_t;
478
734
  #define getwinsize(fd, buf) (ioctl((fd), TIOCGWINSZ, (buf)) == 0)
@@ -531,16 +787,23 @@ console_set_winsize(VALUE io, VALUE size)
531
787
  #if defined _WIN32
532
788
  HANDLE wh;
533
789
  int newrow, newcol;
790
+ BOOL ret;
534
791
  #endif
535
792
  VALUE row, col, xpixel, ypixel;
536
793
  const VALUE *sz;
537
794
  int fd;
795
+ long sizelen;
538
796
 
539
797
  GetOpenFile(io, fptr);
540
798
  size = rb_Array(size);
541
- rb_check_arity(RARRAY_LENINT(size), 2, 4);
799
+ if ((sizelen = RARRAY_LEN(size)) != 2 && sizelen != 4) {
800
+ rb_raise(rb_eArgError,
801
+ "wrong number of arguments (given %ld, expected 2 or 4)",
802
+ sizelen);
803
+ }
542
804
  sz = RARRAY_CONST_PTR(size);
543
- row = sz[0], col = sz[1], xpixel = sz[2], ypixel = sz[3];
805
+ row = sz[0], col = sz[1], xpixel = ypixel = Qnil;
806
+ if (sizelen == 4) xpixel = sz[2], ypixel = sz[3];
544
807
  fd = GetWriteFD(fptr);
545
808
  #if defined TIOCSWINSZ
546
809
  ws.ws_row = ws.ws_col = ws.ws_xpixel = ws.ws_ypixel = 0;
@@ -562,17 +825,21 @@ console_set_winsize(VALUE io, VALUE size)
562
825
  if (!GetConsoleScreenBufferInfo(wh, &ws)) {
563
826
  rb_syserr_fail(LAST_ERROR, "GetConsoleScreenBufferInfo");
564
827
  }
565
- if ((ws.dwSize.X < newcol && (ws.dwSize.X = newcol, 1)) ||
566
- (ws.dwSize.Y < newrow && (ws.dwSize.Y = newrow, 1))) {
567
- if (!SetConsoleScreenBufferSize(wh, ws.dwSize)) {
568
- rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo");
569
- }
570
- }
828
+ ws.dwSize.X = newcol;
829
+ ret = SetConsoleScreenBufferSize(wh, ws.dwSize);
571
830
  ws.srWindow.Left = 0;
572
831
  ws.srWindow.Top = 0;
573
- ws.srWindow.Right = newcol;
574
- ws.srWindow.Bottom = newrow;
575
- if (!SetConsoleWindowInfo(wh, FALSE, &ws.srWindow)) {
832
+ ws.srWindow.Right = newcol-1;
833
+ ws.srWindow.Bottom = newrow-1;
834
+ if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) {
835
+ rb_syserr_fail(LAST_ERROR, "SetConsoleWindowInfo");
836
+ }
837
+ /* retry when shrinking buffer after shrunk window */
838
+ if (!ret && !SetConsoleScreenBufferSize(wh, ws.dwSize)) {
839
+ rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo");
840
+ }
841
+ /* remove scrollbar if possible */
842
+ if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) {
576
843
  rb_syserr_fail(LAST_ERROR, "SetConsoleWindowInfo");
577
844
  }
578
845
  #endif
@@ -580,6 +847,30 @@ console_set_winsize(VALUE io, VALUE size)
580
847
  }
581
848
  #endif
582
849
 
850
+ #ifdef _WIN32
851
+ static VALUE
852
+ console_check_winsize_changed(VALUE io)
853
+ {
854
+ rb_io_t *fptr;
855
+ HANDLE h;
856
+ DWORD num;
857
+
858
+ GetOpenFile(io, fptr);
859
+ h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(fptr));
860
+ while (GetNumberOfConsoleInputEvents(h, &num) && num > 0) {
861
+ INPUT_RECORD rec;
862
+ if (ReadConsoleInput(h, &rec, 1, &num)) {
863
+ if (rec.EventType == WINDOW_BUFFER_SIZE_EVENT) {
864
+ rb_yield(Qnil);
865
+ }
866
+ }
867
+ }
868
+ return io;
869
+ }
870
+ #else
871
+ #define console_check_winsize_changed rb_f_notimplement
872
+ #endif
873
+
583
874
  /*
584
875
  * call-seq:
585
876
  * io.iflush
@@ -675,9 +966,24 @@ console_beep(VALUE io)
675
966
  return io;
676
967
  }
677
968
 
969
+ static int
970
+ mode_in_range(VALUE val, int high, const char *modename)
971
+ {
972
+ int mode;
973
+ if (NIL_P(val)) return 0;
974
+ if (!RB_INTEGER_TYPE_P(val)) {
975
+ wrong_value:
976
+ rb_raise(rb_eArgError, "wrong %s mode: %"PRIsVALUE, modename, val);
977
+ }
978
+ if ((mode = NUM2INT(val)) < 0 || mode > high) {
979
+ goto wrong_value;
980
+ }
981
+ return mode;
982
+ }
983
+
678
984
  #if defined _WIN32
679
985
  static VALUE
680
- console_goto(VALUE io, VALUE x, VALUE y)
986
+ console_goto(VALUE io, VALUE y, VALUE x)
681
987
  {
682
988
  rb_io_t *fptr;
683
989
  int fd;
@@ -705,15 +1011,159 @@ console_cursor_pos(VALUE io)
705
1011
  if (!GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), &ws)) {
706
1012
  rb_syserr_fail(LAST_ERROR, 0);
707
1013
  }
708
- return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.X), UINT2NUM(ws.dwCursorPosition.Y));
1014
+ return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y), UINT2NUM(ws.dwCursorPosition.X));
709
1015
  }
710
1016
 
711
1017
  static VALUE
712
- console_cursor_set(VALUE io, VALUE cpos)
1018
+ console_move(VALUE io, int y, int x)
713
1019
  {
714
- cpos = rb_convert_type(cpos, T_ARRAY, "Array", "to_ary");
715
- if (RARRAY_LEN(cpos) != 2) rb_raise(rb_eArgError, "expected 2D coordinate");
716
- return console_goto(io, RARRAY_AREF(cpos, 0), RARRAY_AREF(cpos, 1));
1020
+ rb_io_t *fptr;
1021
+ HANDLE h;
1022
+ rb_console_size_t ws;
1023
+ COORD *pos = &ws.dwCursorPosition;
1024
+
1025
+ GetOpenFile(io, fptr);
1026
+ h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr));
1027
+ if (!GetConsoleScreenBufferInfo(h, &ws)) {
1028
+ rb_syserr_fail(LAST_ERROR, 0);
1029
+ }
1030
+ pos->X += x;
1031
+ pos->Y += y;
1032
+ if (!SetConsoleCursorPosition(h, *pos)) {
1033
+ rb_syserr_fail(LAST_ERROR, 0);
1034
+ }
1035
+ return io;
1036
+ }
1037
+
1038
+ static VALUE
1039
+ console_goto_column(VALUE io, VALUE val)
1040
+ {
1041
+ rb_io_t *fptr;
1042
+ HANDLE h;
1043
+ rb_console_size_t ws;
1044
+ COORD *pos = &ws.dwCursorPosition;
1045
+
1046
+ GetOpenFile(io, fptr);
1047
+ h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr));
1048
+ if (!GetConsoleScreenBufferInfo(h, &ws)) {
1049
+ rb_syserr_fail(LAST_ERROR, 0);
1050
+ }
1051
+ pos->X = NUM2INT(val);
1052
+ if (!SetConsoleCursorPosition(h, *pos)) {
1053
+ rb_syserr_fail(LAST_ERROR, 0);
1054
+ }
1055
+ return io;
1056
+ }
1057
+
1058
+ static void
1059
+ constat_clear(HANDLE handle, WORD attr, DWORD len, COORD pos)
1060
+ {
1061
+ DWORD written;
1062
+
1063
+ FillConsoleOutputAttribute(handle, attr, len, pos, &written);
1064
+ FillConsoleOutputCharacterW(handle, L' ', len, pos, &written);
1065
+ }
1066
+
1067
+ static VALUE
1068
+ console_erase_line(VALUE io, VALUE val)
1069
+ {
1070
+ rb_io_t *fptr;
1071
+ HANDLE h;
1072
+ rb_console_size_t ws;
1073
+ COORD *pos = &ws.dwCursorPosition;
1074
+ DWORD w;
1075
+ int mode = mode_in_range(val, 2, "line erase");
1076
+
1077
+ GetOpenFile(io, fptr);
1078
+ h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr));
1079
+ if (!GetConsoleScreenBufferInfo(h, &ws)) {
1080
+ rb_syserr_fail(LAST_ERROR, 0);
1081
+ }
1082
+ w = winsize_col(&ws);
1083
+ switch (mode) {
1084
+ case 0: /* after cursor */
1085
+ w -= pos->X;
1086
+ break;
1087
+ case 1: /* before *and* cursor */
1088
+ w = pos->X + 1;
1089
+ pos->X = 0;
1090
+ break;
1091
+ case 2: /* entire line */
1092
+ pos->X = 0;
1093
+ break;
1094
+ }
1095
+ constat_clear(h, ws.wAttributes, w, *pos);
1096
+ return io;
1097
+ }
1098
+
1099
+ static VALUE
1100
+ console_erase_screen(VALUE io, VALUE val)
1101
+ {
1102
+ rb_io_t *fptr;
1103
+ HANDLE h;
1104
+ rb_console_size_t ws;
1105
+ COORD *pos = &ws.dwCursorPosition;
1106
+ DWORD w;
1107
+ int mode = mode_in_range(val, 3, "screen erase");
1108
+
1109
+ GetOpenFile(io, fptr);
1110
+ h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr));
1111
+ if (!GetConsoleScreenBufferInfo(h, &ws)) {
1112
+ rb_syserr_fail(LAST_ERROR, 0);
1113
+ }
1114
+ w = winsize_col(&ws);
1115
+ switch (mode) {
1116
+ case 0: /* erase after cursor */
1117
+ w = (w * (ws.srWindow.Bottom - pos->Y + 1) - pos->X);
1118
+ break;
1119
+ case 1: /* erase before *and* cursor */
1120
+ w = (w * (pos->Y - ws.srWindow.Top) + pos->X + 1);
1121
+ pos->X = 0;
1122
+ pos->Y = ws.srWindow.Top;
1123
+ break;
1124
+ case 2: /* erase entire screen */
1125
+ w = (w * winsize_row(&ws));
1126
+ pos->X = 0;
1127
+ pos->Y = ws.srWindow.Top;
1128
+ break;
1129
+ case 3: /* erase entire screen */
1130
+ w = (w * ws.dwSize.Y);
1131
+ pos->X = 0;
1132
+ pos->Y = 0;
1133
+ break;
1134
+ }
1135
+ constat_clear(h, ws.wAttributes, w, *pos);
1136
+ return io;
1137
+ }
1138
+
1139
+ static VALUE
1140
+ console_scroll(VALUE io, int line)
1141
+ {
1142
+ rb_io_t *fptr;
1143
+ HANDLE h;
1144
+ rb_console_size_t ws;
1145
+
1146
+ GetOpenFile(io, fptr);
1147
+ h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(fptr));
1148
+ if (!GetConsoleScreenBufferInfo(h, &ws)) {
1149
+ rb_syserr_fail(LAST_ERROR, 0);
1150
+ }
1151
+ if (line) {
1152
+ SMALL_RECT scroll;
1153
+ COORD destination;
1154
+ CHAR_INFO fill;
1155
+ scroll.Left = 0;
1156
+ scroll.Top = line > 0 ? line : 0;
1157
+ scroll.Right = winsize_col(&ws) - 1;
1158
+ scroll.Bottom = winsize_row(&ws) - 1 + (line < 0 ? line : 0);
1159
+ destination.X = 0;
1160
+ destination.Y = line < 0 ? -line : 0;
1161
+ fill.Char.UnicodeChar = L' ';
1162
+ fill.Attributes = ws.wAttributes;
1163
+
1164
+ ScrollConsoleScreenBuffer(h, &scroll, NULL, destination, &fill);
1165
+ }
1166
+ return io;
717
1167
  }
718
1168
 
719
1169
  #include "win32_vk.inc"
@@ -744,12 +1194,210 @@ console_key_pressed_p(VALUE io, VALUE k)
744
1194
  return GetKeyState(vk) & 0x80 ? Qtrue : Qfalse;
745
1195
  }
746
1196
  #else
747
- # define console_goto rb_f_notimplement
748
- # define console_cursor_pos rb_f_notimplement
749
- # define console_cursor_set rb_f_notimplement
1197
+ struct query_args {
1198
+ const char *qstr;
1199
+ int opt;
1200
+ };
1201
+
1202
+ static int
1203
+ direct_query(VALUE io, const struct query_args *query)
1204
+ {
1205
+ if (RB_TYPE_P(io, T_FILE)) {
1206
+ rb_io_t *fptr;
1207
+ VALUE wio;
1208
+ GetOpenFile(io, fptr);
1209
+ wio = fptr->tied_io_for_writing;
1210
+ if (wio) {
1211
+ VALUE s = rb_str_new_cstr(query->qstr);
1212
+ rb_io_write(wio, s);
1213
+ rb_io_flush(wio);
1214
+ return 1;
1215
+ }
1216
+ if (write(fptr->fd, query->qstr, strlen(query->qstr)) != -1) {
1217
+ return 1;
1218
+ }
1219
+ if (fptr->fd == 0 &&
1220
+ write(1, query->qstr, strlen(query->qstr)) != -1) {
1221
+ return 1;
1222
+ }
1223
+ }
1224
+ return 0;
1225
+ }
1226
+
1227
+ static VALUE
1228
+ read_vt_response(VALUE io, VALUE query)
1229
+ {
1230
+ struct query_args *qargs = (struct query_args *)query;
1231
+ VALUE result, b;
1232
+ int opt = 0;
1233
+ int num = 0;
1234
+ if (qargs) {
1235
+ opt = qargs->opt;
1236
+ if (!direct_query(io, qargs)) return Qnil;
1237
+ }
1238
+ if (rb_io_getbyte(io) != INT2FIX(0x1b)) return Qnil;
1239
+ if (rb_io_getbyte(io) != INT2FIX('[')) return Qnil;
1240
+ result = rb_ary_new();
1241
+ while (!NIL_P(b = rb_io_getbyte(io))) {
1242
+ int c = NUM2UINT(b);
1243
+ if (c == ';') {
1244
+ rb_ary_push(result, INT2NUM(num));
1245
+ num = 0;
1246
+ }
1247
+ else if (ISDIGIT(c)) {
1248
+ num = num * 10 + c - '0';
1249
+ }
1250
+ else if (opt && c == opt) {
1251
+ opt = 0;
1252
+ }
1253
+ else {
1254
+ char last = (char)c;
1255
+ rb_ary_push(result, INT2NUM(num));
1256
+ b = rb_str_new(&last, 1);
1257
+ break;
1258
+ }
1259
+ }
1260
+ return rb_ary_push(result, b);
1261
+ }
1262
+
1263
+ static VALUE
1264
+ console_vt_response(int argc, VALUE *argv, VALUE io, const struct query_args *qargs)
1265
+ {
1266
+ rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 1, &opts);
1267
+ VALUE query = (VALUE)qargs;
1268
+ VALUE ret = ttymode_with_io(io, read_vt_response, query, set_rawmode, optp);
1269
+ return ret;
1270
+ }
1271
+
1272
+ static VALUE
1273
+ console_cursor_pos(VALUE io)
1274
+ {
1275
+ static const struct query_args query = {"\033[6n", 0};
1276
+ VALUE resp = console_vt_response(0, 0, io, &query);
1277
+ VALUE row, column, term;
1278
+ unsigned int r, c;
1279
+ if (!RB_TYPE_P(resp, T_ARRAY) || RARRAY_LEN(resp) != 3) return Qnil;
1280
+ term = RARRAY_AREF(resp, 2);
1281
+ if (!RB_TYPE_P(term, T_STRING) || RSTRING_LEN(term) != 1) return Qnil;
1282
+ if (RSTRING_PTR(term)[0] != 'R') return Qnil;
1283
+ row = RARRAY_AREF(resp, 0);
1284
+ column = RARRAY_AREF(resp, 1);
1285
+ rb_ary_resize(resp, 2);
1286
+ r = NUM2UINT(row) - 1;
1287
+ c = NUM2UINT(column) - 1;
1288
+ RARRAY_ASET(resp, 0, INT2NUM(r));
1289
+ RARRAY_ASET(resp, 1, INT2NUM(c));
1290
+ return resp;
1291
+ }
1292
+
1293
+ static VALUE
1294
+ console_goto(VALUE io, VALUE y, VALUE x)
1295
+ {
1296
+ rb_io_write(io, rb_sprintf("\x1b[%d;%dH", NUM2UINT(y)+1, NUM2UINT(x)+1));
1297
+ return io;
1298
+ }
1299
+
1300
+ static VALUE
1301
+ console_move(VALUE io, int y, int x)
1302
+ {
1303
+ if (x || y) {
1304
+ VALUE s = rb_str_new_cstr("");
1305
+ if (y) rb_str_catf(s, "\x1b[%d%c", y < 0 ? -y : y, y < 0 ? 'A' : 'B');
1306
+ if (x) rb_str_catf(s, "\x1b[%d%c", x < 0 ? -x : x, x < 0 ? 'D' : 'C');
1307
+ rb_io_write(io, s);
1308
+ rb_io_flush(io);
1309
+ }
1310
+ return io;
1311
+ }
1312
+
1313
+ static VALUE
1314
+ console_goto_column(VALUE io, VALUE val)
1315
+ {
1316
+ rb_io_write(io, rb_sprintf("\x1b[%dG", NUM2UINT(val)+1));
1317
+ return io;
1318
+ }
1319
+
1320
+ static VALUE
1321
+ console_erase_line(VALUE io, VALUE val)
1322
+ {
1323
+ int mode = mode_in_range(val, 2, "line erase");
1324
+ rb_io_write(io, rb_sprintf("\x1b[%dK", mode));
1325
+ return io;
1326
+ }
1327
+
1328
+ static VALUE
1329
+ console_erase_screen(VALUE io, VALUE val)
1330
+ {
1331
+ int mode = mode_in_range(val, 3, "screen erase");
1332
+ rb_io_write(io, rb_sprintf("\x1b[%dJ", mode));
1333
+ return io;
1334
+ }
1335
+
1336
+ static VALUE
1337
+ console_scroll(VALUE io, int line)
1338
+ {
1339
+ if (line) {
1340
+ VALUE s = rb_sprintf("\x1b[%d%c", line < 0 ? -line : line,
1341
+ line < 0 ? 'T' : 'S');
1342
+ rb_io_write(io, s);
1343
+ }
1344
+ return io;
1345
+ }
750
1346
  # define console_key_pressed_p rb_f_notimplement
751
1347
  #endif
752
1348
 
1349
+ static VALUE
1350
+ console_cursor_set(VALUE io, VALUE cpos)
1351
+ {
1352
+ cpos = rb_convert_type(cpos, T_ARRAY, "Array", "to_ary");
1353
+ if (RARRAY_LEN(cpos) != 2) rb_raise(rb_eArgError, "expected 2D coordinate");
1354
+ return console_goto(io, RARRAY_AREF(cpos, 0), RARRAY_AREF(cpos, 1));
1355
+ }
1356
+
1357
+ static VALUE
1358
+ console_cursor_up(VALUE io, VALUE val)
1359
+ {
1360
+ return console_move(io, -NUM2INT(val), 0);
1361
+ }
1362
+
1363
+ static VALUE
1364
+ console_cursor_down(VALUE io, VALUE val)
1365
+ {
1366
+ return console_move(io, +NUM2INT(val), 0);
1367
+ }
1368
+
1369
+ static VALUE
1370
+ console_cursor_left(VALUE io, VALUE val)
1371
+ {
1372
+ return console_move(io, 0, -NUM2INT(val));
1373
+ }
1374
+
1375
+ static VALUE
1376
+ console_cursor_right(VALUE io, VALUE val)
1377
+ {
1378
+ return console_move(io, 0, +NUM2INT(val));
1379
+ }
1380
+
1381
+ static VALUE
1382
+ console_scroll_forward(VALUE io, VALUE val)
1383
+ {
1384
+ return console_scroll(io, +NUM2INT(val));
1385
+ }
1386
+
1387
+ static VALUE
1388
+ console_scroll_backward(VALUE io, VALUE val)
1389
+ {
1390
+ return console_scroll(io, -NUM2INT(val));
1391
+ }
1392
+
1393
+ static VALUE
1394
+ console_clear_screen(VALUE io)
1395
+ {
1396
+ console_erase_screen(io, INT2FIX(2));
1397
+ console_goto(io, INT2FIX(0), INT2FIX(0));
1398
+ return io;
1399
+ }
1400
+
753
1401
  /*
754
1402
  * call-seq:
755
1403
  * IO.console -> #<File:/dev/tty>
@@ -849,7 +1497,7 @@ console_dev(int argc, VALUE *argv, VALUE klass)
849
1497
 
850
1498
  /*
851
1499
  * call-seq:
852
- * io.getch(min: nil, time: nil) -> char
1500
+ * io.getch(min: nil, time: nil, intr: nil) -> char
853
1501
  *
854
1502
  * See IO#getch.
855
1503
  */
@@ -869,7 +1517,7 @@ puts_call(VALUE io)
869
1517
  static VALUE
870
1518
  getpass_call(VALUE io)
871
1519
  {
872
- return ttymode(io, rb_io_gets, set_noecho, NULL);
1520
+ return ttymode(io, rb_io_gets, io, set_noecho, NULL);
873
1521
  }
874
1522
 
875
1523
  static void
@@ -878,7 +1526,6 @@ prompt(int argc, VALUE *argv, VALUE io)
878
1526
  if (argc > 0 && !NIL_P(argv[0])) {
879
1527
  VALUE str = argv[0];
880
1528
  StringValueCStr(str);
881
- rb_check_safe_obj(str);
882
1529
  rb_io_write(io, str);
883
1530
  }
884
1531
  }
@@ -948,6 +1595,7 @@ Init_console(void)
948
1595
  id_close = rb_intern("close");
949
1596
  id_min = rb_intern("min");
950
1597
  id_time = rb_intern("time");
1598
+ id_intr = rb_intern("intr");
951
1599
  #ifndef HAVE_RB_F_SEND
952
1600
  id___send__ = rb_intern("__send__");
953
1601
  #endif
@@ -964,6 +1612,8 @@ InitVM_console(void)
964
1612
  rb_define_method(rb_cIO, "getch", console_getch, -1);
965
1613
  rb_define_method(rb_cIO, "echo=", console_set_echo, 1);
966
1614
  rb_define_method(rb_cIO, "echo?", console_echo_p, 0);
1615
+ rb_define_method(rb_cIO, "console_mode", console_conmode_get, 0);
1616
+ rb_define_method(rb_cIO, "console_mode=", console_conmode_set, 1);
967
1617
  rb_define_method(rb_cIO, "noecho", console_noecho, 0);
968
1618
  rb_define_method(rb_cIO, "winsize", console_winsize, 0);
969
1619
  rb_define_method(rb_cIO, "winsize=", console_set_winsize, 1);
@@ -974,7 +1624,18 @@ InitVM_console(void)
974
1624
  rb_define_method(rb_cIO, "goto", console_goto, 2);
975
1625
  rb_define_method(rb_cIO, "cursor", console_cursor_pos, 0);
976
1626
  rb_define_method(rb_cIO, "cursor=", console_cursor_set, 1);
1627
+ rb_define_method(rb_cIO, "cursor_up", console_cursor_up, 1);
1628
+ rb_define_method(rb_cIO, "cursor_down", console_cursor_down, 1);
1629
+ rb_define_method(rb_cIO, "cursor_left", console_cursor_left, 1);
1630
+ rb_define_method(rb_cIO, "cursor_right", console_cursor_right, 1);
1631
+ rb_define_method(rb_cIO, "goto_column", console_goto_column, 1);
1632
+ rb_define_method(rb_cIO, "erase_line", console_erase_line, 1);
1633
+ rb_define_method(rb_cIO, "erase_screen", console_erase_screen, 1);
1634
+ rb_define_method(rb_cIO, "scroll_forward", console_scroll_forward, 1);
1635
+ rb_define_method(rb_cIO, "scroll_backward", console_scroll_backward, 1);
1636
+ rb_define_method(rb_cIO, "clear_screen", console_clear_screen, 0);
977
1637
  rb_define_method(rb_cIO, "pressed?", console_key_pressed_p, 1);
1638
+ rb_define_method(rb_cIO, "check_winsize_changed", console_check_winsize_changed, 0);
978
1639
  #if ENABLE_IO_GETPASS
979
1640
  rb_define_method(rb_cIO, "getpass", console_getpass, -1);
980
1641
  #endif
@@ -986,4 +1647,15 @@ InitVM_console(void)
986
1647
  rb_define_method(mReadable, "getpass", io_getpass, -1);
987
1648
  #endif
988
1649
  }
1650
+ {
1651
+ /* :stopdoc: */
1652
+ cConmode = rb_define_class_under(rb_cIO, "ConsoleMode", rb_cObject);
1653
+ rb_define_alloc_func(cConmode, conmode_alloc);
1654
+ rb_undef_method(cConmode, "initialize");
1655
+ rb_define_method(cConmode, "initialize_copy", conmode_init_copy, 1);
1656
+ rb_define_method(cConmode, "echo=", conmode_set_echo, 1);
1657
+ rb_define_method(cConmode, "raw!", conmode_set_raw, -1);
1658
+ rb_define_method(cConmode, "raw", conmode_raw_new, -1);
1659
+ /* :startdoc: */
1660
+ }
989
1661
  }