rkremap 0.2.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rkremap/evdev.rb CHANGED
@@ -1,33 +1,13 @@
1
1
  class Rkremap
2
- EV_KEY = 1
3
- EV_SYN = 0
4
- SYN_REPORT = 0
5
-
6
2
  # https://www.kernel.org/doc/html/v4.12/input/input_uapi.html
7
3
  class Evdev
8
4
  EVIOCGBIT_ANY = 2147566880 # EVIOCGBIT(0, 1)
9
- EVIOCGBIT_EV_KEY = 2153792801 # EVIOCGBIT(EV_KEY, (KEY_MAX-1)/8+1)
5
+ EVIOCGBIT_EV_KEY = 2153792801 # EVIOCGBIT(EV_KEY, (KEY_CNT-1)/8+1)
10
6
  EVIOCGRAB = 1074021776
7
+ EVIOCGNAME = 2164278534 # EVIOCGNAME(256)
11
8
 
12
- @io2evdev = {}
13
- def self.io2evdev
14
- @io2evdev
15
- end
16
-
17
- # @return [Array<Evdev>]
18
- def self.devices
19
- Dir.children("/dev/input").map do |dev|
20
- dev =~ /\Aevent\d+\z/ ? Evdev.new("/dev/input/#{dev}") : nil
21
- end.compact
22
- end
23
-
24
- # @param inputs [Array<Evdev>]
25
- # @return [Evdev]
26
- def self.select(inputs)
27
- h = inputs.map{|ev| [ev.io, ev]}.to_h
28
- r, = IO.select(inputs.map(&:io), nil, nil, nil)
29
- @io2evdev[r[0]]
30
- end
9
+ # @return [String]
10
+ attr_reader :path
31
11
 
32
12
  # @param path [String]
33
13
  def initialize(path)
@@ -35,14 +15,25 @@ class Rkremap
35
15
  @io = nil
36
16
  @capa = nil
37
17
  @uinput = nil
18
+ @grab = false
19
+ end
20
+
21
+ def eql?(other)
22
+ other.is_a?(Evdev) && self.path == other.path
38
23
  end
39
24
 
40
25
  # @return [IO]
41
26
  def io
42
27
  return @io if @io
43
28
  @io = File.open(@path)
44
- self.class.io2evdev[@io] = self
45
- @io
29
+ end
30
+
31
+ # @return [String]
32
+ def name
33
+ return @name if @name
34
+ buf = ''
35
+ io.ioctl(EVIOCGNAME, buf)
36
+ @name = buf.sub(/\0+$/, '')
46
37
  end
47
38
 
48
39
  def close
@@ -62,15 +53,37 @@ class Rkremap
62
53
 
63
54
  # @return [Boolean]
64
55
  def keyboard?
65
- buf = ' '
56
+ return @is_keyboard unless @is_keyboard.nil?
57
+ @is_keyboard = false
58
+ return false if name =~ /\Arkremap\0*\z/
59
+
60
+ buf = ''
61
+ io.ioctl(EVIOCGBIT_ANY, buf)
62
+ return false if buf.unpack1('C')[EV_KEY] == 0
63
+ @is_keyboard = capable?(KeyCode::KEY_0) && capable?(KeyCode::KEY_9) &&
64
+ capable?(KeyCode::KEY_A) && capable?(KeyCode::KEY_Z) && capable?(KeyCode::KEY_SPACE)
65
+ end
66
+
67
+ # @return [Boolean]
68
+ def mouse?
69
+ return @is_mouse unless @is_mouse.nil?
70
+ @is_mouse = false
71
+ return false if name =~ /\Arkremap\0*\z/
72
+
73
+ buf = ''
66
74
  io.ioctl(EVIOCGBIT_ANY, buf)
67
75
  return false if buf.unpack1('C')[EV_KEY] == 0
68
- capable?(KeyCode::KEY_0) && capable?(KeyCode::KEY_9) &&
69
- capable?(KeyCode::KEY_A) && capable?(KeyCode::KEY_Z) && capable?(KeyCode::KEY_SPACE)
76
+ @is_mouse = capable?(KeyCode::BTN_MOUSE)
70
77
  end
