[code] Getting modifier and toggle key states in Ruby Tool callback methods

Prompted by these open API tracker issues:

… and this topic:

… in which, after I had tested from SU2021 onward, and the results showed that the flags passed to Tool#onKeyDown are always 0 for SketchUp 23, 24 and 25, … Steve took it another step, thus:

He mentions that the flags require some special handling to extract data:

This [code] post is my response for Windows platform tools with Example

Note that on MS Windows, the window messages sent to an application can have a combination of flags. SketchUp did not define these flag constants when they defined the virtual key constants in the API.

Some of these may not apply to MacOS as they do not use window messaging like the Windows system does.

So you may need to define these in your class or modules as local constants.

# REF:
# https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
#
KF_EXTENDED  = 0x0100  #    256
KF_MENUMODE  = 0x1000  #   4096
KF_ALTDOWN   = 0x2000  #   8192
KF_REPEAT    = 0x4000  #  16384
KF_UP        = 0x8000  #  32768

My testing shows that now since SU2023 began the Qt migration, the only modifier key that returns flags for onKeyDown is ALT.

When pressed by itself first, we get key VK_ALT and flags 8192 which is KF_ALTDOWN.

When we hold ALT down and strike another key, we get the keycode for the 2nd key and flags that are equal to (or contain) KF_ALTDOWN.

alt_depressed = ( flags & KF_ALTDOWN ) == KF_ALTDOWN

Sometimes, using ALT (on Windows) activates the application menu bar and the flags may contain MF_MENUMODE. When this happens we (sometimes) do not get a follow up onKeyUp firing for the ALT key when it is released. (This was reported recently.)

The thing about Windows keystroke messages is that they do not contain information about the CTRL, SHIFT or toggle keys.
A C/C++ developer must call a system function in the message handler callback in order to get whether either of these modifiers are held down.
See Key Status in the above reference article, which mentions calling GetKeyState(), which we can do on Windows. (This likely means you will have separate Tool definition files for Mac and Windows.)

We can call Windows API C functions from Ruby using the Fiddle library.
(Credit Anton whom I believe suggested this workaround in past conversations.)

This is a preliminary version of my KeyTestTool:

For those not wanting to download it, click to expand code viewer ...
# encoding: UTF-8

module Testing

  # RUN ONCE PER SESSION
  unless defined?(@loaded)
    submenu = UI.menu("Plugins").add_submenu("TESTING")
    submenu.add_item("Ruby Tool Keys") {
      Sketchup.active_model.select_tool(KeyTestTool.new)
    }
    @loaded = true
  end


  module KeyInfo

    require 'fiddle' unless defined?(Fiddle)
    require 'fiddle/import' unless defined?(Fiddle::Importer)

    extend Fiddle::Importer
    dlload 'user32.dll'

    # https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getkeystate
    extern 'int GetKeyState(int)'

    # Toggle Keys:
    VK_CAPITAL = 0x14
    VK_NUMLOCK = 0x90
    VK_SCROLL  = 0x91

    # KEY FLAGS - Reference:
    # https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
    #
    KF_EXTENDED  = 0x0100  #    256
    KF_DLGMODE   = 0x0800  #   2048
    KF_MENUMODE  = 0x1000  #   4096
    KF_ALTDOWN   = 0x2000  #   8192
    KF_REPEAT    = 0x4000  #  16384
    KF_UP        = 0x8000  #  32768

    def self.alt_down?(flags)
      # Test whether flags contain KF_ALTDOWN:
      ( flags & KF_ALTDOWN ) == KF_ALTDOWN
    end ###

    def self.caps_toggled?
      # Test whether keystate's low order bit is set (1):
      (self.GetKeyState(VK_CAPITAL) & 0x0001) != 0
    end ###

    def self.control_down?
      # Test whether keystate's high order bit 15 is set (1):
      (self.GetKeyState(VK_CONTROL) & 0x8000) != 0
    end ###

    def self.dialog_mode?(flags)
      # Test whether flags contain KF_DLGMODE:
      ( flags & KF_DLGMODE ) == KF_DLGMODE
    end ###

    def self.extended_key?(flags)
      # Test whether flags contain KF_EXTENDED:
      ( flags & KF_EXTENDED ) == KF_EXTENDED
    end ###

    def self.key_up?(flags)
      # Test whether flags contain KF_UP:
      ( flags & KF_UP ) == KF_UP
    end ###

    def self.menu_mode?(flags)
      # Test whether flags contain KF_MENUMODE:
      ( flags & KF_MENUMODE ) == KF_MENUMODE
    end ###

    def self.numlock_toggled?
      # Test whether keystate's low order bit is set (1):
      (self.GetKeyState(VK_NUMLOCK) & 0x0001) != 0
    end ###

    def self.repeat?(flags)
      # Test whether flags contain KF_REPEAT:
      ( flags & KF_REPEAT ) == KF_REPEAT
    end ###

    def self.scroll_toggled?
      # Test whether keystate's low order bit is set (1):
      (self.GetKeyState(VK_SCROLL) & 0x0001) != 0
    end ###

    def self.shift_down?
      # Test whether keystate's high order bit 15 is set (1):
      (self.GetKeyState(VK_SHIFT) & 0x8000) != 0
    end ###

  end # module

  class KeyTestTool

    def initialize
      @cursor_id = 633 # Select cursor
    end ###

    def onKeyDown(key, repeat, flags, view)
      puts "onKeyDown -  key: #{key}   flags: #{flags}"
      puts "  ALT   : #{KeyInfo.alt_down?(flags)}"
      puts "  CTRL  : #{KeyInfo.control_down?}"
      puts "  SHIFT : #{KeyInfo.shift_down?}"
      puts
      puts "  CAPS    : #{KeyInfo.caps_toggled?}"
      puts "  NUMLOCK : #{KeyInfo.numlock_toggled?}"
      puts "  SCROLL  : #{KeyInfo.scroll_toggled?}"
      return true
    end ###

    def onKeyUp(key, repeat, flags, view)
      puts "onKeyUp   -  key: #{key}   flags: #{flags}"
      puts "  ALT   : #{KeyInfo.alt_down?(flags)}"
      puts "  CTRL  : #{KeyInfo.control_down?}"
      puts "  SHIFT : #{KeyInfo.shift_down?}"
      puts
      puts "  CAPS    : #{KeyInfo.caps_toggled?}"
      puts "  NUMLOCK : #{KeyInfo.numlock_toggled?}"
      puts "  SCROLL  : #{KeyInfo.scroll_toggled?}"
      return true
    end ###

    def onSetCursor
      UI.set_cursor(@cursor_id)
    end ###

  end # KeyTestTool class

end # module Testing

:nerd_face:

1 Like