io-console 0.8.1 → 0.9.0

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.1";
7
+ IO_CONSOLE_VERSION = "0.9.0";
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)); \
@@ -550,7 +545,7 @@ nogvl_getch(void *p)
550
545
 
551
546
  /*
552
547
  * call-seq:
553
- * io.getch(min: nil, time: nil, intr: nil) -> char
548
+ * io.getch(min: nil, time: nil, intr: nil) -> char or nil
554
549
  *
555
550
  * Reads and returns a character in raw mode.
556
551
  *
@@ -635,6 +630,43 @@ console_getch(int argc, VALUE *argv, VALUE io)
635
630
  #endif
636
631
  }
637
632
 
633
+ /*
634
+ * call-seq:
635
+ * io.input_pending? -> true or false
636
+ *
637
+ * Returns whether input can be read without blocking.
638
+ *
639
+ * You must require 'io/console' to use this method.
640
+ */
641
+ static VALUE
642
+ console_input_pending_p(VALUE io)
643
+ {
644
+ rb_io_t *fptr;
645
+
646
+ GetOpenFile(io, fptr);
647
+ if (rb_io_read_pending(fptr)) return Qtrue;
648
+ #ifdef _WIN32
649
+ {
650
+ DWORD mode;
651
+ HANDLE h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(io));
652
+
653
+ if (GetConsoleMode(h, &mode)) return _kbhit() ? Qtrue : Qfalse;
654
+ }
655
+ #endif
656
+ #if defined HAVE_RB_IO_WAIT
657
+ return RTEST(rb_io_wait(io, RB_INT2NUM(RUBY_IO_READABLE), INT2FIX(0))) ? Qtrue : Qfalse;
658
+ #else
659
+ {
660
+ struct timeval timeout = {0, 0};
661
+ int result;
662
+
663
+ result = rb_wait_for_single_fd(fptr->fd, RB_WAITFD_IN, &timeout);
664
+ if (result < 0) sys_fail(io);
665
+ return (result & RB_WAITFD_IN) ? Qtrue : Qfalse;
666
+ }
667
+ #endif
668
+ }
669
+
638
670
  /*
639
671
  * call-seq:
640
672
  * io.noecho {|io| }
@@ -762,6 +794,70 @@ conmode_raw_new(int argc, VALUE *argv, VALUE obj)
762
794
  return conmode_new(rb_obj_class(obj), &t);
763
795
  }
764
796
 
797
+ #ifdef _WIN32
798
+ /*
799
+ * call-seq:
800
+ * mode.virtual_terminal_processing? -> true or false
801
+ *
802
+ * Returns whether virtual terminal sequences are processed on output.
803
+ */
804
+ static VALUE
805
+ conmode_virtual_terminal_processing_p(VALUE obj)
806
+ {
807
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
808
+ return (*t & ENABLE_VIRTUAL_TERMINAL_PROCESSING) ? Qtrue : Qfalse;
809
+ }
810
+
811
+ /*
812
+ * call-seq:
813
+ * mode.virtual_terminal_processing = enabled
814
+ *
815
+ * Enables or disables virtual terminal sequence processing in +mode+.
816
+ * Assign +mode+ to IO#console_mode= to apply the change.
817
+ */
818
+ static VALUE
819
+ conmode_set_virtual_terminal_processing(VALUE obj, VALUE enabled)
820
+ {
821
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
822
+ if (RTEST(enabled))
823
+ *t |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
824
+ else
825
+ *t &= ~ENABLE_VIRTUAL_TERMINAL_PROCESSING;
826
+ return obj;
827
+ }
828
+
829
+ /*
830
+ * call-seq:
831
+ * mode.wrap_at_eol_output? -> true or false
832
+ *
833
+ * Returns whether output wraps at the end of a line.
834
+ */
835
+ static VALUE
836
+ conmode_wrap_at_eol_output_p(VALUE obj)
837
+ {
838
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
839
+ return (*t & ENABLE_WRAP_AT_EOL_OUTPUT) ? Qtrue : Qfalse;
840
+ }
841
+
842
+ /*
843
+ * call-seq:
844
+ * mode.wrap_at_eol_output = enabled
845
+ *
846
+ * Enables or disables wrapping at the end of a line in +mode+.
847
+ * Assign +mode+ to IO#console_mode= to apply the change.
848
+ */
849
+ static VALUE
850
+ conmode_set_wrap_at_eol_output(VALUE obj, VALUE enabled)
851
+ {
852
+ conmode *t = rb_check_typeddata(obj, &conmode_type);
853
+ if (RTEST(enabled))
854
+ *t |= ENABLE_WRAP_AT_EOL_OUTPUT;
855
+ else
856
+ *t &= ~ENABLE_WRAP_AT_EOL_OUTPUT;
857
+ return obj;
858
+ }
859
+ #endif
860
+
765
861
  /*
766
862
  * call-seq:
767
863
  * io.console_mode -> mode
@@ -840,6 +936,9 @@ console_winsize(VALUE io)
840
936
  return rb_assoc_new(INT2NUM(winsize_row(&ws)), INT2NUM(winsize_col(&ws)));
841
937
  }
842
938
 
939
+ static VALUE console_scroll(VALUE io, int line);
940
+ static VALUE console_goto(VALUE io, VALUE y, VALUE x);
941
+
843
942
  /*
844
943
  * call-seq:
845
944
  * io.winsize = [rows, columns]
@@ -856,7 +955,8 @@ console_set_winsize(VALUE io, VALUE size)
856
955
  #if defined _WIN32
857
956
  HANDLE wh;
858
957
  int newrow, newcol;
859
- BOOL ret;
958
+ COORD oldsize;
959
+ SMALL_RECT oldwindow;
860
960
  #endif
861
961
  VALUE row, col, xpixel, ypixel;
862
962
  const VALUE *sz;
@@ -891,18 +991,62 @@ console_set_winsize(VALUE io, VALUE size)
891
991
  if (!GetConsoleScreenBufferInfo(wh, &ws)) {
892
992
  rb_syserr_fail(LAST_ERROR, "GetConsoleScreenBufferInfo");
893
993
  }
994
+ oldsize = ws.dwSize;
995
+ oldwindow = ws.srWindow;
996
+ if (ws.srWindow.Right + 1 < newcol) {
997
+ ws.dwSize.X = newcol;
998
+ }
999
+ if (ws.dwSize.Y < newrow) {
1000
+ ws.dwSize.Y = newrow;
1001
+ }
1002
+ /* expand screen buffer first if needed */
1003
+ if (!SetConsoleScreenBufferSize(wh, ws.dwSize)) {
1004
+ rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo");
1005
+ }
1006
+ /* refresh ws for new dwMaximumWindowSize */
1007
+ if (!GetConsoleScreenBufferInfo(wh, &ws)) {
1008
+ rb_syserr_fail(LAST_ERROR, "GetConsoleScreenBufferInfo");
1009
+ }
1010
+ /* check new size before modifying buffer content */
1011
+ if (newrow <= 0 || newcol <= 0 ||
1012
+ newrow > ws.dwMaximumWindowSize.Y ||
1013
+ newcol > ws.dwMaximumWindowSize.X) {
1014
+ SetConsoleScreenBufferSize(wh, oldsize);
1015
+ /* remove scrollbar if possible */
1016
+ SetConsoleWindowInfo(wh, TRUE, &oldwindow);
1017
+ rb_raise(rb_eArgError, "out of range winsize: [%d, %d]", newrow, newcol);
1018
+ }
1019
+ /* shrink screen buffer width */
894
1020
  ws.dwSize.X = newcol;