71
78
 
72
79
  def grab
73
80
  io.ioctl(EVIOCGRAB, 1)
81
+ @grab = true
82
+ end
83
+
84
+ # @return [Boolean]
85
+ def grab?
86
+ @grab
74
87
  end
75
88
 
76
89
  # struct input_event {
@@ -86,39 +99,14 @@ class Rkremap
86
99
  time = Time.at(sec, usec)
87
100
  return time, type, code, value
88
101
  end
89
- end
90
-
91
- # https://www.kernel.org/doc/html/v4.12/input/uinput.html
92
- class Uinput
93
- UI_SET_EVBIT = 1074025828
94
- UI_SET_KEYBIT = 1074025829
95
- UI_DEV_SETUP = 1079792899
96
- UI_DEV_CREATE = 21761
97
-
98
- def initialize
99
- @dev = File.open('/dev/uinput', 'w')
100
- setup
101
- end
102
-
103
- def setup
104
- @dev.ioctl(UI_SET_EVBIT, EV_KEY)
105
- KeyCode::KEY_CNT.times{|k| @dev.ioctl(UI_SET_KEYBIT, k)}
106
- bustype = 0x03 # BUS_USB
107
- vendor = 0x1234 # てきとー
108
- product = 0x5678 # てきとー
109
- version = 1 # てきとー
110
- name = 'rkremap'
111
- ff_efects_max = 0
112
- setup = [bustype, vendor, product, version, name, ff_efects_max].pack("SSSSZ80L") # struct uinput_setup
113
- @dev.ioctl(UI_DEV_SETUP, setup)
114
- @dev.ioctl(UI_DEV_CREATE)
115
- end
116
102
 
117
- # @param type [Integer] EV_KEY/ EV_SYN
118
- # @param code [Integer] キーコード / SYN_REPORT
119
- # @param value [Integer] 0:release / 1:press / 2:repeat
120
- def write_event(type, code, value)
121
- @dev.syswrite(['', type, code, value].pack('a16SSl'))
103
+ # @param pattern [Symbol, Regexp]
104
+ def match?(pattern)
105
+ return true if pattern == true
106
+ return true if pattern == :keyboard && keyboard?
107
+ return true if pattern == :mouse && mouse?
108
+ return true if pattern.is_a?(Regexp) && pattern =~ name
109
+ return false
122
110
  end
123
111
  end
124
112
  end
