Class: Fusuma::Plugin::Inputs::Hidraw::HhkbBluetoothParser

Inherits:
Object
  • Object
show all
Defined in:
lib/fusuma/plugin/inputs/hidraw/hhkb_bluetooth_parser.rb

Constant Summary collapse

BASE_TIMEOUT =

Base timeout value for reading reports

0.03
MAX_TIMEOUT =

Maximum timeout value before failure

0.2
MULTIPLIER =

Multiplier to exponentially increase timeout

1.1
MAX_REPORT_SIZE =

Maximum report size in bytes

9

Instance Method Summary collapse

Constructor Details

#initialize(hidraw_device) ⇒ HhkbBluetoothParser

Returns a new instance of HhkbBluetoothParser.

Parameters:



15
16
17
# File 'lib/fusuma/plugin/inputs/hidraw/hhkb_bluetooth_parser.rb', line 15

def initialize(hidraw_device)
  @hidraw_device = hidraw_device
end

Instance Method Details

#parseObject

Parse HID raw device events.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/fusuma/plugin/inputs/hidraw/hhkb_bluetooth_parser.rb', line 20

def parse
  File.open(@hidraw_device.hidraw_path, "rb") do |device|
    timeout = nil

    # Continuously read reports from the device.
    while (report = read_with_timeout(device, timeout))
      mouse_state = if report.empty?
        # Handle timeout case
        :end
      else
        case parse_hid_report(report)
        when :mouse
          case mouse_state
          when :begin, :update
            :update
          else
            :begin
          end
        when :keyboard
          # Continue mouse_state when keyboard operation
          mouse_state
        else
          :end
        end
      end

      case mouse_state
      when :begin, :update
        timeout = update_timeout(timeout)
      when :end
        timeout = nil
      end

      yield mouse_state
    end
  end
end

#parse_hid_report(report_bytes) ⇒ Symbol?

Parse the HID report to determine its type.

Parameters:

  • report_bytes (String)

    the HID report as byte data

Returns:

  • (Symbol, nil)

    symbol indicating type of report or nil on error



81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/fusuma/plugin/inputs/hidraw/hhkb_bluetooth_parser.rb', line 81

def parse_hid_report(report_bytes)
  report_id = report_bytes.getbyte(0)
  case report_id
  when 1
    # parse_mouse_report(report_bytes)
    :mouse
  when 127
    # parse_keyboard_report(report_bytes)
    :keyboard
  else
    MultiLogger.warn "Unknown Report ID: #{report_id}"
    nil
  end
end

#parse_keyboard_report(report_bytes) ⇒ Object

Parse keyboard report data.

Parameters:

  • report_bytes (String)

    the HID keyboard report as byte data



115
116
117
118
119
120
121
122
123
124
# File 'lib/fusuma/plugin/inputs/hidraw/hhkb_bluetooth_parser.rb', line 115

def parse_keyboard_report(report_bytes)
  report_id, modifiers, _reserved1, *keys = report_bytes.unpack("CCCC6") # Retrieve 9-byte report
  # - `C`: 1 byte unsigned integer (report ID) (0..255)
  # - `C`: 1 byte unsigned integer (modifier keys) (0..255)
  # - `C`: 1 byte reserved (0)
  # - `C`: 6 bytes of keycodes (0..255)
  modifier_states = %w[LeftControl LeftShift LeftAlt LeftGUI RightControl RightShift RightAlt RightGUI].map.with_index { |m, i| "#{m}: #{((modifiers & (1 << i)) != 0) ? 1 : 0}" }
  keys_output = keys.map { |key| (key == 0) ? "0x70000" : translate_keycode(key) }
  puts "# ReportID: #{report_id} / #{modifier_states.join(" | ")} | Keyboard #{keys_output}"
end

#parse_mouse_report(report_bytes) ⇒ Object

Parse mouse report data.

Parameters:

  • report_bytes (String)

    the HID mouse report as byte data



98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/fusuma/plugin/inputs/hidraw/hhkb_bluetooth_parser.rb', line 98

def parse_mouse_report(report_bytes)
  puts "Raw bytes: #{report_bytes.inspect}" # Display raw byte bytes

  report_id, buttons, x, y, wheel, ac_pan = report_bytes.unpack("CCcccc") # Retrieve 6-byte report
  # - `C`: 1 byte unsigned integer (report ID) (0..255)
  # - `C`: 1 byte unsigned integer (button state) (0..255)
  # - `c`: 1 byte signed integer (x-axis) (-128..127)
  # - `c`: 1 byte signed integer (y-axis) (-128..127)
  # - `c`: 1 byte signed integer (wheel) (-128..127)
  # - `c`: 1 byte signed integer (AC pan) (-128..127)
  button_states = buttons.to_s(2).rjust(8, "0").chars.map(&:to_i)

  puts "# ReportID: #{report_id} / Button: #{button_states.join(" ")} | X: #{x.to_s.rjust(4)} | Y: #{y.to_s.rjust(4)} | Wheel: #{wheel.to_s.rjust(4)} | AC Pan: #{ac_pan.to_s.rjust(4)}"
end

#read_with_timeout(device, timeout) ⇒ String

Reads the HID report from the device with a timeout.

Parameters:

  • device (File)

    the opened device file

  • timeout (Float)

    the timeout duration

Returns:

  • (String)

    the HID report as bytes or an empty string on timeout



62
63
64
65
66
67
# File 'lib/fusuma/plugin/inputs/hidraw/hhkb_bluetooth_parser.rb', line 62

def read_with_timeout(device, timeout)
  # puts "Timeout: #{timeout}"  # Log timeout for debugging
  Timeout.timeout(timeout) { device.read(MAX_REPORT_SIZE) }
rescue Timeout::Error
  ""
end

#translate_keycode(keycode) ⇒ String

Translate keycode to its string representation.

Parameters:

  • keycode (Integer)

    the keycode to translate

Returns:

  • (String)

    the string representation of the keycode



129
130
131
132
133
134
135
136
137
138
# File 'lib/fusuma/plugin/inputs/hidraw/hhkb_bluetooth_parser.rb', line 129

def translate_keycode(keycode)
  # Map of keycodes to their respective characters
  keycodes = {
    4 => "a and A", 7 => "d and D", 16 => "s and S", 19 => "w and W",
    9 => "f and F", 10 => "g and G", 14 => "j and J", 15 => "k and K",
    33 => "[ and {", 47 => "] and }"
    # Add more as needed
  }
  keycodes[keycode] || "0x#{keycode.to_s(16)}"  # Return hexadecimal if not found
end

#update_timeout(timeout) ⇒ Float

Update the timeout based on previous value.

Parameters:

  • timeout (Float, nil)

    previously set timeout

Returns:

  • (Float)

    the updated timeout value



72
73
74
75
76
# File 'lib/fusuma/plugin/inputs/hidraw/hhkb_bluetooth_parser.rb', line 72

def update_timeout(timeout)
  return BASE_TIMEOUT if timeout.nil?

  [timeout * MULTIPLIER, MAX_TIMEOUT].min
end