rkremap 0.4.1 → 0.5.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6476d10293c91c5afb744c6ebe1162d26ab253ad06aade6f5f2568db05bb7631
4
- data.tar.gz: 35fd73d5a2d71ca797fd626397a789e8f22b5bd54cfa2ef0ca08f46adc6efa7f
3
+ metadata.gz: 93f0a2d7d2c705b779d942618d4da708ef395386bc9529d06cc4c4f644243bac
4
+ data.tar.gz: bddb932d29c287b3e00105adac03fc420506253c5f00c309dee22da46678502f
5
5
  SHA512:
6
- metadata.gz: bdf4f1364db8aa24dc49dab823e4f16200994058a6ff8d8e3323272d0783139af49f6fcdbb7f22b573dab92554d39d92429d24aa7b5fedf80e27139cf62bd120
7
- data.tar.gz: d868e6b7af37edfa90324014669ff7de5595f392116bedeaf011cf95cd49ce9621782f1dda6e402c4e6117509ae0a0337322447028975ab743324da318479d0b
6
+ metadata.gz: 41fff4f8444a6c585403ac5a3655eb5b4e1d631332594c95e4d3149a973dced926b4a726cf912522e7ac3c0dcbb4ba0e3ea6461dfe6853e04c1a9086162307e2
7
+ data.tar.gz: 88b6bdb97c04ed2629d1eac337e89f9854f954daf392b4e0a395e6df94699ff1c7e7e66be78ea3adeb3221fff8a6a88d2d8a38699f503575c5376bbdfcf96556
data/README.md CHANGED
@@ -79,6 +79,44 @@ rkremap.exclude = /touchpad/i
79
79
  `true` の場合はキーボードデバイスの接続を自動検知する。
80
80
  `new` 時にデバイスファイルが指定されない場合は `true` になる。
81
81
 
82
+ ### Rkremap#remap
83
+
84
+ 引数の Hash で変換するキーを指定する:
85
+ ```ruby
86
+ rk.remap(KEY_CAPSLOCK => KEY_LEFTCTRL)
87
+ ```
88
+
89
+ 変換元を配列で修飾キーを書くとその修飾キーとの組み合わせで変換する:
90
+ ```ruby
91
+ rk.remap([KEY_LEFTCTRL, KEY_A] => KEY_HOME)
92
+ ```
93
+
94
+ hold を指定すると指定秒数長押ししたときだけ変換する:
95
+ ```ruby
96
+ rk.remap(hold: 0.5, map: {
97
+ KEY_MUHENKAN => KEY_RIGHTMETA,
98
+ KEY_HENKAN => KEY_RIGHTMETA,
99
+ })
100
+ ```
101
+
102
+ 次のように書くと、A も B も C もすべて A になってしまう:
103
+ ```ruby
104
+ rk.remap(KEY_A => KEY_B)
105
+ rk.remap(KEY_B => KEY_C)
106
+ rk.remap(KEY_C => KEY_A)
107
+ ```
108
+
109
+ A を B, B を C, C を A と変換したい場合は次のように書く:
110
+ ```ruby
111
+ rk.remap(
112
+ KEY_A => KEY_B,
113
+ KEY_B => KEY_C,
114
+ KEY_C => KEY_A
115
+ )
116
+ ```
117
+
118
+ `#remap` で実現できないような複雑な処理は、後述の `#match` や `#start` で書くことができるかもしれない。
119
+
82
120
  ### Rkremap#start
83
121
 
84
122
  なにかキーを押されたらブロックを実行する。修飾キー単体ではブロックは実行されない。
@@ -2,48 +2,23 @@
2
2
  # 0.5秒以内でも変換/無変換キーを押しながら他のキーを押すとSUPERキーとして扱う。
3
3
 
4
4
  require 'rkremap'
5
- require 'tmtms_timer'
6
5
 
7
6
  include Rkremap::KeyCode
8
7
 
9
- rk = Rkremap.new
8
+ rk = Rkremap.new(*ARGV, mouse: true)
10
9
  rk.grab = true
11
10
 
