Windows 10上使用Ruby VT100转义序列的不可预测的行为

时间:2017-07-13 00:03:40

标签: ruby windows winapi console vt100

在Windows 10上使用ruby 2.3.1p112 (2016-04-26 revision 54768) [x64-mingw32],版本10.0.14393

首先要做的几件事:

  1. Windows echo命令的行为会绕过VT100的控制台模式标志。根据MSDN,这是正常的,其中该标志仅影响WriteConsole()WriteFile()
  2. 当我使用WriteConsole()更改标记时,Win32函数SetConsoleMode()正常工作。它在设置标志时解释VT100转义序列。
  3. 那么Ruby发生了什么?它显示绿色为红色,并以某种方式忽略我的控制台标志。为什么它呈现出更深的绿色?我的理论是,这是Ruby的一个问题,以及它如何处理将输出写入控制台。

    完整脚本:

    #!/usr/bin/ruby
    # encoding: UTF-8
    
    require 'rbconfig'
    
    unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
      raise 'This script only works on Windows. Quitting.'
    end
    
    require 'fiddle'
    require 'fiddle/types'
    require 'fiddle/import'
    
    class VirtMode
      #TODO: Check Windows 10 build number (>= 1511) for mode support
      module Kernel32
        extend Fiddle::Importer
        dlload 'kernel32'
        include Fiddle::Win32Types
    
        DWORD_SIZE = sizeof('DWORD')
        STD_OUTPUT_HANDLE = -11
        STD_INPUT_HANDLE = -10
        VIRTUAL_TERMINAL_PROCESSING = 0x0004
    
        extern 'HANDLE GetStdHandle(DWORD)'
        extern 'DWORD SetConsoleMode(HANDLE, DWORD)'
        extern 'DWORD GetConsoleMode(HANDLE, PDWORD)'
        extern 'BOOL WriteConsole(HANDLE, const *char, DWORD, PDWORD, PVOID)'
      end
    
      class << self; attr_accessor :stdout, :stdin end
      self.stdout = Kernel32::GetStdHandle(Kernel32::STD_OUTPUT_HANDLE)
      self.stdin = Kernel32::GetStdHandle(Kernel32::STD_INPUT_HANDLE)
    
      def self.get_mode
        mode = [0].pack('L')
        success = Kernel32::GetConsoleMode(stdout, mode)
        return mode.unpack('L').first if success.nonzero?
        raise 'Could not get console mode'
      end
    
      def self.enable_virtual_mode
        new_mode = get_mode | Kernel32::VIRTUAL_TERMINAL_PROCESSING
        #puts new_mode.to_s(2).rjust(32, '0')
        return Kernel32::SetConsoleMode(stdout, new_mode).nonzero?
      end
    
      def self.disable_virtual_mode
        new_mode = get_mode & ~Kernel32::VIRTUAL_TERMINAL_PROCESSING
        #puts new_mode.to_s(2).rjust(32, '0')
        return Kernel32::SetConsoleMode(stdout, new_mode).nonzero?
      end
    
      def self.write_console(text)
        written = 0
        Kernel32::WriteConsole(stdout, text, text.size, written, 0)
      end
    end
    
    # It's already disabled but just in case
    VirtMode.disable_virtual_mode
    puts '--VT100 mode disabled--'
    puts "\e[38;2;255;0;32mRuby: Red!\e[0m"
    VirtMode.write_console "\e[38;2;255;0;32mWin32: Red!\e[0m\n"
    system "echo \e[38;2;255;0;32mEcho: Red!\e[0m\n"
    
    # Now we enable Windows 10 support for VT100
    # https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
    VirtMode.enable_virtual_mode
    puts '--VT100 mode enabled--'
    puts "\e[38;2;0;255;32mRuby: Green!\e[0m"
    VirtMode.write_console "\e[38;2;0;255;32mWin32: Green!\e[0m\n"
    system "echo \e[38;2;0;255;32mEcho: Green!\e[0m\n"
    

    Windows PowerShell中的示例输出: enter image description here

    不要在Rubymine中调试此脚本,除非您已将其输出到Windows控制台,例如PowerShell或命令提示符,因为GetConsoleMode()将失败。

1 个答案:

答案 0 :(得分:0)

在2016年3月8日的commit中添加了对Windows 10或更高版本的原生VT100的Ruby支持。在此之前,Ruby使用自己的VT100转义序列解析器。

我安装的Ruby版本缺少此更改,因此我抓住了一个更新的版本ruby 2.4.1p111 (2017-03-22 revision 58053) [x64-mingw32]

自提交以来,Ruby现在执行以下操作:

  1. 如果控制台模式缺少ENABLE_VIRTUAL_TERMINAL_PROCESSING标志,则使用自己的VT100解析器。
  2. 如果存在标志,则使用Windows VT100支持。
  3. Ruby为红色显示绿色的原因可能是Ruby的RGB颜色代码的VT100解析器中的一个错误。例如,"\e[38;2;255;0;32mRuby: Red!\e[0m"有红色(255),它实际上将转义序列解释为"\e[32mRuby: Red!\e[0m",它是绿色的。此错误还解释了启用VT100模式时的颜色差异。

    使用Windows VT模式的Ruby 2.4正确显示了绿色。事实证明Ruby不解释RGB颜色代码,因为RGB是某些虚拟终端支持的扩展(感谢Thomas-Dickey)。

    enter image description here