895
- ret = SetConsoleScreenBufferSize(wh, ws.dwSize);
1021
+ /* shrink screen buffer height if window height were the same */
1022
+ if (oldsize.Y == ws.srWindow.Bottom - ws.srWindow.Top + 1) {
1023
+ ws.dwSize.Y = newrow;
1024
+ }
896
1025
  ws.srWindow.Left = 0;
897
- ws.srWindow.Top = 0;
898
- ws.srWindow.Right = newcol-1;
899
- ws.srWindow.Bottom = newrow-1;
1026
+ ws.srWindow.Right = newcol - 1;
1027
+ ws.srWindow.Bottom = ws.srWindow.Top + newrow -1;
1028
+ if (ws.dwCursorPosition.Y > ws.srWindow.Bottom) {
1029
+ console_scroll(io, ws.dwCursorPosition.Y - ws.srWindow.Bottom);
1030
+ ws.dwCursorPosition.Y = ws.srWindow.Bottom;
1031
+ console_goto(io, INT2FIX(ws.dwCursorPosition.Y), INT2FIX(ws.dwCursorPosition.X));
1032
+ }
1033
+ if (ws.srWindow.Bottom > ws.dwSize.Y - 1) {
1034
+ console_scroll(io, ws.srWindow.Bottom - (ws.dwSize.Y - 1));
1035
+ ws.dwCursorPosition.Y -= ws.srWindow.Bottom - (ws.dwSize.Y - 1);
1036
+ console_goto(io, INT2FIX(ws.dwCursorPosition.Y), INT2FIX(ws.dwCursorPosition.X));
1037
+ ws.srWindow.Bottom = ws.dwSize.Y - 1;
1038
+ }
1039
+ ws.srWindow.Top = ws.srWindow.Bottom - (newrow - 1);
1040
+ /* perform changes to winsize */
900
1041
  if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) {
901
- rb_syserr_fail(LAST_ERROR, "SetConsoleWindowInfo");
1042
+ int last_error = LAST_ERROR;
1043
+ SetConsoleScreenBufferSize(wh, oldsize);
1044
+ rb_syserr_fail(last_error, "SetConsoleWindowInfo");
902
1045
  }