12
- timer = Tmtms::Timer.new
13
- jobs = {
14
- KEY_HENKAN => nil,
15
- KEY_MUHENKAN => nil,
16
- }
17
-
18
- henkan_map = {
11
+ # HENKAN, MUHENKAN を 0.5秒以上押したら SUPER とする
12
+ rk.remap(hold: 0.5, map: {
19
13
  KEY_HENKAN => KEY_RIGHTMETA,
20
14
  KEY_MUHENKAN => KEY_RIGHTMETA, # LEFTMETA だとデスクトップ環境のメニューが開いたりするため
21
- }
22
-
23
- rk.match(code: [KEY_HENKAN, KEY_MUHENKAN]) do |event|
24
- if event.type == :press
25
- job = timer.set(0.5) do
26
- rk.synchronize do
27
- rk.event(code: henkan_map[event.code], type: :press)
28
- end
29
- end
30
- jobs[event.code] = job
31
- elsif event.type == :release
32
- if jobs[event.code]&.cancel # 0.5秒以内に離された
33
- rk.key(event.code)
34
- else
35
- rk.event(code: henkan_map[event.code], type: :release)
36
- end
37
- end
38
- event.skip
39
- end
15
+ })
40
16
 
41
- # 何かキーが押されたときに HENKAN, MUHENKAN が押されてれば SUPER が押されてることにする
42
- rk.match(type: :press) do
43
- [KEY_HENKAN, KEY_MUHENKAN].each do |code|
44
- if jobs[code]&.cancel # 0.5秒以内
45
- rk.event(code: henkan_map[code], type: :press)
46
- end
17
+ # マウスボタンイベントはそのまま
18
+ rk.match(device: :mouse) do |event|
19
+ unless Rkremap::CODE_KEY[event.code] =~ /^KEY_/
20
+ rk.event(code: event.code, type: event.type)
21
+ event.skip
47
22
  end
48
23
  end
49
24
 
data/example/tmtms.rb CHANGED
@@ -1,68 +1,43 @@
1
1
  # tmtms 用
2
2
 
3
3
  require 'rkremap'
4
- require 'tmtms_timer'
5
4
 
6
5
  include Rkremap::KeyCode
7
6
 
8
- rk = Rkremap.new(*ARGV, mouse: true, exclude: /touchpad/i)
7
+ rk = Rkremap.new(*ARGV, mouse: true, exclude: /\AELAN0676/i)
9
8
  rk.grab = true
10
9
  rk.x11 = true
11
10
 
12
- until system "xinput | grep -q rkremap"
13
- sleep 0.1
14
- end
11
+ # rkremap がシステムに認識されたら中ボタンスクロールを有効にする
12
+ sleep 0.1 until system "xinput | grep -q rkremap"
15
13
  system "xinput set-prop pointer:rkremap 'libinput Scroll Method Enabled' 0 0 1"
16
14
 
17
15
  # HENKAN, MUHENKAN を 0.5秒以上押したら SUPER とする
18
- timer = Tmtms::Timer.new
19
- jobs = {
20
- KEY_HENKAN => nil,
21
- KEY_MUHENKAN => nil,
22
- }
23
-
24
- henkan_map = {
16
+ rk.remap(hold: 0.5, map: {
25
17
  KEY_HENKAN => KEY_RIGHTMETA,
26
18
  KEY_MUHENKAN => KEY_RIGHTMETA, # LEFTMETA だとデスクトップ環境のメニューが開いたりするため
27
- }
28
-
29
- rk.match(code: [KEY_HENKAN, KEY_MUHENKAN]) do |event|
30
- if event.type == :press
31
- job = timer.set(0.5) do
32
- rk.synchronize do
33
- rk.event(code: henkan_map[event.code], type: :press)
34
- end
35
- end
36
- jobs[event.code] = job
37
- elsif event.type == :release
38
- if jobs[event.code]&.cancel # 0.5秒以内に離された
39
- rk.key(event.code)
40
- else
41
- rk.event(code: henkan_map[event.code], type: :release)
42
- end
43
- end
44
- event.skip
45
- end
46
-
47
- # 何かキーが押されたときに HENKAN, MUHENKAN が押されてれば SUPER が押されてることにする
48
- rk.match(type: :press) do
49
- [KEY_HENKAN, KEY_MUHENKAN].each do |code|
50
- if jobs[code]&.cancel # 0.5秒以内
51
- rk.event(code: henkan_map[code], type: :press)
52
- end
53
- end
54
- end
19
+ })
55
20
 
56
21
  # マウスボタンイベントはそのまま
57
22
  rk.match(device: :mouse) do |event|
58
- rk.event(code: event.code, type: event.type)
59
- event.skip
23
+ unless Rkremap::CODE_KEY[event.code] =~ /^KEY_/
24
+ rk.event(code: event.code, type: event.type)
25
+ event.skip
26
+ end
60
27
  end