@@ -0,0 +1,84 @@
1
+ require 'tmtms/timer'
2
+
3
+ class Rkremap
4
+ class EvdevList
5
+ include Enumerable
6
+
7
+ attr_reader :device_list
8
+ attr_accessor :grab
9
+
10
+ # @param device_files [Array<String>] path list (/dev/input/event*)
11
+ # @param auto_detect [Boolean]
12
+ # @param detect_mouse [Boolean]
13
+ # @param exclude [Regexp]
14
+ def initialize(device_files, auto_detect: false, detect_mouse: false, exclude: nil)
15
+ @device_files = device_files
16
+ @auto_detect = auto_detect
17
+ @detect_mouse = detect_mouse
18
+ @exclude = exclude
19
+ @io2evdev = {} # {IO => Evdev}
20
+ @device_list = {} # {path => Evdev}
21
+ @prev_devices = []
22
+ end
23
+
24
+ def each(&block)
25
+ @device_list.each_value do |evdev|
26
+ block.call evdev
27
+ end
28
+ end
29
+
30
+ def detect_loop
31
+ detect
32
+ timer = Tmtms::Timer.new
33
+ timer.repeat(3) do
34
+ detect
35
+ end
36
+ end
37
+
38
+ def detect
39
+ devs = Dir.glob("/dev/input/event*")
40
+ (@device_list.keys - devs).each{|path| remove_device(path)}
41
+ new_devs = devs - @prev_devices
42
+ @prev_devices = devs
43
+ new_devs &= @device_files unless @auto_detect
44
+ new_devs -= @device_list.keys
45
+ new_devs.each do |path|
46
+ ev = Evdev.new(path)
47
+ next if @exclude && ev.name =~ @exclude
48
+ if ev.keyboard? || (@detect_mouse && ev.mouse?)
49
+ ev.grab if ev.match?(@grab)
50
+ puts "detect: #{ev.name} (#{ev.path}) #{ev.grab? ? 'grab' : 'not grab'}" if $VERBOSE
51
+ @io2evdev[ev.io] = ev
52
+ @device_list[path] = ev
53
+ end
54
+ end
55
+ end
56
+
57
+ # @param evdev [path]
58
+ def remove_device(path)
59
+ evdev = @device_list[path]
60
+ @io2evdev.delete evdev.io
61
+ evdev.close
62
+ @device_list.delete path
63
+ end
64
+
65
+ # @param timeout [Numeric]
66
+ # @return [Evdev]
67
+ def select(timeout)
68
+ r, = IO.select(@device_list.values.map(&:io), nil, nil, timeout)
69
+ r && @io2evdev[r[0]]
70
+ end
71
+
72
+ def read_event
73
+ while true
74
+ evdev = select(3)
75
+ next unless evdev
76
+ begin
77
+ return Event.new(evdev)
78
+ rescue Errno::ENODEV
79
+ remove_device(evdev.path)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,58 @@
1
+ class Rkremap
2
+ class Event
3
+ Raw = Struct.new(:time, :type, :code, :value)
4
+
5
+ attr_accessor :app
6
+ attr_reader :raw, :ev_type
7
+ attr_writer :code
8
+
9
+ # @param evdev [Rkremap::Evdev]
10
+ # @param app [Rkremap::App]
11
+ def initialize(evdev)
12
+ @evdev = evdev
13
+ @raw = []
14
+ while true
15
+ s = evdev.read_event
16
+ @raw.push Raw.new(*s)
17
+ break if s[1] == EV_SYN
18
+ end
19
+ @ev_type = (@raw.find{_1[1] != EV_MSC && _1[1] != EV_SYN} || @raw.first)[1]
20
+ end
21
+
22
+ # @return [Rkremap::Event::Raw]
23
+ def key
24
+ @key ||= @raw.find{_1.type == EV_KEY}
25
+ end
26
+
27
+ def device
28
+ @evdev
29
+ end
30
+
31
+ def code
32
+ @code ||= key.code
33
+ end
34
+
35
+ def value
36
+ key.value
37
+ end
38
+
39
+ def type
40
+ @type ||= value == 1 ? :press : value == 0 ? :release : :repeat
41
+ end
42
+
43
+ def match?(device:, code:, type:, app:)
44
+ (device.nil? || self.device.match?(device)) &&
45
+ (code.nil? || Array(code).include?(self.code)) &&
46
+ (type.nil? || Array(type).include?(self.type)) &&
47
+ (app.nil? || @app.nil? || @app.match?(app))
48
+ end
49
+
50
+ def skip
51
+ @skipped = true
52
+ end
53
+
54
+ def skipped?
55
+ @skipped
56
+ end
57
+ end
58
+ end
@@ -249,6 +249,87 @@ class Rkremap
249
249
  KEY_WIMAX = KEY_WWAN
250
250
  KEY_RFKILL = 247 # Key that controls all radios
251
251
  KEY_MICMUTE = 248 # Mute / unmute the microphone
