Skip to content

Commit dbfb2b0

Browse files
committed
Expose Windows output console modes
Expose virtual terminal processing and line wrapping flags so console users can configure output without calling Win32 APIs directly.
1 parent 9db579f commit dbfb2b0

2 files changed

Lines changed: 112 additions & 0 deletions

File tree

ext/io/console/console.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ typedef struct sgttyb conmode;
5656
#include <conio.h>
5757
typedef DWORD conmode;
5858

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+
5966
#define LAST_ERROR rb_w32_map_errno(GetLastError())
6067
#define SET_LAST_ERROR (errno = LAST_ERROR, 0)
6168

@@ -750,6 +757,70 @@ conmode_raw_new(int argc, VALUE *argv, VALUE obj)
750757
return conmode_new(rb_obj_class(obj), &t);
751758
}
752759

760+
#ifdef _WIN32
761+
/*
762+
* call-seq:
763+
* mode.virtual_terminal_processing? -> true or false
764+
*
765+
* Returns whether virtual terminal sequences are processed on output.
766+
*/
767+
static VALUE
768+
conmode_virtual_terminal_processing_p(VALUE obj)
769+
{
770+
conmode *t = rb_check_typeddata(obj, &conmode_type);
771+
return (*t & ENABLE_VIRTUAL_TERMINAL_PROCESSING) ? Qtrue : Qfalse;
772+
}
773+
774+
/*
775+
* call-seq:
776+
* mode.virtual_terminal_processing = enabled
777+
*
778+
* Enables or disables virtual terminal sequence processing in +mode+.
779+
* Assign +mode+ to IO#console_mode= to apply the change.
780+
*/
781+
static VALUE
782+
conmode_set_virtual_terminal_processing(VALUE obj, VALUE enabled)
783+
{
784+
conmode *t = rb_check_typeddata(obj, &conmode_type);
785+
if (RTEST(enabled))
786+
*t |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
787+
else
788+
*t &= ~ENABLE_VIRTUAL_TERMINAL_PROCESSING;
789+
return obj;
790+
}
791+
792+
/*
793+
* call-seq:
794+
* mode.wrap_at_eol_output? -> true or false
795+
*
796+
* Returns whether output wraps at the end of a line.
797+
*/
798+
static VALUE
799+
conmode_wrap_at_eol_output_p(VALUE obj)
800+
{
801+
conmode *t = rb_check_typeddata(obj, &conmode_type);
802+
return (*t & ENABLE_WRAP_AT_EOL_OUTPUT) ? Qtrue : Qfalse;
803+
}
804+
805+
/*
806+
* call-seq:
807+
* mode.wrap_at_eol_output = enabled
808+
*
809+
* Enables or disables wrapping at the end of a line in +mode+.
810+
* Assign +mode+ to IO#console_mode= to apply the change.
811+
*/
812+
static VALUE
813+
conmode_set_wrap_at_eol_output(VALUE obj, VALUE enabled)
814+
{
815+
conmode *t = rb_check_typeddata(obj, &conmode_type);
816+
if (RTEST(enabled))
817+
*t |= ENABLE_WRAP_AT_EOL_OUTPUT;
818+
else
819+
*t &= ~ENABLE_WRAP_AT_EOL_OUTPUT;
820+
return obj;
821+
}
822+
#endif
823+
753824
/*
754825
* call-seq:
755826
* io.console_mode -> mode
@@ -2310,5 +2381,11 @@ InitVM_console(void)
23102381
rb_define_method(cConmode, "echo=", conmode_set_echo, 1);
23112382
rb_define_method(cConmode, "raw!", conmode_set_raw, -1);
23122383
rb_define_method(cConmode, "raw", conmode_raw_new, -1);
2384+
#ifdef _WIN32
2385+
rb_define_method(cConmode, "virtual_terminal_processing?", conmode_virtual_terminal_processing_p, 0);
2386+
rb_define_method(cConmode, "virtual_terminal_processing=", conmode_set_virtual_terminal_processing, 1);
2387+
rb_define_method(cConmode, "wrap_at_eol_output?", conmode_wrap_at_eol_output_p, 0);
2388+
rb_define_method(cConmode, "wrap_at_eol_output=", conmode_set_wrap_at_eol_output, 1);
2389+
#endif
23132390
}
23142391
}

test/io/console/test_io_console.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,41 @@ def test_pressed_invalid
710710

711711
RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ and defined?(IO.console) and IO.console and \
712712
TestIO_Console.class_eval do
713+
def test_output_console_mode
714+
require "fiddle/import"
715+
716+
kernel32 = Module.new do
717+
extend Fiddle::Importer
718+
dlload "kernel32.dll"
719+
extern "void *CreateFileW(void *, long, long, void *, long, long, void *)"
720+
extern "int CloseHandle(void *)"
721+
extern "int GetConsoleMode(void *, void *)"
722+
end
723+
File.open("CONOUT$", "r+") do |output|
724+
path = "CONOUT$\0".encode("UTF-16LE")
725+
handle = kernel32.CreateFileW(path, -0x40000000, 3, nil, 3, 0, nil)
726+
buffer = [0].pack("L<")
727+
assert_not_equal(0, kernel32.GetConsoleMode(handle, buffer))
728+
original = buffer.unpack1("L<")
729+
mode = output.console_mode
730+
begin
731+
assert_equal((original & 4) != 0, mode.virtual_terminal_processing?)
732+
assert_equal((original & 2) != 0, mode.wrap_at_eol_output?)
733+
734+
mode.virtual_terminal_processing = (original & 4) == 0
735+
mode.wrap_at_eol_output = (original & 2) == 0
736+
output.console_mode = mode
737+
assert_not_equal(0, kernel32.GetConsoleMode(handle, buffer))
738+
assert_equal(original ^ 6, buffer.unpack1("L<"))
739+
ensure
740+
mode.virtual_terminal_processing = (original & 4) != 0
741+
mode.wrap_at_eol_output = (original & 2) != 0
742+
output.console_mode = mode
743+
kernel32.CloseHandle(handle)
744+
end
745+
end
746+
end
747+
713748
def test_cursor_visibility
714749
require "fiddle/import"
715750

0 commit comments

Comments
 (0)