rkremap 0.4.1 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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