252
+
253
+ BTN_MISC = 0x100
254
+ BTN_0 = 0x100
255
+ BTN_1 = 0x101
256
+ BTN_2 = 0x102
257
+ BTN_3 = 0x103
258
+ BTN_4 = 0x104
259
+ BTN_5 = 0x105
260
+ BTN_6 = 0x106
261
+ BTN_7 = 0x107
262
+ BTN_8 = 0x108
263
+ BTN_9 = 0x109
264
+
265
+ BTN_LEFT = 0x110
266
+ BTN_MOUSE = 0x110
267
+ BTN_RIGHT = 0x111
268
+ BTN_MIDDLE = 0x112
269
+ BTN_SIDE = 0x113
270
+ BTN_EXTRA = 0x114
271
+ BTN_FORWARD = 0x115
272
+ BTN_BACK = 0x116
273
+ BTN_TASK = 0x117
274
+
275
+ BTN_JOYSTICK = 0x120
276
+ BTN_TRIGGER = 0x120
277
+ BTN_THUMB = 0x121
278
+ BTN_THUMB2 = 0x122
279
+ BTN_TOP = 0x123
280
+ BTN_TOP2 = 0x124
281
+ BTN_PINKIE = 0x125
282
+ BTN_BASE = 0x126
283
+ BTN_BASE2 = 0x127
284
+ BTN_BASE3 = 0x128
285
+ BTN_BASE4 = 0x129
286
+ BTN_BASE5 = 0x12a
287
+ BTN_BASE6 = 0x12b
288
+ BTN_DEAD = 0x12f
289
+
290
+ BTN_GAMEPAD = 0x130
291
+ BTN_SOUTH = 0x130
292
+ BTN_A = BTN_SOUTH
293
+ BTN_EAST = 0x131
294
+ BTN_B = BTN_EAST
295
+ BTN_C = 0x132
296
+ BTN_NORTH = 0x133
297
+ BTN_X = BTN_NORTH
298
+ BTN_WEST = 0x134
299
+ BTN_Y = BTN_WEST
300
+ BTN_Z = 0x135
301
+ BTN_TL = 0x136
302
+ BTN_TR = 0x137
303
+ BTN_TL2 = 0x138
304
+ BTN_TR2 = 0x139
305
+ BTN_SELECT = 0x13a
306
+ BTN_START = 0x13b
307
+ BTN_MODE = 0x13c
308
+ BTN_THUMBL = 0x13d
309
+ BTN_THUMBR = 0x13e
310
+
311
+ BTN_DIGI = 0x140
312
+ BTN_TOOL_PEN = 0x140
313
+ BTN_TOOL_RUBBER = 0x141
314
+ BTN_TOOL_BRUSH = 0x142
315
+ BTN_TOOL_PENCIL = 0x143
316
+ BTN_TOOL_AIRBRUSH = 0x144
317
+ BTN_TOOL_FINGER = 0x145
318
+ BTN_TOOL_MOUSE = 0x146
319
+ BTN_TOOL_LENS = 0x147
320
+ BTN_TOOL_QUINTTAP = 0x148 # Five fingers on trackpad
321
+ BTN_STYLUS3 = 0x149
322
+ BTN_TOUCH = 0x14a
323
+ BTN_STYLUS = 0x14b
324
+ BTN_STYLUS2 = 0x14c
325
+ BTN_TOOL_DOUBLETAP = 0x14d
326
+ BTN_TOOL_TRIPLETAP = 0x14e
327
+ BTN_TOOL_QUADTAP = 0x14f # Four fingers on trackpad
328
+
329
+ BTN_WHEEL = 0x150
330
+ BTN_GEAR_DOWN = 0x150
331
+ BTN_GEAR_UP = 0x151
332
+
252
333
  KEY_OK = 0x160
253
334
  KEY_SELECT = 0x161
254
335
  KEY_GOTO = 0x162
@@ -502,7 +583,6 @@ class Rkremap
502
583
 
503
584
  CODE_KEY = {}
504
585
  KeyCode.constants.each do |k|
505
- next unless k =~ /\AKEY_/
506
586
  code = KeyCode.const_get(k)
507
587
  CODE_KEY[code] = k unless CODE_KEY[code]
508
588
  end