61
28
 
62
29
  # CAPSLOCK は CTRL に変換
63
- rk.match(code: KEY_CAPSLOCK) do |event|
64
- event.code = KEY_LEFTCTRL
65
- end
30
+ rk.remap(KEY_CAPSLOCK => KEY_LEFTCTRL)
31
+
32
+ # JISキーボードを英語配列にしたときに一部キーを変える
33
+ rk.remap(
34
+ KEY_YEN => KEY_BACKSLASH,
35
+ KEY_BACKSLASH => KEY_GRAVE,
36
+ KEY_GRAVE => KEY_ESC,
37
+ [KEY_LEFTSHIFT, KEY_RO] => [KEY_LEFTSHIFT, KEY_MINUS],
38
+ [KEY_RIGHTSHIFT, KEY_RO] => [KEY_LEFTSHIFT, KEY_MINUS],
39
+ KEY_RO => KEY_BACKSLASH,
40
+ )
66
41
 
67
42
  mod_disable_ctrl = {
68
43
  KEY_LEFTCTRL => false,
@@ -82,23 +57,23 @@ mod_disable_meta = {
82
57
  }
83
58
  mod_disable_all = {}.merge(mod_disable_ctrl, mod_disable_shift, mod_disable_alt, mod_disable_meta)
84
59
 
85
- # JISキーボードを英語配列にしたときに一部キーを変える
86
- key_map = {
87
- KEY_YEN => KEY_BACKSLASH,
88
- KEY_BACKSLASH => KEY_GRAVE,
89
- KEY_GRAVE => KEY_ESC,
90
- }
91
- rk.match do |event, mod|
92
- event.code = key_map[event.code] || event.code
93
- # RO キーは「\」(SHIFTキーを押してる場合は「_」)
94
- if event.code == KEY_RO
95
- event.code = (mod[KEY_LEFTSHIFT] || mod[KEY_RIGHTSHIFT]) ? KEY_MINUS : KEY_BACKSLASH
96
- end
60
+ # VirtualBox
61
+ rk.match(app: /VirtualBox Machine/) do |event|
62
+ rk.event(code: event.code, type: event.type)
63
+ event.skip
97
64
  end
98
65
 
99
- # Emacs や VirtualBox ではそのまま
100
- rk.match(app: /Emacs|VirtualBox Machine/) do |event|
101
- rk.event(code: event.code, type: event.type)
66
+ # Emacs
67
+ rk.match(app: {class_name: 'Emacs'}) do |event, mod|
68
+ # 修飾キーが Super だけの場合は Alt として扱う。ただしキーが TAB の場合は除く。
69
+ mod_enabled = mod.select{_2}.keys
70
+ if !mod_enabled.empty? && mod_enabled.all?{_1 == KEY_LEFTMETA || _1 == KEY_RIGHTMETA} && event.code != KEY_TAB
71
+ rk.with_modifier(mod_disable_meta.merge(KEY_LEFTALT => true)) do
72
+ rk.event(code: event.code, type: event.type)
73
+ end
74
+ else
75
+ rk.event(code: event.code, type: event.type)
76
+ end
102
77
  event.skip
103
78
  end
104
79
 
@@ -124,6 +99,16 @@ rk.match(app: /terminal/i) do |event, mod|
124
99
  event.skip
125
100
  end
126
101
 
102
+ # Firefox で Super+Ctrl+C は Alt+Ctrl+C に変換
103
+ rk.match(app: {class_name: 'firefox'}) do |event, mod|
104
+ if (mod[KEY_LEFTMETA] || mod[KEY_RIGHTMETA]) && (mod[KEY_LEFTCTRL] || mod[KEY_RIGHTCTRL]) && event.code == KEY_C
105
+ rk.with_modifier(mod_disable_meta.merge(KEY_LEFTCTRL => true, KEY_LEFTALT => true)) do
106
+ rk.event(code: event.code, type: event.type)
107
+ end
108
+ event.skip
109
+ end
110
+ end
111
+
127
112
  # その他のウィンドウ
128
113
  ctrl_map = {
129
114
  KEY_A => KEY_HOME,
@@ -149,6 +134,11 @@ rk.start do |code, mod|
149
134
  rk.key(code, mod.merge(mod_disable_meta, KEY_LEFTCTRL => true))
150
135
  next
151
136
  end
137
+ # Super+[←→] を Alt+[←→] に変換
138
+ if (mod[KEY_LEFTMETA] || mod[KEY_RIGHTMETA]) && (Rkremap::CODE_KEY[code] =~ /\AKEY_(LEFT|RIGHT)\z/)
139
+ rk.key(code, mod.merge(mod_disable_meta, KEY_LEFTALT => true))
140
+ next
141
+ end
152
142
 
153
143
  # Alt + F, B は Ctrl + →, ← に変換
154
144
  if (mod[KEY_LEFTALT] || mod[KEY_RIGHTALT]) && (code == KEY_F || code == KEY_B)
data/lib/rkremap/event.rb CHANGED
@@ -23,11 +23,15 @@ class Rkremap
23
23
  end
24
24
 
25
25
  def inspect
26
- "#<Rkremap::Event: #{code}(#{CODE_KEY[code]}) #{value} #{device.inspect}>"
26
+ if ev_type == EV_KEY
27
+ "#<Rkremap::Event: #{code}(#{CODE_KEY[code]}) #{value} #{device.inspect}>"
28
+ else
29
+ "#<Rkremap::Event: type: #{ev_type}>"
30
+ end
27
31
  end
28
32
 
29
33
  def type
30
- @type ||= value == 1 ? :press : value == 0 ? :release : :repeat
34
+ value == 1 ? :press : value == 0 ? :release : :repeat
31
35
  end
32
36
 
33
37
  def match?(device:, code:, type:, app:)
@@ -0,0 +1,118 @@
1
+ class Rkremap
2
+ class Remap
3
+ # @param map [Hash] from => to
4
+ # @param hold [Numeric] seconds
5
+ def initialize(rk, map:, hold:)
6
+ @rk = rk
7
+ @pressed = {}
8
+
9
+ if hold
10
+ setup_holding_event(map, hold)
11
+ else
12
+ setup_event(map)
13
+ end
14
+ end
15
+
16
+ # @param map [Hash] {form => to}
17
+ def setup_event(map)
18
+ keycodes = map.keys.flatten
19
+
20
+ @rk.match(code: keycodes, type: :press) do |event, current_mod|
21
+ map.each do |from, to|
22
+ if event_match?(from, event, current_mod)
23
+ make_press_event(to, event)
24
+ break
25
+ end
26
+ end
27
+ end
28
+
29
+ @rk.match(code: keycodes, type: :repeat) do |event|
30
+ event.skip
31
+ end
32
+
33
+ @rk.match(code: keycodes, type: :release) do |event|
34
+ if @pressed[event.code]
35
+ @rk.event(code: @pressed[event.code], type: :release)
36
+ @pressed[event.code] = nil
37
+ event.skip
38
+ end
39
+ end
40
+ end
41
+
42
+ # @param map [Hash] {form => to}
43
+ # @param hold [Numeric] seconds
44
+ def setup_holding_event(map, hold)
45
+ keycodes = map.keys.flatten
46
+ timer = Tmtms::Timer.new
47
+ jobs = map.transform_values{nil}
48
+
49
+ @rk.match(code: keycodes, type: :press) do |event|
50
+ map.each do |from, to|
51
+ if from == event.code
52
+ jobs[event.code] = timer.set(hold) do
53
+ @rk.synchronize{ make_press_event(to) }
54
+ end
55
+ event.skip
56
+ break
57
+ end
58
+ end
59
+ end
60
+
61
+ @rk.match(code: keycodes, type: :repeat) do |event|
62
+ event.skip
63
+ end
64
+
65
+ @rk.match(code: keycodes, type: :release) do |event|
66
+ code = event.code
67
+ if jobs[code]
68
+ if jobs[code].cancel
69
+ event.value = 1
70
+ @rk.append_event(Event.new(event.device, event.time, event.ev_type, event.code, 0))
71
+ elsif map[code]
72
+ event.code = map[code]
73
+ @pressed[code] = nil
74
+ end
75
+ jobs[code] = nil
76
+ end
77
+ end
78
+
79
+ @rk.match(type: :press) do
80
+ jobs.each do |code, job|
81
+ make_press_event(map[code]) if job&.cancel
82
+ end
83
+ end
84
+ end
85
+
86
+ # @param keys [Integer, Array<Integer>] KEY_A, [KEY_LEFTCTRL, KEY_A], [:SHIFT, KEY_A], ...
87
+ # @param event [Rkremap::Event]
88
+ # @param mod [Hash] current modifiers status
89
+ def event_match?(keys, event, mod)
90
+ if keys == event.code
91
+ return true
92
+ end
93
+ keys = Array(keys)
94
+ return false unless keys.include? event.code
95
+ (keys - [event.code]).all?{mod[_1]}
96
+ end
97
+
98
+ # @param to [Integer, Array<Integer>]
99
+ # @param event [Rkremap::Event, nil]
100
+ def make_press_event(to, event=nil)
101
+ to = Array(to)
102
+ f = true
103
+ to.each do |key|
104
+ @pressed[event.code] = key if event
105
+ if @rk.modifiers.include? key
106
+ @rk.event(code: key, type: :press)
107
+ elsif event
108
+ event.code = key
109
+ f = false
110
+ else
111
+ @rk.key(key)
112
+ f = false
113
+ end
114
+ end
115
+ event&.skip if f
116
+ end
117
+ end
118
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Rkremap
4
- VERSION = "0.4.1"
4
+ VERSION = "0.5.1"
5
5
  end
data/lib/rkremap.rb CHANGED
@@ -7,6 +7,7 @@ require_relative 'rkremap/evdev_list'
7
7
  require_relative 'rkremap/evdev'
8
8
  require_relative 'rkremap/event'
9
9
  require_relative 'rkremap/uinput'
10
+ require_relative 'rkremap/remap'
10
11
 
11
12
  class Rkremap
12
13
  include KeyCode
@@ -57,6 +58,17 @@ class Rkremap
57
58
  @mutex = Mutex.new
58
59
  end
59
60
 
61
+ # @overload remap(map)
62
+ # @param map [Hash] from => to
63
+ # @overload remap(map:, hold: nil)
64
+ # @param map [Hash] from => to
65
+ # @param hold [Numeric] seconds
66
+ def remap(**opts)
67
+ map, hold = opts[:map], opts[:hold]
68
+ map ||= opts
69
+ Remap.new(self, map: map, hold: hold)
70
+ end
71
+
60
72
  # @param device [Symbol, Regexp]
61
73
  # @param code [Integer]
62
74
  # @param type [Symbol] :press / :release / :repeat
@@ -71,9 +83,10 @@ class Rkremap
71
83
  @evdev_list.detect_loop
72
84
  @mod_state = @modifiers.map.to_h{|m| [m, false]}
73
85
  winattr = WinAttr.new if @x11
86
+ @events = []
74
87
  while true
75
88
  @keys = []
76
- event = @evdev_list.read_event
89
+ event = @events.shift || @evdev_list.read_event
77
90
  if event.ev_type != EV_KEY
78
91
  if event.device.grab?
79
92
  @uinput.write_event(event.ev_type, event.code, event.value)
@@ -126,6 +139,12 @@ class Rkremap
126
139
  end
127
140
  end
128
141
 
142
+ # @param event [Rkremap::Event]
143
+ def append_event(event)
144
+ @events.push event
145
+ @events.push Event.new(event.device, event.time, EV_SYN, SYN_REPORT, 0)
146
+ end
147
+
129
148
  private
130
149
 
131
150
  def update_modifiers(code, value)
@@ -178,7 +197,7 @@ class Rkremap
178
197
  # @param app [Array, Hash, String, Regexp]
179
198
  # @return [Boolean]
180
199
  def match?(app)
181
- Array(app).each do |a|
200
+ [app].flatten.each do |a|
182
201
  a = {class_name: a, title: a} unless a.is_a? Hash
183
202
  return true if match_sub(a[:class_name], class_name) || match_sub(a[:title], title)
184
203
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rkremap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - TOMITA Masahiro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-27 00:00:00.000000000 Z
11
+ date: 2023-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tmtms_timer
@@ -41,6 +41,7 @@ files:
41
41
  - lib/rkremap/evdev_list.rb
42
42
  - lib/rkremap/event.rb
43
43
  - lib/rkremap/keycode.rb
44
+ - lib/rkremap/remap.rb
44
45
  - lib/rkremap/uinput.rb
45
46
  - lib/rkremap/version.rb
46
47
  - lib/rkremap/winattr.rb
@@ -67,7 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
68
  - !ruby/object:Gem::Version
68
69
  version: '0'
69
70
  requirements: []
70
- rubygems_version: 3.3.7
71
+ rubygems_version: 3.5.0.dev
71
72
  signing_key:
72
73
  specification_version: 4
73
74
  summary: key remapper