903
- /* retry when shrinking buffer after shrunk window */
904
- if (!ret && !SetConsoleScreenBufferSize(wh, ws.dwSize)) {
905
- rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo");
1046
+ /* perform screen buffer shrinking if necessary */
1047
+ if ((ws.dwSize.Y < oldsize.Y || ws.dwSize.X < oldsize.X) &&
1048
+ !SetConsoleScreenBufferSize(wh, ws.dwSize)) {
1049
+ rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo");
906
1050
  }
907
1051
  /* remove scrollbar if possible */
908
1052
  if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) {
@@ -914,12 +1058,207 @@ console_set_winsize(VALUE io, VALUE size)
914
1058
  #endif
915
1059
 
916
1060
  #ifdef _WIN32
1061
+ enum console_input_handle_index {
1062
+ console_input_handle,
1063
+ console_input_wakeup,
1064
+ console_input_handle_count
1065
+ };
1066
+
1067
+ typedef struct {
1068
+ HANDLE handles[console_input_handle_count];
1069
+ INPUT_RECORD *records;
1070
+ DWORD length;
1071
+ DWORD count;
1072
+ DWORD timeout;
1073
+ DWORD wait_result;
1074
+ DWORD error;
1075
+ BOOL result;
1076
+ } read_console_input_args_t;
1077
+
1078
+ static void *
1079
+ nogvl_read_console_input(void *ptr)
1080
+ {
1081
+ read_console_input_args_t *args = ptr;
1082
+
1083
+ args->wait_result = WaitForMultipleObjects(console_input_handle_count,
1084
+ args->handles, FALSE, args->timeout);
1085
+ if (args->wait_result == WAIT_OBJECT_0 + console_input_handle) {
1086
+ args->result = ReadConsoleInputW(args->handles[console_input_handle],
1087
+ args->records, args->length, &args->count);
1088
+ if (!args->result) args->error = GetLastError();
1089
+ }
1090
+ else if (args->wait_result == WAIT_FAILED) {
1091
+ args->error = GetLastError();
1092
+ }
1093
+ return 0;
1094
+ }
1095
+
1096
+ static void
1097
+ ubf_console_input(void *ptr)
1098
+ {
1099
+ read_console_input_args_t *args = ptr;
1100
+ SetEvent(args->handles[console_input_wakeup]);
1101
+ }
1102
+
1103
+ static void
1104
+ console_input_event_set(VALUE event, const char *name, VALUE value)
1105
+ {
1106
+ rb_hash_aset(event, ID2SYM(rb_intern(name)), value);
1107
+ }
1108
+
1109
+ static VALUE
1110
+ console_input_event(const INPUT_RECORD *record)
1111
+ {
1112
+ VALUE event = rb_hash_new();
1113
+
1114
+ switch (record->EventType) {
1115
+ case KEY_EVENT:
1116
+ console_input_event_set(event, "type", ID2SYM(rb_intern("key")));
1117
+ console_input_event_set(event, "key_down", record->Event.KeyEvent.bKeyDown ? Qtrue : Qfalse);
1118
+ console_input_event_set(event, "repeat_count", UINT2NUM(record->Event.KeyEvent.wRepeatCount));
1119
+ console_input_event_set(event, "virtual_key_code", UINT2NUM(record->Event.KeyEvent.wVirtualKeyCode));
1120
+ console_input_event_set(event, "virtual_scan_code", UINT2NUM(record->Event.KeyEvent.wVirtualScanCode));
1121
+ console_input_event_set(event, "unicode_char", UINT2NUM(record->Event.KeyEvent.uChar.UnicodeChar));
1122
+ console_input_event_set(event, "control_key_state", UINT2NUM(record->Event.KeyEvent.dwControlKeyState));
1123
+ break;
1124
+ case MOUSE_EVENT:
1125
+ console_input_event_set(event, "type", ID2SYM(rb_intern("mouse")));
1126
+ console_input_event_set(event, "position", rb_assoc_new(
1127
+ INT2NUM(record->Event.MouseEvent.dwMousePosition.Y),
1128
+ INT2NUM(record->Event.MouseEvent.dwMousePosition.X)));
1129
+ console_input_event_set(event, "button_state", UINT2NUM(record->Event.MouseEvent.dwButtonState));
1130
+ console_input_event_set(event, "control_key_state", UINT2NUM(record->Event.MouseEvent.dwControlKeyState));
1131
+ console_input_event_set(event, "event_flags", UINT2NUM(record->Event.MouseEvent.dwEventFlags));
1132
+ break;
1133
+ case WINDOW_BUFFER_SIZE_EVENT:
1134
+ console_input_event_set(event, "type", ID2SYM(rb_intern("window_buffer_size")));
1135
+ console_input_event_set(event, "size", rb_assoc_new(
1136
+ INT2NUM(record->Event.WindowBufferSizeEvent.dwSize.Y),
1137
+ INT2NUM(record->Event.WindowBufferSizeEvent.dwSize.X)));
1138
+ break;
1139
+ case MENU_EVENT:
1140
+ console_input_event_set(event, "type", ID2SYM(rb_intern("menu")));
1141
+ console_input_event_set(event, "command_id", UINT2NUM(record->Event.MenuEvent.dwCommandId));
1142
+ break;
1143
+ case FOCUS_EVENT:
1144
+ console_input_event_set(event, "type", ID2SYM(rb_intern("focus")));
1145
+ console_input_event_set(event, "set_focus", record->Event.FocusEvent.bSetFocus ? Qtrue : Qfalse);
1146
+ break;
1147
+ default:
1148
+ console_input_event_set(event, "type", UINT2NUM(record->EventType));
1149
+ break;
1150
+ }
1151
+
1152
+ return event;
1153
+ }
1154
+
1155
+ static VALUE
1156
+ console_input_events_read(VALUE vargs)
1157
+ {
1158
+ read_console_input_args_t *args = (read_console_input_args_t *)vargs;
1159
+ VALUE events;
1160
+ DWORD i;
1161
+
1162
+ rb_thread_call_without_gvl(nogvl_read_console_input, args,
1163
+ ubf_console_input, args);
1164
+ if (args->wait_result == WAIT_TIMEOUT) return rb_ary_new();
1165
+ if (args->wait_result != WAIT_OBJECT_0 + console_input_handle ||
1166
+ !args->result) {
1167
+ rb_syserr_fail(rb_w32_map_errno(args->error), 0);
1168
+ }
1169
+
1170
+ events = rb_ary_new_capa(args->count);
1171
+ for (i = 0; i < args->count; ++i) {
1172
+ rb_ary_push(events, console_input_event(&args->records[i]));
1173
+ }
1174
+ return events;
1175
+ }
1176
+
1177
+ static VALUE
1178
+ console_input_events_ensure(VALUE vargs)
1179
+ {
1180
+ read_console_input_args_t *args = (read_console_input_args_t *)vargs;
1181
+
1182
+ CloseHandle(args->handles[console_input_wakeup]);
1183
+ xfree(args->records);
1184
+ return Qnil;
1185
+ }
1186
+
1187
+ /*
1188
+ * call-seq:
1189
+ * io.console_input_events([max_events], timeout: nil) -> array
1190
+ *
1191
+ * Reads up to +max_events+ console input events, preserving their order.
1192
+ * The default is one event. Blocks until at least one event is available,
1193
+ * or for +timeout+ seconds if specified. Returns an empty Array on timeout.
1194
+ *
1195
+ * Each event is returned as a Hash. The +:type+ and remaining keys are:
1196
+ *
1197
+ * - +:key+ : +:key_down+, +:repeat_count+, +:virtual_key_code+,
1198
+ * +:virtual_scan_code+, +:unicode_char+, and +:control_key_state+.
1199
+ * - +:mouse+ : +:position+ ([row, column]), +:button_state+,
1200
+ * +:control_key_state+, and +:event_flags+.
1201
+ * - +:window_buffer_size+ : +:size+ ([rows, columns]).
1202
+ * - +:menu+ : +:command_id+.
1203
+ * - +:focus+ : +:set_focus+.
1204
+ *
1205
+ * This method is Windows only.
1206
+ *
1207
+ * You must require 'io/console' to use this method.
1208
+ */
1209
+ static VALUE
1210
+ console_input_events(int argc, VALUE *argv, VALUE io)
1211
+ {
1212
+ VALUE vmax = Qnil, vopts = Qnil, vtimeout = Qundef;
1213
+ VALUE values[1];
1214
+ ID keywords[1] = {id_timeout};
1215
+ DWORD max_events = 1;
1216
+ read_console_input_args_t args;
1217
+
1218
+ rb_scan_args(argc, argv, "01:", &vmax, &vopts);
1219
+ if (rb_get_kwargs(vopts, keywords, 0, 1, values)) {
1220
+ vtimeout = values[0];
1221
+ }
1222
+ if (!NIL_P(vmax)) {
1223
+ max_events = NUM2UINT(vmax);
1224
+ if (max_events == 0) rb_raise(rb_eArgError, "max_events must be positive");
1225
+ }
1226
+
1227
+ args.timeout = INFINITE;
1228
+ if (!NIL_OR_UNDEF_P(vtimeout)) {
1229
+ struct timeval timeout = rb_time_interval(vtimeout);
1230
+ uint64_t milliseconds = (uint64_t)timeout.tv_sec * 1000;
1231
+ milliseconds += ((uint64_t)timeout.tv_usec + 999) / 1000;
1232
+ args.timeout = milliseconds < INFINITE ? (DWORD)milliseconds : INFINITE - 1;
1233
+ }
1234
+
1235
+ args.handles[console_input_handle] =
1236
+ (HANDLE)rb_w32_get_osfhandle(GetReadFD(io));
1237
+ args.records = ALLOC_N(INPUT_RECORD, max_events);
1238
+ args.handles[console_input_wakeup] = CreateEvent(NULL, FALSE, FALSE, NULL);
1239
+ if (!args.handles[console_input_wakeup]) {
1240
+ int error = LAST_ERROR;
1241
+ xfree(args.records);
1242
+ rb_syserr_fail(error, 0);
1243
+ }
1244
+ args.length = max_events;
1245
+ args.count = 0;
1246
+ args.wait_result = WAIT_FAILED;
1247
+ args.error = ERROR_SUCCESS;
1248
+ args.result = FALSE;
1249
+ return rb_ensure(console_input_events_read, (VALUE)&args,
1250
+ console_input_events_ensure, (VALUE)&args);
1251
+ }
1252
+
917
1253
  /*
918
1254
  * call-seq:
919
1255
  * io.check_winsize_changed { ... } -> io
920
1256
  *
921
1257
  * Yields while console input events are queued.
922
1258
  *
1259
+ * Deprecated because it discards queued input events other than window buffer
1260
+ * size changes. Use IO#console_input_events instead to preserve all events.
1261
+ *
923
1262
  * This method is Windows only.
924
1263
  *
925
1264
  * You must require 'io/console' to use this method.
@@ -930,6 +1269,9 @@ console_check_winsize_changed(VALUE io)
930
1269
  HANDLE h;
931
1270
  DWORD num;
932
1271
 
1272
+ rb_category_warn(RB_WARN_CATEGORY_DEPRECATED,
1273
+ "IO#check_winsize_changed is deprecated; "
1274
+ "use IO#console_input_events instead");
933
1275
  h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(io));
934
1276
  while (GetNumberOfConsoleInputEvents(h, &num) && num > 0) {
935
1277
  INPUT_RECORD rec;
@@ -942,6 +1284,7 @@ console_check_winsize_changed(VALUE io)
942
1284
  return io;
943
1285
  }
944
1286
  #else
1287
+ #define console_input_events rb_f_notimplement
945
1288
  #define console_check_winsize_changed rb_f_notimplement
946
1289
  #endif
947
1290
 
@@ -1221,7 +1564,7 @@ console_cursor_pos(VALUE io)
1221
1564
  if (!GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), &ws)) {
1222
1565
  rb_syserr_fail(LAST_ERROR, 0);
1223
1566
  }
1224
- return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y), UINT2NUM(ws.dwCursorPosition.X));
1567
+ return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y - ws.srWindow.Top), UINT2NUM(ws.dwCursorPosition.X));
1225
1568
  #else
1226
1569
  static const struct query_args query = {"\033[6n", 0};
1227
1570
  VALUE resp = console_vt_response(0, 0, io, &query);
@@ -1242,6 +1585,54 @@ console_cursor_pos(VALUE io)
1242
1585
  #endif
1243
1586
  }
1244
1587
 
1588
+ static VALUE
1589
+ console_cursor_visibility(VALUE io, int visible)
1590
+ {
1591
+ #ifdef _WIN32
1592
+ HANDLE h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io));
1593
+ CONSOLE_CURSOR_INFO info;
1594
+
1595
+ if (!GetConsoleCursorInfo(h, &info)) {
1596
+ rb_syserr_fail(LAST_ERROR, 0);
1597
+ }
1598
+ info.bVisible = visible;
1599
+ if (!SetConsoleCursorInfo(h, &info)) {
1600
+ rb_syserr_fail(LAST_ERROR, 0);
1601
+ }
1602
+ #else
1603
+ rb_io_write(io, rb_str_new_cstr(visible ? CSI "?25h" : CSI "?25l"));
1604
+ #endif
1605
+ return io;
1606
+ }
1607
+
1608
+ /*
1609
+ * call-seq:
1610
+ * io.hide_cursor -> io
1611
+ *
1612
+ * Hides the cursor.
1613
+ *
1614
+ * You must require 'io/console' to use this method.
1615
+ */
1616
+ static VALUE
1617
+ console_hide_cursor(VALUE io)
1618
+ {
1619
+ return console_cursor_visibility(io, 0);
1620
+ }
1621
+
1622
+ /*
1623
+ * call-seq:
1624
+ * io.show_cursor -> io
1625
+ *
1626
+ * Shows the cursor.
1627
+ *
1628
+ * You must require 'io/console' to use this method.
1629
+ */
1630
+ static VALUE
1631
+ console_show_cursor(VALUE io)
1632
+ {
1633
+ return console_cursor_visibility(io, 1);
1634
+ }
1635
+
1245
1636
  /*
1246
1637
  * call-seq:
1247
1638
  * io.goto(line, column) -> io
@@ -1254,11 +1645,17 @@ static VALUE
1254
1645
  console_goto(VALUE io, VALUE y, VALUE x)
1255
1646
  {
1256
1647
  #ifdef _WIN32
1257
- COORD pos;
1258
- int fd = GetWriteFD(io);
1259
- pos.X = NUM2UINT(x);
1260
- pos.Y = NUM2UINT(y);
1261
- if (!SetConsoleCursorPosition((HANDLE)rb_w32_get_osfhandle(fd), pos)) {
1648
+ HANDLE h;
1649
+ rb_console_size_t ws;
1650
+ COORD *pos = &ws.dwCursorPosition;
1651
+
1652
+ h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io));
1653
+ if (!GetConsoleScreenBufferInfo(h, &ws)) {
1654
+ rb_syserr_fail(LAST_ERROR, 0);
1655
+ }
1656
+ pos->X = NUM2UINT(x);
1657
+ pos->Y = ws.srWindow.Top + NUM2UINT(y);
1658
+ if (!SetConsoleCursorPosition(h, *pos)) {
1262
1659
  rb_syserr_fail(LAST_ERROR, 0);
1263
1660
  }
1264
1661
  #else
@@ -1651,13 +2048,11 @@ console_dev(int argc, VALUE *argv, VALUE klass)
1651
2048
  VALUE con = 0;
1652
2049
  VALUE sym = 0;
1653
2050
 
1654
- rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS);
1655
-
1656
2051
  if (argc) {
1657
2052
  Check_Type(sym = argv[0], T_SYMBOL);
1658
2053
  }
1659
2054
 
1660
- // Force the class to be File.
2055
+ /* Force the class to be File. */
1661
2056
  if (klass == rb_cIO) klass = rb_cFile;
1662
2057
 
1663
2058
  if (console_dev_get(klass, &con)) {
@@ -1724,7 +2119,7 @@ console_dev(int argc, VALUE *argv, VALUE klass)
1724
2119
 
1725
2120
  /*
1726
2121
  * call-seq:
1727
- * io.getch(min: nil, time: nil, intr: nil) -> char
2122
+ * io.getch(min: nil, time: nil, intr: nil) -> char or nil
1728
2123
  *
1729
2124
  * See IO#getch.
1730
2125
  */
@@ -1878,6 +2273,103 @@ console_ttyname(VALUE io)
1878
2273
  # define console_ttyname rb_f_notimplement
1879
2274
  #endif
1880
2275
 
2276
+ typedef enum {
2277
+ platform_default,
2278
+ #if defined _WIN32
2279
+ platform_cygwin,
2280
+ platform_msys,
2281
+ #endif
2282
+ platform_any,
2283
+
2284
+ platform_default_bit = 1U << platform_default,
2285
+ #if defined _WIN32
2286
+ platform_cygwin_bit = 1U << platform_cygwin,
2287
+ platform_msys_bit = 1U << platform_msys,
2288
+ #endif
2289
+ platform_any_bit = (1U << platform_any) - 1 /* all bits */
2290
+ } console_platform_t;
2291
+
2292
+ /*
2293
+ * call-seq:
2294
+ * io.tty?([mode, ...]) -> true or false
2295
+ *
2296
+ * Returns +true+ if the stream is associated with a terminal device (tty),
2297
+ * +false+ otherwise.
2298
+ *
2299
+ * If one or more +type+s are given, returns +true+ if the stream is
2300
+ * associated with any of the specified tty types.
2301
+ *
2302
+ * - +nil+ : Returns the result of the default tty check, as if no type were
2303
+ * given. It can be combined with other types.
2304
+ * - +:any+ : Returns +true+ for any known kind of tty, including the
2305
+ * default tty.
2306
+ * - +:cygwin+ : Returns +true+ for cygwin tty, on Windows.
2307
+ * - +:msys+ : Returns +true+ for msys2 tty, on Windows.
2308
+ */
2309
+ static VALUE
2310
+ console_platform_tty_p(int argc, VALUE *argv, VALUE io)
2311
+ {
2312
+ VALUE ret = Qfalse;
2313
+ int mode = 0;
2314
+
2315
+ if (argc > 0) {
2316
+ int i;
2317
+ for (i = 0; i < argc; ++i) {
2318
+ VALUE m = argv[i];
2319
+ if (NIL_P(m)) {
2320
+ mode |= platform_default_bit;
2321
+ continue;
2322
+ }
2323
+ Check_Type(m, T_SYMBOL);
2324
+ if (m == ID2SYM(rb_intern("any"))) {
2325
+ mode |= platform_any_bit;
2326
+ }
2327
+ #if defined _WIN32
2328
+ else if (m == ID2SYM(rb_intern("cygwin"))) {
2329
+ mode |= platform_cygwin_bit;
2330
+ }
2331
+ else if (m == ID2SYM(rb_intern("msys"))) {
2332
+ mode |= platform_msys_bit;
2333
+ }
2334
+ #endif
2335
+ else {
2336
+ rb_raise(rb_eArgError, "unknown tty type: %+" PRIsVALUE, m);
2337
+ }
2338
+ }
2339
+ }
2340
+ if ((mode & platform_default_bit) || (mode == 0)) {
2341
+ ret = rb_call_super(0, 0);
2342
+ }
2343
+ if ((mode & ~platform_default_bit) && !RTEST(ret)) {
2344
+ #if defined _WIN32
2345
+ if (mode & (platform_cygwin_bit | platform_msys_bit)) {
2346
+ struct {
2347
+ FILE_NAME_INFO info;
2348
+ WCHAR rest[MAX_PATH];
2349
+ } buffer;
2350
+
2351
+ HANDLE h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(io));
2352
+ if ((GetFileType(h) == FILE_TYPE_PIPE) &&
2353
+ GetFileInformationByHandleEx(h, FileNameInfo, &buffer, sizeof(buffer))) {
2354
+ WCHAR *const name = buffer.info.FileName;
2355
+ DWORD len = buffer.info.FileNameLength / sizeof(WCHAR);
2356
+ name[len] = L'\0';
2357
+ # define tty_pipe_p(type) \
2358
+ (memcmp(name, L"\\" #type "-", sizeof(L"\\" #type)) == 0 && \
2359
+ wcsstr(&name[rb_strlen_lit("\\" #type "-")], L"-pty") != NULL)
2360
+ if (!ret && (mode & platform_cygwin_bit)) {
2361
+ ret = tty_pipe_p(cygwin);
2362
+ }
2363
+ if (!ret && (mode & platform_msys_bit)) {
2364
+ ret = tty_pipe_p(msys);
2365
+ }
2366
+ }
2367
+ }
2368
+ #endif
2369
+ }
2370
+ return ret;
2371
+ }
2372
+
1881
2373
  /*
1882
2374
  * IO console methods
1883
2375
  */
@@ -1899,6 +2391,7 @@ Init_console(void)
1899
2391
  id_flush = rb_intern("flush");
1900
2392
  id_chomp_bang = rb_intern("chomp!");
1901
2393
  id_close = rb_intern("close");
2394
+ id_timeout = rb_intern("timeout");
1902
2395
  #define init_rawmode_opt_id(name) \
1903
2396
  rawmode_opt_ids[kwd_##name] = rb_intern(#name)
1904
2397
  init_rawmode_opt_id(min);
@@ -1913,11 +2406,31 @@ Init_console(void)
1913
2406
  void
1914
2407
  InitVM_console(void)
1915
2408
  {
2409
+ /* :nodoc: */
2410
+ VALUE mConsole = rb_define_module_under(rb_cIO, "Console");
2411
+ #ifdef _WIN32
2412
+ /* :nodoc: */
2413
+ VALUE mWindows = rb_define_module_under(mConsole, "Windows");
2414
+ #define define_win32_const(name) rb_define_const(mWindows, #name, UINT2NUM(name))
2415
+ EACH_VK(define_win32_const,;);
2416
+ define_win32_const(RIGHT_ALT_PRESSED);
2417
+ define_win32_const(LEFT_ALT_PRESSED);
2418
+ define_win32_const(RIGHT_CTRL_PRESSED);
2419
+ define_win32_const(LEFT_CTRL_PRESSED);
2420
+ define_win32_const(SHIFT_PRESSED);
2421
+ define_win32_const(NUMLOCK_ON);
2422
+ define_win32_const(SCROLLLOCK_ON);
2423
+ define_win32_const(CAPSLOCK_ON);
2424
+ define_win32_const(ENHANCED_KEY);
2425
+ #undef define_win32_const
2426
+ #endif
2427
+
1916
2428
  rb_define_method(rb_cIO, "raw", console_raw, -1);
1917
2429
  rb_define_method(rb_cIO, "raw!", console_set_raw, -1);
1918
2430
  rb_define_method(rb_cIO, "cooked", console_cooked, 0);
1919
2431
  rb_define_method(rb_cIO, "cooked!", console_set_cooked, 0);
1920
2432
  rb_define_method(rb_cIO, "getch", console_getch, -1);
2433
+ rb_define_method(rb_cIO, "input_pending?", console_input_pending_p, 0);
1921
2434
  rb_define_method(rb_cIO, "echo=", console_set_echo, 1);
1922
2435
  rb_define_method(rb_cIO, "echo?", console_echo_p, 0);
1923
2436
  rb_define_method(rb_cIO, "console_mode", console_conmode_get, 0);
@@ -1932,6 +2445,8 @@ InitVM_console(void)
1932
2445
  rb_define_method(rb_cIO, "goto", console_goto, 2);
1933
2446
  rb_define_method(rb_cIO, "cursor", console_cursor_pos, 0);
1934
2447
  rb_define_method(rb_cIO, "cursor=", console_cursor_set, 1);
2448
+ rb_define_method(rb_cIO, "hide_cursor", console_hide_cursor, 0);
2449
+ rb_define_method(rb_cIO, "show_cursor", console_show_cursor, 0);
1935
2450
  rb_define_method(rb_cIO, "cursor_up", console_cursor_up, 1);
1936
2451
  rb_define_method(rb_cIO, "cursor_down", console_cursor_down, 1);
1937
2452
  rb_define_method(rb_cIO, "cursor_left", console_cursor_left, 1);
@@ -1943,27 +2458,58 @@ InitVM_console(void)
1943
2458
  rb_define_method(rb_cIO, "scroll_backward", console_scroll_backward, 1);
1944
2459
  rb_define_method(rb_cIO, "clear_screen", console_clear_screen, 0);
1945
2460
  rb_define_method(rb_cIO, "pressed?", console_key_pressed_p, 1);
2461
+ rb_define_method(rb_cIO, "console_input_events", console_input_events, -1);
1946
2462
  rb_define_method(rb_cIO, "check_winsize_changed", console_check_winsize_changed, 0);
1947
2463
  rb_define_method(rb_cIO, "getpass", console_getpass, -1);
1948
2464
  rb_define_method(rb_cIO, "ttyname", console_ttyname, 0);
2465
+ {
2466
+ /* :nodoc: */
2467
+ VALUE platform = rb_define_module_under(rb_cIO, "platform_tty");
2468
+ {
2469
+ VALUE rb_cIO = platform;
2470
+ rb_define_method(rb_cIO, "tty?", console_platform_tty_p, -1);
2471
+ rb_define_method(rb_cIO, "isatty", console_platform_tty_p, -1);
2472
+ }
2473
+ #ifdef HAVE_RB_PREPEND_MODULE
2474
+ rb_prepend_module(rb_cIO, platform);
2475
+ #else
2476
+ rb_funcall(rb_cIO, rb_intern_const("prepend"), 1, platform);
2477
+ #endif
2478
+ }
1949
2479
  rb_define_singleton_method(rb_cIO, "console", console_dev, -1);
1950
2480
  {
1951
- /* :stopdoc: */
2481
+ /* :nodoc: */
1952
2482
  VALUE mReadable = rb_define_module_under(rb_cIO, "generic_readable");
1953
- /* :startdoc: */
1954
2483
  rb_define_method(mReadable, "getch", io_getch, -1);
1955
2484
  rb_define_method(mReadable, "getpass", io_getpass, -1);
1956
2485
  }
1957
2486
  {
1958
- /* :stopdoc: */
1959
- cConmode = rb_define_class_under(rb_cIO, "ConsoleMode", rb_cObject);
1960
- rb_define_const(cConmode, "VERSION", rb_obj_freeze(rb_str_new_cstr(IO_CONSOLE_VERSION)));
2487
+ /* :nodoc: */
2488
+ VALUE version = rb_obj_freeze(rb_str_new_cstr(IO_CONSOLE_VERSION));
2489
+ ID cid, deprecate_constant = rb_intern_const("deprecate_constant");
2490
+ rb_define_const(mConsole, "VERSION", version);
2491
+ /* :nodoc: */
2492
+ cConmode = rb_define_class_under(mConsole, "Mode", rb_cObject);
2493
+
2494
+ /* old internal names; do not use */
2495
+ cid = rb_intern_const("ConsoleMode");
2496
+ rb_const_set(rb_cIO, cid, cConmode);
2497
+ rb_funcall(rb_cIO, deprecate_constant, 1, ID2SYM(cid));
2498
+ cid = rb_intern_const("VERSION");
2499
+ rb_const_set(cConmode, cid, version);
2500
+ rb_funcall(cConmode, deprecate_constant, 1, ID2SYM(cid));
2501
+
1961
2502
  rb_define_alloc_func(cConmode, conmode_alloc);
1962
2503
  rb_undef_method(cConmode, "initialize");
1963
2504
  rb_define_method(cConmode, "initialize_copy", conmode_init_copy, 1);
1964
2505
  rb_define_method(cConmode, "echo=", conmode_set_echo, 1);
1965
2506
  rb_define_method(cConmode, "raw!", conmode_set_raw, -1);
1966
2507
  rb_define_method(cConmode, "raw", conmode_raw_new, -1);
1967
- /* :startdoc: */
2508
+ #ifdef _WIN32
2509
+ rb_define_method(cConmode, "virtual_terminal_processing?", conmode_virtual_terminal_processing_p, 0);
2510
+ rb_define_method(cConmode, "virtual_terminal_processing=", conmode_set_virtual_terminal_processing, 1);
2511
+ rb_define_method(cConmode, "wrap_at_eol_output?", conmode_wrap_at_eol_output_p, 0);
2512
+ rb_define_method(cConmode, "wrap_at_eol_output=", conmode_set_wrap_at_eol_output, 1);
2513
+ #endif
1968
2514
  }
1969
2515
  }