rkremap 0.1.0 → 0.2.0

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: 945483dcd11240d5c54b359a15055219532f2f311275633503e7d485aa68917b
4
- data.tar.gz: d594c9b549696f964cde750094169ba7198c0ac9ab5f5dfc59a7936d9610a09d
3
+ metadata.gz: b9cb72d3bf69bf2d0641eb7856903d110f0a253d5377c2d97ea452a1f0fd5c13
4
+ data.tar.gz: 1dd89d3569fc5396a6c314618bcc975e04b63b0f2a55d9c3dd8cd5497f1e2164
5
5
  SHA512:
6
- metadata.gz: a1412a373a0673172a9eba0449613ab9a76debf400b622977887031c294f8381a58a7c060ef6451e6264703690974aeddb305ce35619c07958a2b5a1239ecf12
7
- data.tar.gz: 619ee1de0814c2542c2a4f347dca7c48e640590a01ff58f2b7362ed80ef447acaf1576df2a2a659eaac95c9a7ec0a8f52d732d8024356613fbef43f055b16dd2
6
+ metadata.gz: 8cf20b635b0aa1584e81cc0317040931f62f011e004189e30428534c04456159cde7ac37c5f790cc1af06d396b9d60c36c4398a5d51947dcf99309eb3edb584b
7
+ data.tar.gz: cde725ba50b0d5b2fd3daf837af9e1244a1cc5cb3d40f497d9dbf00e6655edc0e436dcdc94056b8bc9efa3887ec2419fd7e53b11e8289c142217be2dd577f8ef
data/README.md CHANGED
@@ -25,7 +25,9 @@ KERNEL=="uinput", GROUP="input"
25
25
 
26
26
  ### `Rkremap.new`
27
27
 
28
- 引数に `/dev/input/event*` を指定する。キーボードデバイス以外を指定してもたぶん無意味。
28
+ 引数を指定しなければ自動的にキーボードデバイスの検出を試みる。
29
+
30
+ 引数を指定する場合は `/dev/input/event*` を指定する。キーボードデバイス以外を指定してもたぶん無意味。
29
31
  キーボードのデバイスファイル名を調べるには
