@@ -88,7 +88,7 @@ getattr(int fd, conmode *t)
8888
8989#define CSI "\x1b\x5b"
9090
91- static ID id_getc , id_close ;
91+ static ID id_getc , id_close , id_timeout ;
9292static ID id_gets , id_flush , id_chomp_bang ;
9393
9494#ifndef HAVE_RB_INTERNED_STR_CSTR
@@ -1058,22 +1058,48 @@ console_set_winsize(VALUE io, VALUE size)
10581058#endif
10591059
10601060#ifdef _WIN32
1061+ enum console_input_handle_index {
1062+ console_input_handle ,
1063+ console_input_wakeup ,
1064+ console_input_handle_count
1065+ };
1066+
10611067typedef struct {
1062- HANDLE handle ;
1068+ HANDLE handles [ console_input_handle_count ] ;
10631069 INPUT_RECORD * records ;
10641070 DWORD length ;
10651071 DWORD count ;
1072+ DWORD timeout ;
1073+ DWORD wait_result ;
1074+ DWORD error ;
10661075 BOOL result ;
10671076} read_console_input_args_t ;
10681077
10691078static void *
10701079nogvl_read_console_input (void * ptr )
10711080{
10721081 read_console_input_args_t * args = ptr ;
1073- args -> result = ReadConsoleInputW (args -> handle , args -> records , args -> length , & args -> count );
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+ }
10741093 return 0 ;
10751094}
10761095
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+
10771103static void
10781104console_input_event_set (VALUE event , const char * name , VALUE value )
10791105{
@@ -1126,12 +1152,45 @@ console_input_event(const INPUT_RECORD *record)
11261152 return event ;
11271153}
11281154
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+
11291187/*
11301188 * call-seq:
1131- * io.console_input_events([max_events]) -> array
1189+ * io.console_input_events([max_events], timeout: nil ) -> array
11321190 *
11331191 * Reads up to +max_events+ console input events, preserving their order.
1134- * The default is one event. Blocks until at least one event is available.
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.
11351194 *
11361195 * Each event is returned as a Hash. The +:type+ and remaining keys are:
11371196 *
@@ -1150,37 +1209,45 @@ console_input_event(const INPUT_RECORD *record)
11501209static VALUE
11511210console_input_events (int argc , VALUE * argv , VALUE io )
11521211{
1153- VALUE vmax ;
1212+ VALUE vmax = Qnil , vopts = Qnil , vtimeout = Qundef ;
1213+ VALUE values [1 ];
1214+ ID keywords [1 ] = {id_timeout };
11541215 DWORD max_events = 1 ;
11551216 read_console_input_args_t args ;
1156- VALUE event_buffer = 0 ;
1157- VALUE events ;
1158- DWORD i ;
11591217
1160- rb_scan_args (argc , argv , "01" , & vmax );
1218+ rb_scan_args (argc , argv , "01:" , & vmax , & vopts );
1219+ if (rb_get_kwargs (vopts , keywords , 0 , 1 , values )) {
1220+ vtimeout = values [0 ];
1221+ }
11611222 if (!NIL_P (vmax )) {
11621223 max_events = NUM2UINT (vmax );
11631224 if (max_events == 0 ) rb_raise (rb_eArgError , "max_events must be positive" );
11641225 }
11651226
1166- args .handle = (HANDLE )rb_w32_get_osfhandle (GetReadFD (io ));
1167- args .records = ALLOCV_N (INPUT_RECORD , event_buffer , max_events );
1168- args .length = max_events ;
1169- args .count = 0 ;
1170- args .result = FALSE;
1171- rb_thread_call_without_gvl (nogvl_read_console_input , & args , RUBY_UBF_IO , 0 );
1172- if (!args .result ) {
1173- int error = LAST_ERROR ;
1174- ALLOCV_END (event_buffer );
1175- rb_syserr_fail (error , 0 );
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 ;
11761233 }
11771234
1178- events = rb_ary_new_capa (args .count );
1179- for (i = 0 ; i < args .count ; ++ i ) {
1180- rb_ary_push (events , console_input_event (& args .records [i ]));
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 );
11811243 }
1182- ALLOCV_END (event_buffer );
1183- return events ;
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 );
11841251}
11851252
11861253/*
@@ -2324,6 +2391,7 @@ Init_console(void)
23242391 id_flush = rb_intern ("flush" );
23252392 id_chomp_bang = rb_intern ("chomp!" );
23262393 id_close = rb_intern ("close" );
2394+ id_timeout = rb_intern ("timeout" );
23272395#define init_rawmode_opt_id (name ) \
23282396 rawmode_opt_ids[kwd_##name] = rb_intern(#name)
23292397 init_rawmode_opt_id (min );
0 commit comments