io-console 0.8.2 → 0.9.4

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,7 +4,7 @@
4
4
  */
5
5
 
6
6
  static const char *const
7
- IO_CONSOLE_VERSION = "0.8.2";
7
+ IO_CONSOLE_VERSION = "0.9.4";
8
8
 
9
9
  #include "ruby.h"
10
10
  #include "ruby/io.h"
@@ -56,6 +56,13 @@ typedef struct sgttyb conmode;
56
56
  #include <conio.h>
57
57
  typedef DWORD conmode;
58
58
 
59
+ #ifndef ENABLE_WRAP_AT_EOL_OUTPUT
60
+ # define ENABLE_WRAP_AT_EOL_OUTPUT 0x0002
61
+ #endif
62
+ #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
63
+ # define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
64
+ #endif
65
+
59
66
  #define LAST_ERROR rb_w32_map_errno(GetLastError())
60
67
  #define SET_LAST_ERROR (errno = LAST_ERROR, 0)
61
68
 
@@ -81,7 +88,7 @@ getattr(int fd, conmode *t)
81
88
 
82
89
  #define CSI "\x1b\x5b"
83
90
 
84
- static ID id_getc, id_close;
91
+ static ID id_getc, id_close, id_timeout;
85
92
  static ID id_gets, id_flush, id_chomp_bang;
86
93
 
87
94
  #ifndef HAVE_RB_INTERNED_STR_CSTR
@@ -89,6 +96,10 @@ static ID id_gets, id_flush, id_chomp_bang;
89
96
  # define rb_interned_str_cstr(str) rb_str_freeze(rb_usascii_str_new_cstr(str))
90
97
  #endif
91
98
 
99
+ #if !defined(HAVE_RB_CATEGORY_WARN) || !defined(HAVE_CONST_RB_WARN_CATEGORY_DEPRECATED)
100
+ # define rb_category_warn(category, ...) rb_warn(__VA_ARGS__)
101
+ #endif
102
+
92
103
  #if defined HAVE_RUBY_FIBER_SCHEDULER_H
93
104
  # include "ruby/fiber/scheduler.h"
94
105
  #elif defined HAVE_RB_SCHEDULER_TIMEOUT
@@ -118,22 +129,6 @@ io_path_fallback(VALUE io)
118
129
  #define rb_io_path io_path_fallback
119
130
  #endif
120
131
 
121
- #ifndef HAVE_RB_IO_GET_WRITE_IO
122
- static VALUE
123
- io_get_write_io_fallback(VALUE io)
124
- {
125
- rb_io_t *fptr;
126
- GetOpenFile(io, fptr);
127
- VALUE wio = fptr->tied_io_for_writing;
128
- return wio ? wio : io;
129
- }
130
- #define rb_io_get_write_io io_get_write_io_fallback
131
- #endif
132
-
133
- #ifndef DHAVE_RB_SYSERR_FAIL_STR
134
- # define rb_syserr_fail_str(e, mesg) rb_exc_raise(rb_syserr_new_str(e, mesg))
135
- #endif
136
-
137
132
  #define sys_fail(io) do { \
138
133
  int err = errno; \
139
134
  rb_syserr_fail_str(err, rb_io_path(io)); \
@@ -183,6 +178,22 @@ typedef struct {
183
178
  # define NIL_OR_UNDEF_P(obj) (NIL_P(obj) || UNDEF_P(obj))
184
179
  #endif
185
180
 
181
+ static int
182
+ to_vtime(VALUE vtime)
183
+ {
184
+ VALUE v10 = INT2FIX(10);
185
+ vtime = rb_funcall3(vtime, '*', 1, &v10);
186
+ return NUM2INT(vtime);
187
+ }
188
+
189
+ static unsigned char
190
+ clamp_uchar(int n)
191
+ {
192
+ if ((unsigned int)n >= UCHAR_MAX) n = UCHAR_MAX;
193
+ else if (n < 0) n = 0;
194
+ return (unsigned char)n;
195
+ }
196
+
186
197
  static rawmode_arg_t *
187
198
  rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t *opts)