@@ -0,0 +1,40 @@
1
+ class Rkremap
2
+ # https://www.kernel.org/doc/html/v4.12/input/uinput.html
3
+ class Uinput
4
+ UI_SET_EVBIT = 1074025828
5
+ UI_SET_KEYBIT = 1074025829
6
+ UI_SET_RELBIT = 1074025830
7
+ UI_DEV_SETUP = 1079792899
8
+ UI_DEV_CREATE = 21761
9
+
10
+ REL_CNT = 0x10
11
+
12
+ def initialize
13
+ @dev = File.open('/dev/uinput', 'w')
14
+ setup
15
+ end
16
+
17
+ def setup
18
+ @dev.ioctl(UI_SET_EVBIT, EV_KEY)
19
+ KeyCode::KEY_CNT.times{|k| @dev.ioctl(UI_SET_KEYBIT, k)}
20
+ @dev.ioctl(UI_SET_EVBIT, EV_REL)
21
+ REL_CNT.times{|r| @dev.ioctl(UI_SET_RELBIT, r)}
22
+ bustype = 0x03 # BUS_USB
23
+ vendor = 0x1234 # てきとー
24
+ product = 0x5678 # てきとー
25
+ version = 1 # てきとー
26
+ name = 'rkremap'
27
+ ff_efects_max = 0
28
+ setup = [bustype, vendor, product, version, name, ff_efects_max].pack("SSSSZ80L") # struct uinput_setup
29
+ @dev.ioctl(UI_DEV_SETUP, setup)
30
+ @dev.ioctl(UI_DEV_CREATE)
31
+ end
32
+
33
+ # @param type [Integer] EV_KEY/ EV_SYN
34
+ # @param code [Integer] キーコード / SYN_REPORT
35
+ # @param value [Integer] 0:release / 1:press / 2:repeat
36
+ def write_event(type, code, value)
37
+ @dev.syswrite(['', type, code, value].pack('a16SSl'))
38
+ end
39
+ end
40
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Rkremap
4
- VERSION = "0.2.1"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -46,11 +46,14 @@ class Rkremap
46
46
  # @return [String]
47
47
  def app_title(window)
48
48
  win = app_win(window)[0]
49
+ return '' if win == 0
49
50
  prop = X11::XTextProperty.malloc(Fiddle::RUBY_FREE)
50
51
  text_list = X11::Pointer.malloc(Fiddle::RUBY_FREE)
51
52
  X11.XGetWMName(@display, win, prop)
52
53
  X11.Xutf8TextPropertyToTextList(@display, prop, text_list, @buf)
53
- title = text_list.ptr.ptr.to_s.force_encoding('utf-8')
54
+ ptr = text_list.ptr
55
+ return '' if ptr.null?
56
+ title = ptr.ptr.to_s.force_encoding('utf-8')
54
57
  X11.XFreeStringList(text_list.ptr)
55
58
  title
56
59
  end
data/lib/rkremap.rb CHANGED
@@ -1,13 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "rkremap/version"
3
+ require_relative 'rkremap/version'
4
4
  require_relative 'rkremap/keycode'
5
5
  require_relative 'rkremap/winattr'
6
+ require_relative 'rkremap/evdev_list'
6
7
  require_relative 'rkremap/evdev'
8
+ require_relative 'rkremap/event'
9
+ require_relative 'rkremap/uinput'
7
10
 
8
11
  class Rkremap
9
12
  include KeyCode
10
13
 
