rkremap 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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