188
199
  {
@@ -214,13 +225,11 @@ rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t *
214
225
  opts->vtime = 0;
215
226
  opts->intr = 0;
216
227
  if (!NIL_OR_UNDEF_P(vmin)) {
217
- opts->vmin = NUM2INT(vmin);
228
+ opts->vmin = clamp_uchar(NUM2INT(vmin));
218
229
  optp = opts;
219
230
  }
220
231
  if (!NIL_OR_UNDEF_P(vtime)) {
221
- VALUE v10 = INT2FIX(10);
222
- vtime = rb_funcall3(vtime, '*', 1, &v10);
223
- opts->vtime = NUM2INT(vtime);
232
+ opts->vtime = clamp_uchar(to_vtime(vtime));
224
233
  optp = opts;
225
234
  }
226
235
  switch (intr) {
@@ -550,7 +559,7 @@ nogvl_getch(void *p)
550
559
 
551
560
  /*
552
561
  * call-seq:
553
- * io.getch(min: nil, time: nil, intr: nil) -> char
562
+ * io.getch(min: nil, time: nil, intr: nil) -> char or nil
554
563
  *
555
564
  * Reads and returns a character in raw mode.
556
565
  *
@@ -635,6 +644,43 @@ console_getch(int argc, VALUE *argv, VALUE io)
635
644
  #endif
636
645
  }
637
646
 
647
+ /*
648
+ * call-seq:
649
+ * io.input_pending? -> true or false
650
+ *
651
+ * Returns whether input can be read without blocking.
652
+ *
653
+ * You must require 'io/console' to use this method.
654
+ */
655
+ static VALUE
656
+ console_input_pending_p(VALUE io)
657
+ {
658
+ rb_io_t *fptr;
659
+
660
+ GetOpenFile(io, fptr);
661
+ if (rb_io_read_pending(fptr)) return Qtrue;
662
+ #ifdef _WIN32
663
+ {
664
+ DWORD mode;
665
+ HANDLE h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(io));
666
+
667
+ if (GetConsoleMode(h, &mode)) return _kbhit() ? Qtrue : Qfalse;
668
+ }
669
+ #endif
670
+ #if defined HAVE_RB_IO_WAIT
671
+ return RTEST(rb_io_wait(io, RB_INT2NUM(RUBY_IO_READABLE), INT2FIX(0))) ? Qtrue : Qfalse;
672
+ #else
673
+ {
674
+ struct timeval timeout = {0, 0};
675
+ int result;
676
+
677
+ result = rb_wait_for_single_fd(fptr->fd, RB_WAITFD_IN, &timeout);
678
+ if (result < 0) sys_fail(io);
679
+ return (result & RB_WAITFD_IN) ? Qtrue : Qfalse;
680
+ }
681
+ #endif
682
+ }
683
+
638
684
  /*
639
685
  * call-seq:
640
686
  * io.noecho {|io| }
@@ -730,6 +776,13 @@ conmode_init_copy(VALUE obj, VALUE obj2)
730
776
  return obj;
731
777
  }
732
778
 
779
+ static VALUE
780
+ conmode_get_echo(VALUE obj)
781
+ {
782
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
783
+ return echo_p(t) ? Qtrue : Qfalse;
784
+ }
785
+
733
786
  static VALUE
734
787
  conmode_set_echo(VALUE obj, VALUE f)
735
788
  {
@@ -762,6 +815,122 @@ conmode_raw_new(int argc, VALUE *argv, VALUE obj)
762
815
  return conmode_new(rb_obj_class(obj), &t);
763
816
  }
764
817
 
818
+ static VALUE
819
+ conmode_get_min(VALUE obj)
820
+ {
821
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
822
+ #ifdef VMIN
823
+ return INT2FIX(t->c_cc[VMIN]);
824
+ #else
825
+ (void)t;
826
+ return Qnil;
827
+ #endif
828
+ }
829
+
830
+ static VALUE
831
+ conmode_set_min(VALUE obj, VALUE min)
832
+ {
833
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
834
+ int vmin = NIL_P(min) ? 1 : clamp_uchar(NUM2INT(min));
835
+ #ifdef VMIN
836
+ t->c_cc[VMIN] = vmin;
837
+ #else
838
+ (void)t;
839
+ (void)vmin;
840
+ #endif
841
+ return min;
842
+ }
843
+
844
+ static VALUE
845
+ conmode_get_time(VALUE obj)
846
+ {
847
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
848
+ #ifdef VTIME
849
+ return rb_rational_new(INT2FIX(t->c_cc[VTIME]), INT2FIX(10));
850
+ #else
851
+ (void)t;
852
+ return Qnil;
853
+ #endif
854
+ }
855
+
856
+ static VALUE
857
+ conmode_set_time(VALUE obj, VALUE time)
858
+ {
859
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
860
+ int vtime = NIL_P(time) ? 0 : clamp_uchar(to_vtime(time));
861
+ #ifdef VTIME
862
+ t->c_cc[VTIME] = vtime;
863
+ #else
864
+ (void)t;
865
+ (void)vtime;
866
+ #endif
867
+ return time;
868
+ }
869
+
870
+ #ifdef _WIN32
871
+ /*
872
+ * call-seq:
873
+ * mode.virtual_terminal_processing? -> true or false
874
+ *
875
+ * Returns whether virtual terminal sequences are processed on output.
876
+ */
877
+ static VALUE
878
+ conmode_virtual_terminal_processing_p(VALUE obj)
879
+ {
880
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
881
+ return (*t & ENABLE_VIRTUAL_TERMINAL_PROCESSING) ? Qtrue : Qfalse;
882
+ }
883
+
884
+ /*
885
+ * call-seq:
886
+ * mode.virtual_terminal_processing = enabled
887
+ *
888
+ * Enables or disables virtual terminal sequence processing in +mode+.
889
+ * Assign +mode+ to IO#console_mode= to apply the change.
890
+ */
891
+ static VALUE
892
+ conmode_set_virtual_terminal_processing(VALUE obj, VALUE enabled)
893
+ {
894
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
895
+ if (RTEST(enabled))
896
+ *t |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
897
+ else
898
+ *t &= ~ENABLE_VIRTUAL_TERMINAL_PROCESSING;
899
+ return obj;
900
+ }
901
+
902
+ /*
903
+ * call-seq:
904
+ * mode.wrap_at_eol_output? -> true or false
905
+ *
906
+ * Returns whether output wraps at the end of a line.
907
+ */
908
+ static VALUE
909
+ conmode_wrap_at_eol_output_p(VALUE obj)
910
+ {
911
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
912
+ return (*t & ENABLE_WRAP_AT_EOL_OUTPUT) ? Qtrue : Qfalse;
913
+ }
914
+
915
+ /*
916
+ * call-seq:
917
+ * mode.wrap_at_eol_output = enabled
918
+ *
919
+ * Enables or disables wrapping at the end of a line in +mode+.
920
+ * Assign +mode+ to IO#console_mode= to apply the change.
921
+ */
922
+ static VALUE
923
+ conmode_set_wrap_at_eol_output(VALUE obj, VALUE enabled)
924
+ {
925
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
926
+ if (RTEST(enabled))
927
+ *t |= ENABLE_WRAP_AT_EOL_OUTPUT;
928
+ else
929
+ *t &= ~ENABLE_WRAP_AT_EOL_OUTPUT;
930
+ return obj;
931
+ }
932
+ #endif
933
+
765
934
  /*
766
935
  * call-seq:
767
936
  * io.console_mode -> mode
@@ -861,7 +1030,6 @@ console_set_winsize(VALUE io, VALUE size)
861
1030
  int newrow, newcol;
862
1031
  COORD oldsize;
863
1032
  SMALL_RECT oldwindow;
864
- BOOL ret;
865
1033
  #endif
866
1034
  VALUE row, col, xpixel, ypixel;
867
1035
  const VALUE *sz;
@@ -963,12 +1131,207 @@ console_set_winsize(VALUE io, VALUE size)
963
1131
  #endif
964
1132
 
965
1133
  #ifdef _WIN32
1134
+ enum console_input_handle_index {
1135
+ console_input_handle,
1136
+ console_input_wakeup,
1137
+ console_input_handle_count
1138
+ };
1139
+
1140
+ typedef struct {
1141
+ HANDLE handles[console_input_handle_count];
1142
+ INPUT_RECORD *records;
1143
+ DWORD length;
1144
+ DWORD count;
1145
+ DWORD timeout;
1146
+ DWORD wait_result;
1147
+ DWORD error;
1148
+ BOOL result;
1149
+ } read_console_input_args_t;
1150
+
1151
+ static void *
1152
+ nogvl_read_console_input(void *ptr)
1153
+ {
1154
+ read_console_input_args_t *args = ptr;
1155
+
1156
+ args->wait_result = WaitForMultipleObjects(console_input_handle_count,
1157
+ args->handles, FALSE, args->timeout);
1158
+ if (args->wait_result == WAIT_OBJECT_0 + console_input_handle) {
1159
+ args->result = ReadConsoleInputW(args->handles[console_input_handle],
1160
+ args->records, args->length, &args->count);
1161
+ if (!args->result) args->error = GetLastError();
1162
+ }
1163
+ else if (args->wait_result == WAIT_FAILED) {
1164
+ args->error = GetLastError();
1165
+ }
1166
+ return 0;
1167
+ }
1168
+
1169
+ static void
1170
+ ubf_console_input(void *ptr)
1171
+ {
1172
+ read_console_input_args_t *args = ptr;
1173
+ SetEvent(args->handles[console_input_wakeup]);
1174
+ }
1175
+
1176
+ static void
1177
+ console_input_event_set(VALUE event, const char *name, VALUE value)
1178
+ {
1179
+ rb_hash_aset(event, ID2SYM(rb_intern(name)), value);
1180
+ }
1181
+
1182
+ static VALUE
1183
+ console_input_event(const INPUT_RECORD *record)
1184
+ {
1185
+ VALUE event = rb_hash_new();
1186
+
1187
+ switch (record->EventType) {
1188
+ case KEY_EVENT:
1189
+ console_input_event_set(event, "type", ID2SYM(rb_intern("key")));
1190
+ console_input_event_set(event, "key_down", record->Event.KeyEvent.bKeyDown ? Qtrue : Qfalse);
1191
+ console_input_event_set(event, "repeat_count", UINT2NUM(record->Event.KeyEvent.wRepeatCount));
1192
+ console_input_event_set(event, "virtual_key_code", UINT2NUM(record->Event.KeyEvent.wVirtualKeyCode));
1193
+ console_input_event_set(event, "virtual_scan_code", UINT2NUM(record->Event.KeyEvent.wVirtualScanCode));
1194
+ console_input_event_set(event, "unicode_char", UINT2NUM(record->Event.KeyEvent.uChar.UnicodeChar));
1195
+ console_input_event_set(event, "control_key_state", UINT2NUM(record->Event.KeyEvent.dwControlKeyState));
1196
+ break;
1197
+ case MOUSE_EVENT:
1198
+ console_input_event_set(event, "type", ID2SYM(rb_intern("mouse")));
1199
+ console_input_event_set(event, "position", rb_assoc_new(
1200
+ INT2NUM(record->Event.MouseEvent.dwMousePosition.Y),
1201
+ INT2NUM(record->Event.MouseEvent.dwMousePosition.X)));
1202
+ console_input_event_set(event, "button_state", UINT2NUM(record->Event.MouseEvent.dwButtonState));
1203
+ console_input_event_set(event, "control_key_state", UINT2NUM(record->Event.MouseEvent.dwControlKeyState));
1204
+ console_input_event_set(event, "event_flags", UINT2NUM(record->Event.MouseEvent.dwEventFlags));
1205
+ break;
1206
+ case WINDOW_BUFFER_SIZE_EVENT:
1207
+ console_input_event_set(event, "type", ID2SYM(rb_intern("window_buffer_size")));
1208
+ console_input_event_set(event, "size", rb_assoc_new(
1209
+ INT2NUM(record->Event.WindowBufferSizeEvent.dwSize.Y),
1210
+ INT2NUM(record->Event.WindowBufferSizeEvent.dwSize.X)));
1211
+ break;
1212
+ case MENU_EVENT:
1213
+ console_input_event_set(event, "type", ID2SYM(rb_intern("menu")));
1214
+ console_input_event_set(event, "command_id", UINT2NUM(record->Event.MenuEvent.dwCommandId));
1215
+ break;
1216
+ case FOCUS_EVENT:
1217
+ console_input_event_set(event, "type", ID2SYM(rb_intern("focus")));
1218
+ console_input_event_set(event, "set_focus", record->Event.FocusEvent.bSetFocus ? Qtrue : Qfalse);
1219
+ break;
1220
+ default:
1221
+ console_input_event_set(event, "type", UINT2NUM(record->EventType));
1222
+ break;
1223
+ }
1224
+
1225
+ return event;
1226
+ }
1227
+
1228
+ static VALUE
1229
+ console_input_events_read(VALUE vargs)
1230
+ {
1231
+ read_console_input_args_t *args = (read_console_input_args_t *)vargs;
1232
+ VALUE events;
1233
+ DWORD i;
1234
+
1235
+ rb_thread_call_without_gvl(nogvl_read_console_input, args,
1236
+ ubf_console_input, args);
1237
+ if (args->wait_result == WAIT_TIMEOUT) return rb_ary_new();
1238
+ if (args->wait_result != WAIT_OBJECT_0 + console_input_handle ||
1239
+ !args->result) {
1240
+ rb_syserr_fail(rb_w32_map_errno(args->error), 0);
1241
+ }
1242
+
1243
+ events = rb_ary_new_capa(args->count);
1244
+ for (i = 0; i < args->count; ++i) {
1245
+ rb_ary_push(events, console_input_event(&args->records[i]));
1246
+ }
1247
+ return events;
1248
+ }
1249
+
1250
+ static VALUE
1251
+ console_input_events_ensure(VALUE vargs)
1252
+ {
1253
+ read_console_input_args_t *args = (read_console_input_args_t *)vargs;
1254
+
1255
+ CloseHandle(args->handles[console_input_wakeup]);
1256
+ xfree(args->records);
1257
+ return Qnil;
1258
+ }
1259
+
1260
+ /*
1261
+ * call-seq:
1262
+ * io.console_input_events([max_events], timeout: nil) -> array
1263
+ *
1264
+ * Reads up to +max_events+ console input events, preserving their order.
1265
+ * The default is one event. Blocks until at least one event is available,
1266
+ * or for +timeout+ seconds if specified. Returns an empty Array on timeout.
1267
+ *
1268
+ * Each event is returned as a Hash. The +:type+ and remaining keys are:
1269
+ *
1270
+ * - +:key+ : +:key_down+, +:repeat_count+, +:virtual_key_code+,
1271
+ * +:virtual_scan_code+, +:unicode_char+, and +:control_key_state+.
1272
+ * - +:mouse+ : +:position+ ([row, column]), +:button_state+,
1273
+ * +:control_key_state+, and +:event_flags+.
1274
+ * - +:window_buffer_size+ : +:size+ ([rows, columns]).
1275
+ * - +:menu+ : +:command_id+.
1276
+ * - +:focus+ : +:set_focus+.
1277
+ *
1278
+ * This method is Windows only.
1279
+ *
1280
+ * You must require 'io/console' to use this method.
1281
+ */
1282
+ static VALUE
1283
+ console_input_events(int argc, VALUE *argv, VALUE io)
1284
+ {
1285
+ VALUE vmax = Qnil, vopts = Qnil, vtimeout = Qundef;
1286
+ VALUE values[1];
1287
+ ID keywords[1] = {id_timeout};
1288
+ DWORD max_events = 1;
1289
+ read_console_input_args_t args;
1290
+
1291
+ rb_scan_args(argc, argv, "01:", &vmax, &vopts);
1292
+ if (rb_get_kwargs(vopts, keywords, 0, 1, values)) {
1293
+ vtimeout = values[0];
1294
+ }
1295
+ if (!NIL_P(vmax)) {
1296
+ max_events = NUM2UINT(vmax);
1297
+ if (max_events == 0) rb_raise(rb_eArgError, "max_events must be positive");
1298
+ }
1299
+
1300
+ args.timeout = INFINITE;
1301
+ if (!NIL_OR_UNDEF_P(vtimeout)) {
1302
+ struct timeval timeout = rb_time_interval(vtimeout);
1303
+ uint64_t milliseconds = (uint64_t)timeout.tv_sec * 1000;
1304
+ milliseconds += ((uint64_t)timeout.tv_usec + 999) / 1000;
1305
+ args.timeout = milliseconds < INFINITE ? (DWORD)milliseconds : INFINITE - 1;
1306
+ }
1307
+
1308
+ args.handles[console_input_handle] =
1309
+ (HANDLE)rb_w32_get_osfhandle(GetReadFD(io));
1310
+ args.records = ALLOC_N(INPUT_RECORD, max_events);
1311
+ args.handles[console_input_wakeup] = CreateEvent(NULL, FALSE, FALSE, NULL);
1312
+ if (!args.handles[console_input_wakeup]) {
1313
+ int error = LAST_ERROR;
1314
+ xfree(args.records);
1315
+ rb_syserr_fail(error, 0);
1316
+ }
1317
+ args.length = max_events;
1318
+ args.count = 0;
1319
+ args.wait_result = WAIT_FAILED;
1320
+ args.error = ERROR_SUCCESS;
1321
+ args.result = FALSE;
1322
+ return rb_ensure(console_input_events_read, (VALUE)&args,
1323
+ console_input_events_ensure, (VALUE)&args);
1324
+ }
1325
+
966
1326
  /*
967
1327
  * call-seq:
968
1328
  * io.check_winsize_changed { ... } -> io
969
1329
  *
970
1330
  * Yields while console input events are queued.
971
1331
  *
1332
+ * Deprecated because it discards queued input events other than window buffer
1333
+ * size changes. Use IO#console_input_events instead to preserve all events.
1334
+ *
972
1335
  * This method is Windows only.
973
1336
  *
974
1337
  * You must require 'io/console' to use this method.
@@ -979,6 +1342,9 @@ console_check_winsize_changed(VALUE io)
979
1342
  HANDLE h;
980
1343
  DWORD num;
981
1344
 
1345
+ rb_category_warn(RB_WARN_CATEGORY_DEPRECATED,
1346
+ "IO#check_winsize_changed is deprecated; "
1347
+ "use IO#console_input_events instead");
982
1348
  h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(io));
983
1349
  while (GetNumberOfConsoleInputEvents(h, &num) && num > 0) {
984
1350
  INPUT_RECORD rec;
@@ -991,6 +1357,7 @@ console_check_winsize_changed(VALUE io)
991
1357
  return io;
992
1358
  }
993
1359
  #else
1360
+ #define console_input_events rb_f_notimplement
994
1361
  #define console_check_winsize_changed rb_f_notimplement
995
1362
  #endif
996
1363
 
@@ -1291,6 +1658,54 @@ console_cursor_pos(VALUE io)
1291
1658
  #endif
1292
1659
  }
1293
1660
 
1661
+ static VALUE
1662
+ console_cursor_visibility(VALUE io, int visible)
1663
+ {
1664
+ #ifdef _WIN32
1665
+ HANDLE h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io));
1666
+ CONSOLE_CURSOR_INFO info;
1667
+
1668
+ if (!GetConsoleCursorInfo(h, &info)) {
1669
+ rb_syserr_fail(LAST_ERROR, 0);
1670
+ }
1671
+ info.bVisible = visible;
1672
+ if (!SetConsoleCursorInfo(h, &info)) {
1673
+ rb_syserr_fail(LAST_ERROR, 0);
1674
+ }
1675
+ #else
1676
+ rb_io_write(io, rb_str_new_cstr(visible ? CSI "?25h" : CSI "?25l"));
1677
+ #endif
1678
+ return io;
1679
+ }
1680
+
1681
+ /*
1682
+ * call-seq:
1683
+ * io.hide_cursor -> io
1684
+ *
1685
+ * Hides the cursor.
1686
+ *
1687
+ * You must require 'io/console' to use this method.
1688
+ */
1689
+ static VALUE
1690
+ console_hide_cursor(VALUE io)
1691
+ {
1692
+ return console_cursor_visibility(io, 0);
1693
+ }
1694
+
1695
+ /*
1696
+ * call-seq:
1697
+ * io.show_cursor -> io
1698
+ *
1699
+ * Shows the cursor.
1700
+ *
1701
+ * You must require 'io/console' to use this method.
1702
+ */
1703
+ static VALUE
1704
+ console_show_cursor(VALUE io)
1705
+ {
1706
+ return console_cursor_visibility(io, 1);
1707
+ }
1708
+
1294
1709
  /*
1295
1710
  * call-seq:
1296
1711
  * io.goto(line, column) -> io
@@ -1710,7 +2125,7 @@ console_dev(int argc, VALUE *argv, VALUE klass)
1710
2125
  Check_Type(sym = argv[0], T_SYMBOL);
1711
2126
  }
1712
2127
 
1713
- // Force the class to be File.
2128
+ /* Force the class to be File. */
1714
2129
  if (klass == rb_cIO) klass = rb_cFile;
1715
2130
 
1716
2131
  if (console_dev_get(klass, &con)) {
@@ -1777,7 +2192,7 @@ console_dev(int argc, VALUE *argv, VALUE klass)
1777
2192
 
1778
2193
  /*
1779
2194
  * call-seq:
1780
- * io.getch(min: nil, time: nil, intr: nil) -> char
2195
+ * io.getch(min: nil, time: nil, intr: nil) -> char or nil
1781
2196
  *
1782
2197
  * See IO#getch.
1783
2198
  */
@@ -1793,10 +2208,17 @@ puts_call(VALUE io)
1793
2208
  return rb_io_write(io, rb_default_rs);
1794
2209
  }
1795
2210
 
2211
+ static VALUE
2212
+ funcall_with_rs(VALUE obj, ID mid)
2213
+ {
2214
+ const VALUE rs = rb_default_rs; /* rvalue in TruffleRuby */
2215
+ return rb_funcallv(obj, mid, 1, &rs);
2216
+ }
2217
+
1796
2218
  static VALUE
1797
2219
  gets_call(VALUE io)
1798
2220
  {
1799
- return rb_funcallv(io, id_gets, 0, 0);
2221
+ return funcall_with_rs(io, id_gets);
1800
2222
  }
1801
2223
 
1802
2224
  static VALUE
@@ -1819,8 +2241,7 @@ static VALUE
1819
2241
  str_chomp(VALUE str)
1820
2242
  {
1821
2243
  if (!NIL_P(str)) {
1822
- const VALUE rs = rb_default_rs; /* rvalue in TruffleRuby */
1823
- rb_funcallv(str, id_chomp_bang, 1, &rs);
2244
+ funcall_with_rs(str, id_chomp_bang);
1824
2245
  }
1825
2246
  return str;
1826
2247
  }
@@ -1881,7 +2302,7 @@ io_getpass(int argc, VALUE *argv, VALUE io)
1881
2302
  * call-seq:
1882
2303
  * io.ttyname -> string or nil
1883
2304
  *
1884
- * Returns name of associated terminal (tty) if +io+ is not a tty.
2305
+ * Returns name of associated terminal (tty) if +io+ is a tty.
1885
2306
  * Returns +nil+ otherwise.
1886
2307
  */
1887
2308
  static VALUE
@@ -1893,20 +2314,23 @@ console_ttyname(VALUE io)
1893
2314
  return rb_usascii_str_new_lit("con");
1894
2315
  # elif defined HAVE_TTYNAME_R
1895
2316
  {
2317
+ # ifdef TTYNAME_R_RETURNS_CHAR_P
2318
+ # define ttyname_r(fd, name, size) (ttyname_r(fd, name, size) == NULL ? errno : 0)
2319
+ # endif
1896
2320
  char termname[1024], *tn = termname;
1897
2321
  size_t size = sizeof(termname);
1898
2322
  int e;
1899
- if (ttyname_r(fd, tn, size) == 0)
2323
+ if ((e = ttyname_r(fd, tn, size)) == 0)
1900
2324
  return rb_interned_str_cstr(tn);
1901
- if ((e = errno) == ERANGE) {
2325
+ if (e == ERANGE) {
1902
2326
  VALUE s = rb_str_new(0, size);
1903
2327
  while (1) {
1904
2328
  tn = RSTRING_PTR(s);
1905
2329
  size = rb_str_capacity(s);
1906
- if (ttyname_r(fd, tn, size) == 0) {
2330
+ if ((e = ttyname_r(fd, tn, size)) == 0) {
1907
2331
  return rb_str_to_interned_str(rb_str_resize(s, strlen(tn)));
1908
2332
  }
1909
- if ((e = errno) != ERANGE) break;
2333
+ if (e != ERANGE) break;
1910
2334
  if ((size *= 2) >= INT_MAX/2) break;
1911
2335
  rb_str_resize(s, size);
1912
2336
  }
@@ -1931,6 +2355,103 @@ console_ttyname(VALUE io)
1931
2355
  # define console_ttyname rb_f_notimplement
1932
2356
  #endif
1933
2357
 
2358
+ typedef enum {
2359
+ platform_default,
2360
+ #if defined _WIN32
2361
+ platform_cygwin,
2362
+ platform_msys,
2363
+ #endif
2364
+ platform_any,
2365
+
2366
+ platform_default_bit = 1U << platform_default,
2367
+ #if defined _WIN32
2368
+ platform_cygwin_bit = 1U << platform_cygwin,
2369
+ platform_msys_bit = 1U << platform_msys,
2370
+ #endif
2371
+ platform_any_bit = (1U << platform_any) - 1 /* all bits */
2372
+ } console_platform_t;
2373
+
2374
+ /*
2375
+ * call-seq:
2376
+ * io.tty?([mode, ...]) -> true or false
2377
+ *
2378
+ * Returns +true+ if the stream is associated with a terminal device (tty),
2379
+ * +false+ otherwise.
2380
+ *
2381
+ * If one or more +type+s are given, returns +true+ if the stream is
2382
+ * associated with any of the specified tty types.
2383
+ *
2384
+ * - +nil+ : Returns the result of the default tty check, as if no type were
2385
+ * given. It can be combined with other types.
2386
+ * - +:any+ : Returns +true+ for any known kind of tty, including the
2387
+ * default tty.
2388
+ * - +:cygwin+ : Returns +true+ for cygwin tty, on Windows.
2389
+ * - +:msys+ : Returns +true+ for msys2 tty, on Windows.
2390
+ */
2391
+ static VALUE
2392
+ console_platform_tty_p(int argc, VALUE *argv, VALUE io)
2393
+ {
2394
+ VALUE ret = Qfalse;
2395
+ int mode = 0;
2396
+
2397
+ if (argc > 0) {
2398
+ int i;
2399
+ for (i = 0; i < argc; ++i) {
2400
+ VALUE m = argv[i];
2401
+ if (NIL_P(m)) {
2402
+ mode |= platform_default_bit;
2403
+ continue;
2404
+ }
2405
+ Check_Type(m, T_SYMBOL);
2406
+ if (m == ID2SYM(rb_intern("any"))) {
2407
+ mode |= platform_any_bit;
2408
+ }
2409
+ #if defined _WIN32
2410
+ else if (m == ID2SYM(rb_intern("cygwin"))) {
2411
+ mode |= platform_cygwin_bit;
2412
+ }
2413
+ else if (m == ID2SYM(rb_intern("msys"))) {
2414
+ mode |= platform_msys_bit;
2415
+ }
2416
+ #endif
2417
+ else {
2418
+ rb_raise(rb_eArgError, "unknown tty type: %+" PRIsVALUE, m);
2419
+ }
2420
+ }
2421
+ }
2422
+ if ((mode & platform_default_bit) || (mode == 0)) {
2423
+ ret = rb_call_super(0, 0);
2424
+ }
2425
+ if ((mode & ~platform_default_bit) && !RTEST(ret)) {
2426
+ #if defined _WIN32
2427
+ if (mode & (platform_cygwin_bit | platform_msys_bit)) {
2428
+ struct {
2429
+ FILE_NAME_INFO info;
2430
+ WCHAR rest[MAX_PATH];
2431
+ } buffer;
2432
+
2433
+ HANDLE h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(io));
2434
+ if ((GetFileType(h) == FILE_TYPE_PIPE) &&
2435
+ GetFileInformationByHandleEx(h, FileNameInfo, &buffer, sizeof(buffer))) {
2436
+ WCHAR *const name = buffer.info.FileName;
2437
+ DWORD len = buffer.info.FileNameLength / sizeof(WCHAR);
2438
+ name[len] = L'\0';
2439
+ # define tty_pipe_p(type) \
2440
+ (memcmp(name, L"\\" #type "-", sizeof(L"\\" #type)) == 0 && \
2441
+ wcsstr(&name[rb_strlen_lit("\\" #type "-")], L"-pty") != NULL)
2442
+ if (!ret && (mode & platform_cygwin_bit)) {
2443
+ ret = tty_pipe_p(cygwin);
2444
+ }
2445
+ if (!ret && (mode & platform_msys_bit)) {
2446
+ ret = tty_pipe_p(msys);
2447
+ }
2448
+ }
2449
+ }
2450
+ #endif
2451
+ }
2452
+ return ret;
2453
+ }
2454
+
1934
2455
  /*
1935
2456
  * IO console methods
1936
2457
  */
@@ -1952,6 +2473,7 @@ Init_console(void)
1952
2473
  id_flush = rb_intern("flush");
1953
2474
  id_chomp_bang = rb_intern("chomp!");
1954
2475
  id_close = rb_intern("close");
2476
+ id_timeout = rb_intern("timeout");
1955
2477
  #define init_rawmode_opt_id(name) \
1956
2478
  rawmode_opt_ids[kwd_##name] = rb_intern(#name)
1957
2479
  init_rawmode_opt_id(min);
@@ -1966,11 +2488,31 @@ Init_console(void)
1966
2488
  void
1967
2489
  InitVM_console(void)
1968
2490
  {
2491
+ /* :nodoc: */
2492
+ VALUE mConsole = rb_define_module_under(rb_cIO, "Console");
2493
+ #ifdef _WIN32
2494
+ /* :nodoc: */
2495
+ VALUE mWindows = rb_define_module_under(mConsole, "Windows");
2496
+ #define define_win32_const(name) rb_define_const(mWindows, #name, UINT2NUM(name))
2497
+ EACH_VK(define_win32_const,;);
2498
+ define_win32_const(RIGHT_ALT_PRESSED);
2499
+ define_win32_const(LEFT_ALT_PRESSED);
2500
+ define_win32_const(RIGHT_CTRL_PRESSED);
2501
+ define_win32_const(LEFT_CTRL_PRESSED);
2502
+ define_win32_const(SHIFT_PRESSED);
2503
+ define_win32_const(NUMLOCK_ON);
2504
+ define_win32_const(SCROLLLOCK_ON);
2505
+ define_win32_const(CAPSLOCK_ON);
2506
+ define_win32_const(ENHANCED_KEY);
2507
+ #undef define_win32_const
2508
+ #endif
2509
+
1969
2510
  rb_define_method(rb_cIO, "raw", console_raw, -1);
1970
2511
  rb_define_method(rb_cIO, "raw!", console_set_raw, -1);
1971
2512
  rb_define_method(rb_cIO, "cooked", console_cooked, 0);
1972
2513
  rb_define_method(rb_cIO, "cooked!", console_set_cooked, 0);
1973
2514
  rb_define_method(rb_cIO, "getch", console_getch, -1);
2515
+ rb_define_method(rb_cIO, "input_pending?", console_input_pending_p, 0);
1974
2516
  rb_define_method(rb_cIO, "echo=", console_set_echo, 1);
1975
2517
  rb_define_method(rb_cIO, "echo?", console_echo_p, 0);
1976
2518
  rb_define_method(rb_cIO, "console_mode", console_conmode_get, 0);
@@ -1985,6 +2527,8 @@ InitVM_console(void)
1985
2527
  rb_define_method(rb_cIO, "goto", console_goto, 2);
1986
2528
  rb_define_method(rb_cIO, "cursor", console_cursor_pos, 0);
1987
2529
  rb_define_method(rb_cIO, "cursor=", console_cursor_set, 1);
2530
+ rb_define_method(rb_cIO, "hide_cursor", console_hide_cursor, 0);
2531
+ rb_define_method(rb_cIO, "show_cursor", console_show_cursor, 0);
1988
2532
  rb_define_method(rb_cIO, "cursor_up", console_cursor_up, 1);
1989
2533
  rb_define_method(rb_cIO, "cursor_down", console_cursor_down, 1);
1990
2534
  rb_define_method(rb_cIO, "cursor_left", console_cursor_left, 1);
@@ -1996,27 +2540,64 @@ InitVM_console(void)
1996
2540
  rb_define_method(rb_cIO, "scroll_backward", console_scroll_backward, 1);
1997
2541
  rb_define_method(rb_cIO, "clear_screen", console_clear_screen, 0);
1998
2542
  rb_define_method(rb_cIO, "pressed?", console_key_pressed_p, 1);
2543
+ rb_define_method(rb_cIO, "console_input_events", console_input_events, -1);
1999
2544
  rb_define_method(rb_cIO, "check_winsize_changed", console_check_winsize_changed, 0);
2000
2545
  rb_define_method(rb_cIO, "getpass", console_getpass, -1);
2001
2546
  rb_define_method(rb_cIO, "ttyname", console_ttyname, 0);
2547
+ {
2548
+ /* :nodoc: */
2549
+ VALUE platform = rb_define_module_under(rb_cIO, "platform_tty");
2550
+ {
2551
+ VALUE rb_cIO = platform;
2552
+ rb_define_method(rb_cIO, "tty?", console_platform_tty_p, -1);
2553
+ rb_define_method(rb_cIO, "isatty", console_platform_tty_p, -1);
2554
+ }
2555
+ #ifdef HAVE_RB_PREPEND_MODULE
2556
+ rb_prepend_module(rb_cIO, platform);
2557
+ #else
2558
+ rb_funcall(rb_cIO, rb_intern_const("prepend"), 1, platform);
2559
+ #endif
2560
+ }
2002
2561
  rb_define_singleton_method(rb_cIO, "console", console_dev, -1);
2003
2562
  {
2004
- /* :stopdoc: */
2563
+ /* :nodoc: */
2005
2564
  VALUE mReadable = rb_define_module_under(rb_cIO, "generic_readable");
2006
- /* :startdoc: */
2007
2565
  rb_define_method(mReadable, "getch", io_getch, -1);
2008
2566
  rb_define_method(mReadable, "getpass", io_getpass, -1);
2009
2567
  }
2010
2568
  {
2011
- /* :stopdoc: */
2012
- cConmode = rb_define_class_under(rb_cIO, "ConsoleMode", rb_cObject);
2013
- rb_define_const(cConmode, "VERSION", rb_obj_freeze(rb_str_new_cstr(IO_CONSOLE_VERSION)));
2569
+ /* :nodoc: */
2570
+ VALUE version = rb_obj_freeze(rb_str_new_cstr(IO_CONSOLE_VERSION));
2571
+ ID cid, deprecate_constant = rb_intern_const("deprecate_constant");
2572
+ rb_define_const(mConsole, "VERSION", version);
2573
+ /* :nodoc: */
2574
+ cConmode = rb_define_class_under(mConsole, "Mode", rb_cObject);
2575
+
2576
+ /* old internal names; do not use */
2577
+ cid = rb_intern_const("ConsoleMode");
2578
+ rb_const_set(rb_cIO, cid, cConmode);
2579
+ rb_funcall(rb_cIO, deprecate_constant, 1, ID2SYM(cid));
2580
+ cid = rb_intern_const("VERSION");
2581
+ rb_const_set(cConmode, cid, version);
2582
+ rb_funcall(cConmode, deprecate_constant, 1, ID2SYM(cid));
2583
+
2014
2584
  rb_define_alloc_func(cConmode, conmode_alloc);
2015
2585
  rb_undef_method(cConmode, "initialize");
2016
2586
  rb_define_method(cConmode, "initialize_copy", conmode_init_copy, 1);
2587
+ rb_define_method(cConmode, "echo?", conmode_get_echo, 0);
2588
+ rb_define_method(cConmode, "echo", conmode_get_echo, 0);
2017
2589
  rb_define_method(cConmode, "echo=", conmode_set_echo, 1);
2018
2590
  rb_define_method(cConmode, "raw!", conmode_set_raw, -1);
2019
2591
  rb_define_method(cConmode, "raw", conmode_raw_new, -1);
2020
- /* :startdoc: */
2592
+ rb_define_method(cConmode, "min", conmode_get_min, 0);
2593
+ rb_define_method(cConmode, "min=", conmode_set_min, 1);
2594
+ rb_define_method(cConmode, "time", conmode_get_time, 0);
2595
+ rb_define_method(cConmode, "time=", conmode_set_time, 1);
2596
+ #ifdef _WIN32
2597
+ rb_define_method(cConmode, "virtual_terminal_processing?", conmode_virtual_terminal_processing_p, 0);
2598
+ rb_define_method(cConmode, "virtual_terminal_processing=", conmode_set_virtual_terminal_processing, 1);
2599
+ rb_define_method(cConmode, "wrap_at_eol_output?", conmode_wrap_at_eol_output_p, 0);
2600
+ rb_define_method(cConmode, "wrap_at_eol_output=", conmode_set_wrap_at_eol_output, 1);
2601
+ #endif
2021
2602
  }
2022
2603
  }