14
+ # /usr/include/linux/input-event-codes.h
15
+ EV_SYN = 0
16
+ EV_KEY = 1
17
+ EV_REL = 2
18
+ EV_ABS = 3
19
+ EV_MSC = 4
20
+ SYN_REPORT = 0
21
+
11
22
  EVENT_TYPE_VALUE = {
12
23
  release: 0,
13
24
  press: 1,
@@ -17,12 +28,22 @@ class Rkremap
17
28
  attr_accessor :grab
18
29
  attr_accessor :modifiers
19
30
  attr_accessor :x11
31
+ attr_accessor :auto_detect
32
+ attr_accessor :exclude
33
+ attr_accessor :mouse
20
34
 
21
35
  # @param devices [Array<String>, String]
22
- def initialize(devices=nil)
23
- devices = Array(devices) if devices.is_a? String
24
- @inputs = devices ? devices.map{|d| Evdev.new(d)} : Evdev.devices.select(&:keyboard?)
25
- raise 'Unable to detect keyboard device' if @inputs.empty?
36
+ # @param exclude [Regexp]
37
+ # @param mouse [Boolean]
38
+ def initialize(devices=[], exclude: nil, mouse: false)
39
+ if devices.empty?
40
+ @auto_detect = true
41
+ else
42
+ devices = Array(devices)
43
+ end
44
+ @devices = devices
45
+ @exclude = exclude
46
+ @mouse = mouse
26
47
  @uinput = Uinput.new
27
48
  @grab = false
28
49
  @x11 = false
@@ -32,34 +53,45 @@ class Rkremap
32
53
  KEY_LEFTALT, KEY_RIGHTALT,
33
54
  KEY_LEFTMETA, KEY_RIGHTMETA,
34
55
  ]
35
- @events = []
56
+ @event_procs = []
36
57
  @mutex = Mutex.new
37
58
  end
38
59
 
60
+ # @param device [Symbol, Regexp]
39
61
  # @param code [Integer]
40
62
  # @param type [Symbol] :press / :release / :repeat
41
- def match(code: nil, type: nil, &block)
42
- @events.push [{code: code, type: type}, block]
63
+ # @param app [Regexp, String]
64
+ def match(device: nil, code: nil, type: nil, app: nil, &block)
65
+ @event_procs.push [{device: device, code: code, type: type, app: app}, block]
43
66
  end
44
67
 
45
68
  def start(&block)
46
- @mod_state = @modifiers.map{|m| [m, false]}.to_h
47
- @winattr = WinAttr.new if @x11
48
- @inputs.each(&:grab) if @grab
69
+ @evdev_list = EvdevList.new(@devices, auto_detect: @auto_detect, exclude: @exclude, detect_mouse: @mouse)
70
+ @evdev_list.grab = @grab
71
+ @evdev_list.detect_loop
72
+ @mod_state = @modifiers.map.to_h{|m| [m, false]}
73
+ winattr = WinAttr.new if @x11
49
74
  while true
50
75
  @keys = []
51
- input = Evdev.select(@inputs)
52
- time, type, code, value = input.read_event
53
- next if type != EV_KEY
54
- event = Event.new(time, code, value, @winattr)
55
- @events.each do |cond, b|
56
- synchronize{ b.call event } if event.match?(**cond)
57
- break if event.skipped
76
+ event = @evdev_list.read_event
77
+ if event.ev_type != EV_KEY
78
+ if event.device.grab?
79
+ event.raw.each do |r|
80
+ @uinput.write_event(r.type, r.code, r.value)
81
+ end
82
+ end
83
+ next
58
84
  end
59
- next if event.skipped
60
- synchronize{ proc_event(code, value, event) }
61
- @keys.each do |c, mod, app|
62
- synchronize{ block.call(c, mod, app) } if block
85
+ app = App.new(winattr) if winattr
86
+ event.app = app
87
+ @event_procs.each do |cond, b|
88
+ synchronize{ b.call(event, @mod_state.dup, app) } if event.match?(**cond)
89
+ break if event.skipped?
90
+ end
91
+ next if event.skipped?
92
+ synchronize{ proc_event(event, app) }
93
+ @keys.each do |c, mod, app_|
94
+ synchronize{ block.call(c, mod, app_) } if block
63
95
  end
64
96
  end
65
97
  end
@@ -70,20 +102,27 @@ class Rkremap
70
102
 
71
103
  # @param code [Integer]
72
104
  # @param type [Symbol] :press / :release / :repeat
73
- # @param ev [Rkmap::Event]
74
- def event(code: nil, type: nil, event: nil)
105
+ def event(code: nil, type: nil)
75
106
  value = EVENT_TYPE_VALUE[type] or raise "invalid type: #{type.inspect}"
