lib/fusuma/plugin/sendkey/keyboard.rb in fusuma-plugin-sendkey-0.2.1 vs lib/fusuma/plugin/sendkey/keyboard.rb in fusuma-plugin-sendkey-0.3.0

- old
+ new

@@ -1,7 +1,9 @@ # frozen_string_literal: true +require 'linux_input' +require 'fcntl' require_relative './device.rb' module Fusuma module Plugin module Sendkey @@ -10,44 +12,103 @@ def initialize(device: nil) @device = device || Device.new end # @param param [String] - def type_command(param:) + def type(param:) return unless param.is_a?(String) - codes = param.split('+') - press_commands = codes.map { |code| press_command(code) } - release_commands = codes.reverse.map { |code| release_command(code) } + keycodes = split_param(param) - (press_commands | release_commands).join(' && ') + clear_modifiers + keycodes.each { |keycode| key_event(keycode: keycode, press: true) } + key_sync(press: true) + keycodes.reverse.map { |keycode| key_event(keycode: keycode, press: false) } + key_sync(press: false) + device_file.close end - def press_command(code) - return unless support?(code) + # @param param [String] + def valid?(param:) + return unless param.is_a?(String) - @device.emulate(code: code, press: true) + keycodes = split_param(param) + keycodes.all? { |keycode| support?(keycode) } end - def release_command(code) - return unless support?(code) + def device_file + return @device_file if @device_file && !@device_file.closed? - @device.emulate(code: code, press: false) + @device_file = File.open(@device.path, Fcntl::O_WRONLY | Fcntl::O_NDELAY) end - def support?(code) + def key_event(keycode:, press: true) + event = LinuxInput::InputEvent.new + event[:time] = LinuxInput::Timeval.new + event[:time][:tv_sec] = Time.now.to_i + event[:type] = LinuxInput::EV_KEY + event[:code] = keycode_const(keycode) + event[:value] = press ? 1 : 0 + device_file.syswrite(event.pointer.read_bytes(event.size)) + end + + def key_sync(press: true) + event = LinuxInput::InputEvent.new + event[:time] = LinuxInput::Timeval.new + event[:time][:tv_sec] = Time.now.to_i + event[:type] = LinuxInput::EV_SYN + event[:code] = LinuxInput::SYN_REPORT + event[:value] = press ? 1 : 0 + device_file.syswrite(event.pointer.read_bytes(event.size)) + end + + def support?(keycode) @supported_code ||= {} - @supported_code[code] ||= if @device.support?(code) - true - else - warn "sendkey: #{code} is unsupported." - warn 'Please check your config.yml.' - exit 1 - end + @supported_code[keycode] ||= if @device.support?(keycode) + true + else + search_candidates(keycode: keycode) + exit(1) + end end - def available_codes - @device.search_codes + def search_candidates(keycode:) + candidates = search_codes(keycode: keycode) + + warn "Did you mean? #{candidates.join(' / ')}" unless candidates.empty? + warn "sendkey: #{remove_prefix(keycode)} is unsupported." + end + + def search_codes(keycode: nil) + query = keycode&.upcase&.gsub('KEY_', '') + LinuxInput.constants + .select { |c| c[/KEY_.*#{query}.*/] } + .select { |c| @device.support?(c) } + .map { |c| c.to_s.gsub('KEY_', '') } + end + + def keycode_const(keycode) + Object.const_get "LinuxInput::#{keycode}" + end + + def clear_modifiers + modifiers = %w[ CAPSLOCK LEFTALT LEFTCTRL LEFTMETA + LEFTSHIFT RIGHTALT RIGHTCTRL RIGHTSHIFT ] + modifiers.each { |code| key_event(keycode: key_prefix(code), press: false) } + end + + private + + def split_param(param) + param.split('+').map { |code| key_prefix(code) } + end + + def key_prefix(code) + "KEY_#{code.upcase}" + end + + def remove_prefix(keycode) + keycode.gsub('KEY_', '') end end end end end