Class: Fusuma::Plugin::Sendkey::Keyboard

Inherits:
Object
  • Object
show all
Defined in:
lib/fusuma/plugin/sendkey/keyboard.rb

Overview

Emulate Keyboard

Constant Summary collapse

KEY_INTERVAL =
0.03
MODIFIER_KEY_CODES =
%w[
  KEY_CAPSLOCK
  KEY_LEFTALT
  KEY_LEFTCTRL
  KEY_LEFTMETA
  KEY_LEFTSHIFT
  KEY_RIGHTALT
  KEY_RIGHTCTRL
  KEY_RIGHTSHIFT
  KEY_RIGHTMETA
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(device:) ⇒ Keyboard

Returns a new instance of Keyboard.



47
48
49
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 47

def initialize(device:)
  @device = device
end

Class Method Details

.find_device(name_patterns:) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 27

def self.find_device(name_patterns:)
  Fusuma::Device.reset

  Array(name_patterns).each do |name_pattern|
    fusuma_device = Fusuma::Device.all.find { |d|
      next unless d.capabilities.include?("keyboard")

      d.name.match(/#{name_pattern}/)
    }

    if fusuma_device
      MultiLogger.info "sendkey: Keyboard: #{fusuma_device.name}"
      return Device.new(path: "/dev/input/#{fusuma_device.id}")
    end
    warn "sendkey: Keyboard: /#{name_pattern}/ is not found"
  end

  exit(1)
end

Instance Method Details

#capabilitiesObject



137
138
139
140
141
142
143
144
145
146
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 137

def capabilities
  return @capabilities if defined?(@capabilities)

  @capabilities = Set.new.tap do |set|
    @device.reload_capability.each do |id|
      code_sym = Revdev::REVERSE_MAPS[:KEY][id]
      set << code_sym if code_sym
    end
  end
end

#clear_modifiers(keycodes) ⇒ Object

Parameters:

  • keycodes (Array<String>)

    to be released



177
178
179
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 177

def clear_modifiers(keycodes)
  keycodes.each { |code| send_event(code: code, press: false) }
end

#find_code(code) ⇒ Object



165
166
167
168
169
170
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 165

def find_code(code)
  result = capabilities.find { |c| c == code.to_sym }

  warn_undefined_codes(code) unless result
  result
end

#key_syncObject



127
128
129
130
131
132
133
134
135
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 127

def key_sync
  event = Revdev::InputEvent.new(
    nil,
    Revdev.const_get(:EV_SYN),
    Revdev.const_get(:SYN_REPORT),
    0
  )
  @device.write_event(event)
end

#keycode_const(keycode) ⇒ Object



172
173
174
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 172

def keycode_const(keycode)
  Object.const_get "Revdev::#{keycode}"
end

#keydown(keycode) ⇒ Object



91
92
93
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 91

def keydown(keycode)
  send_event(code: keycode, press: true)
end

#keyup(keycode) ⇒ Object



95
96
97
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 95

def keyup(keycode)
  send_event(code: keycode, press: false)
end

#param_to_codes(param) ⇒ Array<String>

Parameters:

  • (String)

Returns:

  • (Array<String>)


183
184
185
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 183

def param_to_codes(param)
  param.split("+").map { |keyname| add_prefix(keyname) }
end

#search_codes(code) ⇒ Object



161
162
163
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 161

def search_codes(code)
  capabilities.select { |c| c[code] }
end

#send_event(code:, press: true) ⇒ Object



117
118
119
120
121
122
123
124
125
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 117

def send_event(code:, press: true)
  event = Revdev::InputEvent.new(
    nil,
    Revdev.const_get(:EV_KEY),
    Revdev.const_get(code),
    press ? 1 : 0
  )
  @device.write_event(event)
end

#support?(code) ⇒ Boolean

Returns:

  • (Boolean)


148
149
150
151
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 148

def support?(code)
  @supported_code ||= {}
  @supported_code[code] ||= find_code(code)
end

#type(param:, keep: "", clear: :none) ⇒ Object

Parameters:

  • param (String)

    key names separated by ‘+’ to type

  • keep (String) (defaults to: "")

    key names separated by ‘+’ to keep

  • clear (String, Symbol, TrueClass) (defaults to: :none)

    key names separated by ‘+’ to clear or :all to release all modifiers



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 68

def type(param:, keep: "", clear: :none)
  return unless param.is_a?(String)

  param_keycodes = param_to_codes(param)
  type_keycodes = param_keycodes - param_to_codes(keep)

  clear_keycodes =
    case clear
    when true
      MODIFIER_KEY_CODES
    when :none, false
      []
    else
      # release keys specified by clearmodifiers
      param_to_codes(clear)
    end

  clear_modifiers(clear_keycodes - param_keycodes)

  type_keycodes.each { |keycode| keydown(keycode) && key_sync && sleep(KEY_INTERVAL) }
  type_keycodes.reverse_each { |keycode| keyup(keycode) && key_sync && sleep(KEY_INTERVAL) }
end

#types(args) ⇒ Object

Parameters:

  • params (Array)


52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 52

def types(args)
  return unless args.is_a?(Array)

  args.each do |arg|
    case arg
    when String
      type(param: arg)
    when Hash
      type(**arg)
    end
  end
end

#valid?(params) ⇒ TrueClass, FalseClass

Parameters:

  • param (String)

Returns:

  • (TrueClass, FalseClass)


101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 101

def valid?(params)
  return false if params.nil?

  case params
  when Array
    params.all? { |param| valid?(param) }
  when String
    param = params
    keycodes = param_to_codes(param)
    keycodes.all? { |keycode| support?(keycode) }
  else
    MultiLogger.error "sendkey: Invalid config: #{params}"
    nil
  end
end

#warn_undefined_codes(code) ⇒ Object



153
154
155
156
157
158
159
# File 'lib/fusuma/plugin/sendkey/keyboard.rb', line 153

def warn_undefined_codes(code)
  candidates = search_codes(code).map { |c| remove_prefix(c.to_s) }

  warn "Did you mean? #{candidates.join(" / ")}" unless candidates.empty?

  warn "sendkey: #{remove_prefix(code)} is unsupported."
end