76
- proc_event(code, value, event)
107
+ update_modifiers(code, value)
108
+ write_event(code, value)
77
109
  end
78
110
 
79
111
  # @param code [Integer]
80
112
  # @param mod [Hash] {MOD_KEY => true, ...}
81
113
  def key(code, mod={})
114
+ with_modifier(mod) do
115
+ write_event(code, 1)
116
+ write_event(code, 0)
117
+ end
118
+ end
119
+
120
+ # @param mod [Hash] {MOD_KEY => true, ...}
121
+ def with_modifier(mod, &block)
82
122
  mod_diff(mod).each do |mcode, state|
83
123
  write_event(mcode, state ? 1 : 0)
84
124
  end
85
- write_event(code, 1)
86
- write_event(code, 0)
125
+ block.call
87
126
  mod_diff(mod).each do |mcode, _|
88
127
  write_event(mcode, @mod_state[mcode] ? 1 : 0)
89
128
  end
@@ -91,12 +130,23 @@ class Rkremap
91
130
 
92
131
  private
93
132
 
94
- def proc_event(code, value, event)
133
+ def update_modifiers(code, value)
95
134
  if @mod_state.include?(code)
96
135
  @mod_state[code] = value != 0
136
+ end
137
+ end
138
+
139
+ # @param event [Rkremap::Event]
140
+ # @param app [Rkremap::App]
141
+ def proc_event(event, app)
142
+ code, value = event.code, event.value
143
+ if @mod_state.include?(code)
144
+ write_event(code, value)
145
+ update_modifiers(code, value)
146
+ elsif value == 0
97
147
  write_event(code, value)
98
- elsif value != 0
99
- @keys.push [code, @mod_state.dup, App.new(event&.win, @winattr)]
148
+ else
149
+ @keys.push [code, @mod_state.dup, app]
100
150
  end
101
151
  end
102
152
 
@@ -111,43 +161,44 @@ class Rkremap
111
161
  @uinput.write_event(EV_SYN, SYN_REPORT, 0)
112
162
  end
113
163
 
114
- class Event
115
- attr_reader :time
116
- attr_reader :code
117
- attr_reader :type
118
- attr_reader :win
119
- attr_reader :skipped
120
-
121
- def initialize(time, code, value, winattr)
122
- @time = time
123
- @code = code
124
- @value = value
125
- @type = value == 1 ? :press : value == 0 ? :release : :repeat
126
- @win = winattr&.focus_win
127
- @skipped = false
128
- end
129
-
130
- def match?(code: nil, type: nil)
131
- (code.nil? || Array(code).include?(@code)) && (type.nil? || Array(type).include?(@type))
132
- end
133
-
134
- def skip
135
- @skipped = true
136
- end
137
- end
138
-
139
164
  class App
140
- def initialize(win, winattr)
141
- @win = win
165
+ # @param winattr [Rkremap::WinAttr]
166
+ def initialize(winattr)
142
167
  @winattr = winattr
168
+ @win = winattr.focus_win
143
169
  end
144
170
 
171
+ # @return [String, nil]
145
172
  def class_name
146
173
  @winattr.app_win(@win)[2] if @winattr && @win
147
174
  end
148
175
 
176
+ # @return [String, nil]
149
177
  def title
150
178
  @winattr.app_title(@win) if @winattr && @win
151
179
  end
180
+
181
+ # @param app [Array, Hash, String, Regexp]
182
+ # @return [Boolean]
183
+ def match?(app)
184
+ Array(app).each do |a|
185
+ a = {class_name: a, title: a} unless a.is_a? Hash
186
+ return true if match_sub(a[:class_name], class_name) || match_sub(a[:title], title)
187
+ end
188
+ false
189
+ end
190
+
191
+ # @param a [String, Regexp]
192
+ # @param b [String]
193
+ def match_sub(a, b)
194
+ case a
195
+ when String
196
+ a == b
197
+ when Regexp
198
+ a =~ b
199
+ else
200
+ false
201
+ end
202
+ end
152
203
  end
153
204
  end