30
32
  ```
31
33
  cat /proc/bus/input/devices
@@ -79,6 +81,7 @@ ThinkPad 本体のキーボードは `/dev/input/event3` だった。
79
81
  * [xkeysnail - もうひとつの Linux 向けキーリマッパ - Qiita](https://qiita.com/mooz@github/items/c5f25f27847333dd0b37)
80
82
  * [k0kubun/xremap: Dynamic key remapper for X11 and Wayland](https://github.com/k0kubun/xremap)
81
83
  * [Linux向けの最強のキーリマッパーを作った - k0kubun's blog](https://k0kubun.hatenablog.com/entry/xkremap)
84
+ * [Linux用キーリマッパーxremapをRustで書き直した - k0kubun's blog](https://k0kubun.hatenablog.com/entry/xremap)
82
85
  * [kui/rbindkeys: key remap with ruby](https://github.com/kui/rbindkeys)
83
86
  * [Ruby で設定できる Linux 環境向けキーリマッパー作った — 電卓片手に](http://k-ui.jp/blog/2012/06/06/rbindkeys-configurable-key-remapper-in-ruby/)
84
87
  * [Linux の入力デバイスをカスタマイズ - Qiita](https://qiita.com/propella/items/a73fc333c95d14d06835)
@@ -4,7 +4,7 @@
4
4
  require 'rkremap'
5
5
  include Rkremap::KeyCode
6
6
 
7
- rk = Rkremap.new('/dev/input/event3')
7
+ rk = Rkremap.new
8
8
  rk.grab = true
9
9
 
10
10
  class State
data/example/keylogger.rb CHANGED
@@ -6,7 +6,7 @@ def code2key(code)
6
6
  Rkremap::CODE_KEY[code].to_s.sub(/\AKEY_/, '')
7
7
  end
8
8
 
9
- rk = Rkremap.new('/dev/input/event3')
9
+ rk = Rkremap.new
10
10
  rk.grab = false
11
11
  rk.x11 = true
12
12
  rk.start do |code, mod, app|
@@ -33,7 +33,7 @@ mod_disable_meta = {
33
33
  }
34
34
  mod_disable_all = {}.merge(mod_disable_ctrl, mod_disable_shift, mod_disable_alt, mod_disable_meta)
35
35
 
36
- rk = Rkremap.new("/dev/input/event3")
36
+ rk = Rkremap.new
37
37
  rk.grab = true
38
38
  rk.x11 = true
39
39
  rk.start do |code, mod, app|
data/example/tmtms.rb CHANGED
@@ -40,7 +40,7 @@ class State
40
40
  end
41
41
  end
42
42
 
43
- rk = Rkremap.new("/dev/input/event3")
43
+ rk = Rkremap.new
44
44
  rk.grab = true
45
45
  rk.x11 = true
46
46
 
@@ -160,10 +160,15 @@ rk.start do |code, mod, app|
160
160
  # Ctrl は他のキーに変換
161
161
  if mod[KEY_LEFTCTRL] || mod[KEY_RIGHTCTRL]
162
162
  # Ctrl+K は行末まで削除 (日本語変換時はそのまま)
163
- if code == KEY_K && !File.exist?('/tmp/fcitx-enabled')
164
- rk.key(KEY_END, mod_disable_all.merge({KEY_LEFTSHIFT => true}))
165
- rk.key(KEY_X, mod_disable_all.merge({KEY_LEFTCTRL => true}))
166
- next
163
+ if code == KEY_K
164
+ if File.exist?('/tmp/fcitx-enabled')
165
+ rk.key(code, mod)
166
+ next
167
+ else
168
+ rk.key(KEY_END, mod_disable_all.merge({KEY_LEFTSHIFT => true}))
169
+ rk.key(KEY_X, mod_disable_all.merge({KEY_LEFTCTRL => true}))
170
+ next
171
+ end
167
172
  end
168
173
  # 単純な変換
169
174
  if ctrl_map[code]
@@ -0,0 +1,124 @@
1
+ class Rkremap
2
+ EV_KEY = 1
3
+ EV_SYN = 0
4
+ SYN_REPORT = 0
5
+
6
+ # https://www.kernel.org/doc/html/v4.12/input/input_uapi.html
7
+ class Evdev
8
+ EVIOCGBIT_ANY = 2147566880 # EVIOCGBIT(0, 1)
9
+ EVIOCGBIT_EV_KEY = 2153792801 # EVIOCGBIT(EV_KEY, (KEY_MAX-1)/8+1)
10
+ EVIOCGRAB = 1074021776
11
+
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
31
+
32
+ # @param path [String]
33
+ def initialize(path)
34
+ @path = path
35
+ @io = nil
36
+ @capa = nil
37
+ @uinput = nil
38
+ end
39
+
40
+ # @return [IO]
41
+ def io
42
+ return @io if @io
43
+ @io = File.open(@path)
44
+ self.class.io2evdev[@io] = self
45
+ @io
46
+ end
47
+
48
+ def close
49
+ @io&.close
50
+ end
51
+
52
+ # @param key [Integer]
53
+ # @return [Boolean]
54
+ def capable?(key)
55
+ unless @capa
56
+ buf = ' ' * ((KeyCode::KEY_MAX-1)/8+1)
57
+ io.ioctl(EVIOCGBIT_EV_KEY, buf)
58
+ @capa = buf.unpack('C*')
59
+ end
60
+ @capa[key/8][key%8] != 0
61
+ end
62
+
63
+ # @return [Boolean]
64
+ def keyboard?
65
+ buf = ' '
66
+ io.ioctl(EVIOCGBIT_ANY, buf)
67
+ 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)
70
+ end
71
+
72
+ def grab
73
+ io.ioctl(EVIOCGRAB, 1)
74
+ end
75
+
76
+ # struct input_event {
77
+ # struct timeval time;
78
+ # unsigned short type;
79
+ # unsigned short code;
80
+ # unsigned int value;
81
+ # };
82
+ # @return [Array<Time, Integer, Integer, Integer>]
83
+ def read_event
84
+ raw = io.sysread(24) # sizeof(struct input_event)
85
+ sec, usec, type, code, value = raw.unpack('Q!Q!SSl')
86
+ time = Time.at(sec, usec)
87
+ return time, type, code, value
88
+ 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
+
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'))
122
+ end
123
+ end
124
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Rkremap
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -1,12 +1,4 @@
1
- require 'ffi'
2
-
3
- module FFI
4
- typedef :pointer, :Display
5
- typedef :ulong, :XID
6
- typedef :XID, :Window
7
- typedef :int, :Status
8
- typedef :ulong, :Atom
9
- end
1
+ require 'fiddle/import'
10
2
 
11
3
  class Rkremap
12
4
  # アプリの name, class, title を取得する
@@ -14,38 +6,39 @@ class Rkremap
14
6
  def initialize
15
7
  @app_win = {}
16
8
  @display = X11.XOpenDisplay(nil) or raise "Cannot open display: #{ENV['DISPLAY']}"
9
+ @buf = ' '*8
17
10
  end
18
11
 
12
+ # @return [Integer] Window
19
13
  def focus_win
20
- win = FFI::MemoryPointer.new(:Window)
21
- revert_to = FFI::MemoryPointer.new(:int)
22
- X11.XGetInputFocus(@display, win, revert_to)
23
- win.read(:Window)
14
+ win_buf = ' ' * X11.sizeof('Window')
15
+ X11.XGetInputFocus(@display, win_buf, @buf)
16
+ win_buf.unpack1('L')
24
17
  end
25
18
 
26
19
  # @return [Array<window, name, class>]
27
20
  def app_win(window)
28
21
  return @app_win[window] if @app_win[window]
29
- class_hint = XClassHint.new
30
- root = FFI::MemoryPointer.new(:Window)
31
- parent = FFI::MemoryPointer.new(:Window)
32
- children = FFI::MemoryPointer.new(:pointer)
33
- nchildren = FFI::MemoryPointer.new(:int)
22
+ class_hint = X11::XClassHint.malloc
23
+ parent = ' ' * X11.sizeof('Window')
24
+ children = ' ' * X11.sizeof('void*')
34
25
  win = window
35
26
  while win > 0
27
+ class_hint.name = nil
28
+ class_hint.class_name = nil
36
29
  X11.XGetClassHint(@display, win, class_hint)
37
- X11.XQueryTree(@display, win, root, parent, children, nchildren)
38
- X11.XFree(children.read_pointer)
39
- break unless class_hint[:name].null? && class_hint[:class].null?
40
- win = parent.read(:Window)
30
+ X11.XQueryTree(@display, win, @buf, parent, children, @buf)
31
+ X11.XFree(Fiddle::Pointer[children].ptr)
32
+ break unless class_hint.name.null? && class_hint.class_name.null?
33
+ win = parent.unpack1('L')
41
34
  end
42
- unless class_hint[:name].null?
43
- win_name = class_hint[:name].read_string
44
- X11.XFree(class_hint[:name])
35
+ unless class_hint.name.null?
36
+ win_name = class_hint.name.to_s
37
+ X11.XFree(class_hint.name)
45
38
  end
46
- unless class_hint[:class].null?
47
- win_class = class_hint[:class].read_string
48
- X11.XFree(class_hint[:class])
39
+ unless class_hint.class_name.null?
40
+ win_class = class_hint.class_name.to_s
41
+ X11.XFree(class_hint.class_name)
49
42
  end
50
43
  @app_win[window] = [win, win_name, win_class]
51
44
  end
@@ -53,43 +46,33 @@ class Rkremap
53
46
  # @return [String]
54
47
  def app_title(window)
55
48
  win = app_win(window)[0]
56
- prop = XTextProperty.new
57
- text_list = FFI::MemoryPointer.new(:pointer)
58
- count = FFI::MemoryPointer.new(:int)
49
+ prop = X11::XTextProperty.malloc
50
+ text_list = ' ' * X11.sizeof('void*')
59
51
  X11.XGetWMName(@display, win, prop)
60
- X11.Xutf8TextPropertyToTextList(@display, prop, text_list, count)
61
- title = text_list.read(:pointer).read(:string)&.force_encoding('utf-8')
62
- X11.XFreeStringList(text_list.read(:pointer))
52
+ X11.Xutf8TextPropertyToTextList(@display, prop, text_list, @buf)
53
+ title = Fiddle::Pointer[text_list].ptr.ptr.to_s.force_encoding('utf-8')
54
+ X11.XFreeStringList(Fiddle::Pointer[text_list].ptr)
63
55
  title
64
56
  end
65
57
  end
66
58
 
67
- class XClassHint < FFI::Struct
68
- layout(
69
- :name, :pointer,
70
- :class, :pointer,
71
- )
72
- end
73
-
74
- class XTextProperty < FFI::Struct
75
- layout(
76
- :value, :pointer,
77
- :encoding, :Atom,
78
- :format, :int,
79
- :nitems, :ulong,
80
- )
81
- end
82
-
83
59
  module X11
84
- extend FFI::Library
85
- ffi_lib 'X11'
86
- attach_function :XOpenDisplay, [:string], :Display
87
- attach_function :XGetInputFocus, [:Display, :pointer, :pointer], :int
88
- attach_function :XGetClassHint, [:Display, :Window, XClassHint.by_ref], :int
89
- attach_function :XQueryTree, [:Display, :Window, :pointer, :pointer, :pointer, :pointer], :Status
90
- attach_function :XGetWMName, [:Display, :Window, XTextProperty.by_ref], :void
91
- attach_function :Xutf8TextPropertyToTextList, [:Display, XTextProperty.by_ref, :pointer, :pointer], :int
92
- attach_function :XFree, [:pointer], :int
93
- attach_function :XFreeStringList, [:pointer], :void
60
+ extend Fiddle::Importer
61
+ dlload 'libX11.so'
62
+ typealias 'XID', 'unsigned long'
63
+ typealias 'Window', 'XID'
64
+ typealias 'Status', 'int'
65
+ typealias 'Atom', 'unsigned long'
66
+ XClassHint = struct(['char *name', 'char *class_name'])
67
+ XTextProperty = struct(['unsigned char *value', 'Atom encoding', 'int format', 'unsigned long nitems'])
68
+
69
+ extern 'Display* XOpenDisplay(char*)'
70
+ extern 'int XGetInputFocus(Display*, Window*, int*)'
71
+ extern 'int XGetClassHint(Display*, Window, XClassHint*)'
72
+ extern 'Status XQueryTree(Display*, Window, Window*, Window*, Window**, unsigned int*)'
73
+ extern 'Status XGetWMName(Display*, Window, XTextProperty*)'
74
+ extern 'int Xutf8TextPropertyToTextList(Display*, XTextProperty*, char***, int*)'
75
+ extern 'int XFree(void*)'
76
+ extern 'void XFreeStringList(char**)'
94
77
  end
95
78
  end
data/lib/rkremap.rb CHANGED
@@ -3,20 +3,11 @@
3
3
  require_relative "rkremap/version"
4
4
  require_relative 'rkremap/keycode'
5
5
  require_relative 'rkremap/winattr'
6
+ require_relative 'rkremap/evdev'
6
7
 
7
8
  class Rkremap
8
9
  include KeyCode
9
10
 
10
- # ioctl constants
11
- EVIOCGRAB = 1074021776
12
- UI_SET_EVBIT = 1074025828
13
- UI_SET_KEYBIT = 1074025829
14
- UI_DEV_SETUP = 1079792899
15
- UI_DEV_CREATE = 21761
16
- EV_KEY = 1
17
- EV_SYN = 0
18
- SYN_REPORT = 0
19
-
20
11
  EVENT_TYPE_VALUE = {
21
12
  release: 0,
22
13
  press: 1,
@@ -27,11 +18,12 @@ class Rkremap
27
18
  attr_accessor :modifiers
28
19
  attr_accessor :x11
29
20
 
30
- # @param device [String]
31
- def initialize(device)
32
- @input = File.open(device)
33
- @uinput = File.open('/dev/uinput', 'w')
34
- setup_uinput
21
+ # @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?
26
+ @uinput = Uinput.new
35
27
  @grab = false
36
28
  @x11 = false
37
29
  @modifiers = [
@@ -53,13 +45,12 @@ class Rkremap
53
45
  def start(&block)
54
46
  @mod_state = @modifiers.map{|m| [m, false]}.to_h
55
47
  @winattr = WinAttr.new if @x11
56
- @input.ioctl(EVIOCGRAB, 1) if @grab
48
+ @inputs.each(&:grab) if @grab
57
49
  while true
58
50
  @keys = []
59
- raw = @input.sysread(24)
60
- sec, usec, type, code, value = raw.unpack('Q!Q!SSl')
51
+ input = Evdev.select(@inputs)
52
+ time, type, code, value = input.read_event
61
53
  next if type != EV_KEY
62
- time = Time.at(sec, usec)
63
54
  event = Event.new(time, code, value, @winattr)
64
55
  @events.each do |cond, b|
65
56
  synchronize{ b.call event } if event.match?(**cond)
@@ -100,20 +91,6 @@ class Rkremap
100
91
 
101
92
  private
102
93
 
103
- def setup_uinput
104
- @uinput.ioctl(UI_SET_EVBIT, EV_KEY)
105
- KEY_CNT.times{|k| @uinput.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
- @uinput.ioctl(UI_DEV_SETUP, setup)
114
- @uinput.ioctl(UI_DEV_CREATE)
115
- end
116
-
117
94
  def proc_event(code, value, event)
118
95
  if @mod_state.include?(code)
119
96
  @mod_state[code] = value != 0
@@ -130,8 +107,8 @@ class Rkremap
130
107
  # @param code [Integer]
131
108
  # @param value [Integer] 0:release / 1:press / 2:repeat
132
109
  def write_event(code, value)
133
- @uinput.syswrite(['', EV_KEY, code, value].pack('a16SSl'))
134
- @uinput.syswrite(['', EV_SYN, SYN_REPORT, 0].pack('a16SSl'))
110
+ @uinput.write_event(EV_KEY, code, value)
111
+ @uinput.write_event(EV_SYN, SYN_REPORT, 0)
135
112
  end
136
113
 
137
114
  class Event
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rkremap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
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-01-10 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: ffi
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.0'
11
+ date: 2022-01-12 00:00:00.000000000 Z
12
+ dependencies: []
27
13
  description: key remapper
28
14
  email:
29
15
  - tommy@tmtm.org
@@ -37,6 +23,7 @@ files:
37
23
  - example/mac-shortcut.rb
38
24
  - example/tmtms.rb
39
25
  - lib/rkremap.rb
26
+ - lib/rkremap/evdev.rb
40
27
  - lib/rkremap/keycode.rb
41
28
  - lib/rkremap/version.rb
42
29
  - lib/rkremap/winattr.rb