rkremap 0.5.1 → 0.6.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.
- checksums.yaml +4 -4
- data/README.md +3 -2
- data/example/hoge.rb +11 -0
- data/example/tmtms.rb +2 -6
- data/lib/rkremap/evdev.rb +4 -3
- data/lib/rkremap/evdev_list.rb +2 -0
- data/lib/rkremap/remap.rb +5 -2
- data/lib/rkremap/version.rb +1 -1
- data/lib/rkremap/winattr.rb +10 -74
- data/lib/rkremap/winattr_kde.rb +67 -0
- data/lib/rkremap/winattr_x11.rb +89 -0
- data/lib/rkremap.rb +6 -3
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 590e1e170524210ac70556095262d1cda5a7a94330d2aaa2927a644a96a431d0
|
4
|
+
data.tar.gz: 97f670607504cac0b34b8e128711eabf8f8a7000edf122350deaf7409d481315
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b9932e445cb887e0a4ee4e7b48c9b29767bee3a44936d99e5eb34e6f873a7c0ce24a1edbdea10da5985744ac29c6871d284896c2497ff0ce82407bb8e16eacdf
|
7
|
+
data.tar.gz: 8f88809a29fa4d7a407807ddd11c8e570258fcb8f760cf4b285c2e35314e61cd18de0c43516d143a63cfc3f71d778fa5e07e97c5eb204ba5687338572f186545
|
data/README.md
CHANGED
@@ -66,9 +66,10 @@ rkremap.exclude = /touchpad/i
|
|
66
66
|
|
67
67
|
正規表現を設定するとデバイス名がマッチした場合にデバイスを grab する。
|
68
68
|
|
69
|
-
### Rkremap#
|
69
|
+
### Rkremap#window_system
|
70
70
|
|
71
|
-
`
|
71
|
+
`"x11"` に設定すると X のアプリ名等を取得できる。Wayland 環境では動かない。
|
72
|
+
`"kde"`, `"kwin"` に設定すると KDE/Kubuntu 環境でアプリメオ等を取得できる。Wayland 環境でも動作する。
|
72
73
|
|
73
74
|
### Rkremap#modifers
|
74
75
|
|
data/example/hoge.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rkremap'
|
2
|
+
|
3
|
+
include Rkremap::KeyCode
|
4
|
+
|
5
|
+
rk = Rkremap.new(*ARGV, mouse: false, exclude: /\AELAN0676/i)
|
6
|
+
rk.grab = true
|
7
|
+
|
8
|
+
rk.remap(KEY_CAPSLOCK => KEY_LEFTCTRL, KEY_MUHENKAN => KEY_LEFTMETA, KEY_HENKAN => KEY_RIGHTMETA)
|
9
|
+
rk.remap(KEY_LEFTMETA => [KEY_LEFTSHIFT, KEY_LEFTCTRL])
|
10
|
+
rk.match{|ev| rk.event(code: ev.code, type: ev.value); ev.skip }
|
11
|
+
rk.start
|
data/example/tmtms.rb
CHANGED
@@ -4,13 +4,9 @@ require 'rkremap'
|
|
4
4
|
|
5
5
|
include Rkremap::KeyCode
|
6
6
|
|
7
|
-
rk = Rkremap.new(*ARGV, mouse: true, exclude: /\AELAN0676/i)
|
7
|
+
rk = Rkremap.new(*ARGV, mouse: true, exclude: /\AELAN0676|\ALenovo TrackPoint Keyboard II/i)
|
8
8
|
rk.grab = true
|
9
|
-
rk.
|
10
|
-
|
11
|
-
# rkremap がシステムに認識されたら中ボタンスクロールを有効にする
|
12
|
-
sleep 0.1 until system "xinput | grep -q rkremap"
|
13
|
-
system "xinput set-prop pointer:rkremap 'libinput Scroll Method Enabled' 0 0 1"
|
9
|
+
rk.window_system = 'kde'
|
14
10
|
|
15
11
|
# HENKAN, MUHENKAN を 0.5秒以上押したら SUPER とする
|
16
12
|
rk.remap(hold: 0.5, map: {
|
data/lib/rkremap/evdev.rb
CHANGED
@@ -16,6 +16,7 @@ class Rkremap
|
|
16
16
|
@capa = nil
|
17
17
|
@uinput = nil
|
18
18
|
@grab = false
|
19
|
+
io
|
19
20
|
end
|
20
21
|
|
21
22
|
def inspect
|
@@ -35,7 +36,7 @@ class Rkremap
|
|
35
36
|
# @return [String]
|
36
37
|
def name
|
37
38
|
return @name if @name
|
38
|
-
buf = ''
|
39
|
+
buf = +''
|
39
40
|
io.ioctl(EVIOCGNAME, buf)
|
40
41
|
@name = buf.sub(/\0+$/, '')
|
41
42
|
end
|
@@ -61,7 +62,7 @@ class Rkremap
|
|
61
62
|
@is_keyboard = false
|
62
63
|
return false if name =~ /\Arkremap\0*\z/
|
63
64
|
|
64
|
-
buf = ''
|
65
|
+
buf = +''
|
65
66
|
io.ioctl(EVIOCGBIT_ANY, buf)
|
66
67
|
return false if buf.unpack1('C')[EV_KEY] == 0
|
67
68
|
@is_keyboard = capable?(KeyCode::KEY_0) && capable?(KeyCode::KEY_9) &&
|
@@ -74,7 +75,7 @@ class Rkremap
|
|
74
75
|
@is_mouse = false
|
75
76
|
return false if name =~ /\Arkremap\0*\z/
|
76
77
|
|
77
|
-
buf = ''
|
78
|
+
buf = +''
|
78
79
|
io.ioctl(EVIOCGBIT_ANY, buf)
|
79
80
|
return false if buf.unpack1('C')[EV_KEY] == 0
|
80
81
|
@is_mouse = capable?(KeyCode::BTN_MOUSE)
|
data/lib/rkremap/evdev_list.rb
CHANGED
data/lib/rkremap/remap.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
class Rkremap
|
2
2
|
class Remap
|
3
|
+
def self.timer
|
4
|
+
@timer ||= Tmtms::Timer.new
|
5
|
+
end
|
6
|
+
|
3
7
|
# @param map [Hash] from => to
|
4
8
|
# @param hold [Numeric] seconds
|
5
9
|
def initialize(rk, map:, hold:)
|
@@ -43,13 +47,12 @@ class Rkremap
|
|
43
47
|
# @param hold [Numeric] seconds
|
44
48
|
def setup_holding_event(map, hold)
|
45
49
|
keycodes = map.keys.flatten
|
46
|
-
timer = Tmtms::Timer.new
|
47
50
|
jobs = map.transform_values{nil}
|
48
51
|
|
49
52
|
@rk.match(code: keycodes, type: :press) do |event|
|
50
53
|
map.each do |from, to|
|
51
54
|
if from == event.code
|
52
|
-
jobs[event.code] = timer.set(hold) do
|
55
|
+
jobs[event.code] = Remap.timer.set(hold) do
|
53
56
|
@rk.synchronize{ make_press_event(to) }
|
54
57
|
end
|
55
58
|
event.skip
|
data/lib/rkremap/version.rb
CHANGED
data/lib/rkremap/winattr.rb
CHANGED
@@ -1,83 +1,19 @@
|
|
1
1
|
require 'fiddle/import'
|
2
|
+
require_relative 'winattr_x11'
|
3
|
+
require_relative 'winattr_kde'
|
2
4
|
|
3
5
|
class Rkremap
|
4
6
|
# アプリの name, class, title を取得する
|
5
7
|
class WinAttr
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
win = X11::Window.malloc(Fiddle::RUBY_FREE)
|
15
|
-
X11.XGetInputFocus(@display, win, @buf)
|
16
|
-
win.window
|
17
|
-
end
|
18
|
-
|
19
|
-
# @return [Array<window, name, class>]
|
20
|
-
def app_win(window)
|
21
|
-
return @app_win[window] if @app_win[window]
|
22
|
-
class_hint = X11::XClassHint.malloc(Fiddle::RUBY_FREE)
|
23
|
-
parent = X11::Window.malloc(Fiddle::RUBY_FREE)
|
24
|
-
children = X11::Pointer.malloc(Fiddle::RUBY_FREE)
|
25
|
-
win = window
|
26
|
-
while win > 0
|
27
|
-
class_hint.name = nil
|
28
|
-
class_hint.class_name = nil
|
29
|
-
X11.XGetClassHint(@display, win, class_hint)
|
30
|
-
X11.XQueryTree(@display, win, @buf, parent, children, @buf)
|
31
|
-
X11.XFree(children.ptr)
|
32
|
-
break unless class_hint.name.null? && class_hint.class_name.null?
|
33
|
-
win = parent.window
|
34
|
-
end
|
35
|
-
unless class_hint.name.null?
|
36
|
-
win_name = class_hint.name.to_s
|
37
|
-
X11.XFree(class_hint.name)
|
38
|
-
end
|
39
|
-
unless class_hint.class_name.null?
|
40
|
-
win_class = class_hint.class_name.to_s
|
41
|
-
X11.XFree(class_hint.class_name)
|
8
|
+
def self.for(win_system)
|
9
|
+
case win_system.to_s
|
10
|
+
when 'x11'
|
11
|
+
WinAttrX11.new
|
12
|
+
when 'kde', 'kwin', 'kubuntu'
|
13
|
+
WinAttrKDE.new
|
14
|
+
else
|
15
|
+
raise "unknown system: #{win_system}"
|
42
16
|
end
|
43
|
-
@app_win[window] = [win, win_name, win_class]
|
44
17
|
end
|
45
|
-
|
46
|
-
# @return [String]
|
47
|
-
def app_title(window)
|
48
|
-
win = app_win(window)[0]
|
49
|
-
return '' if win == 0
|
50
|
-
prop = X11::XTextProperty.malloc(Fiddle::RUBY_FREE)
|
51
|
-
text_list = X11::Pointer.malloc(Fiddle::RUBY_FREE)
|
52
|
-
X11.XGetWMName(@display, win, prop)
|
53
|
-
X11.Xutf8TextPropertyToTextList(@display, prop, text_list, @buf)
|
54
|
-
ptr = text_list.ptr
|
55
|
-
return '' if ptr.null?
|
56
|
-
title = ptr.ptr.to_s.force_encoding('utf-8')
|
57
|
-
X11.XFreeStringList(text_list.ptr)
|
58
|
-
title
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
module X11
|
63
|
-
extend Fiddle::Importer
|
64
|
-
dlload 'libX11.so.6'
|
65
|
-
typealias 'XID', 'unsigned long'
|
66
|
-
typealias 'Window', 'XID'
|
67
|
-
typealias 'Status', 'int'
|
68
|
-
typealias 'Atom', 'unsigned long'
|
69
|
-
Window = struct ['Window window']
|
70
|
-
Pointer = struct ['void *ptr']
|
71
|
-
XClassHint = struct ['char *name', 'char *class_name']
|
72
|
-
XTextProperty = struct ['unsigned char *value', 'Atom encoding', 'int format', 'unsigned long nitems']
|
73
|
-
|
74
|
-
extern 'Display* XOpenDisplay(char*)'
|
75
|
-
extern 'int XGetInputFocus(Display*, Window*, int*)'
|
76
|
-
extern 'int XGetClassHint(Display*, Window, XClassHint*)'
|
77
|
-
extern 'Status XQueryTree(Display*, Window, Window*, Window*, Window**, unsigned int*)'
|
78
|
-
extern 'Status XGetWMName(Display*, Window, XTextProperty*)'
|
79
|
-
extern 'int Xutf8TextPropertyToTextList(Display*, XTextProperty*, char***, int*)'
|
80
|
-
extern 'int XFree(void*)'
|
81
|
-
extern 'void XFreeStringList(char**)'
|
82
18
|
end
|
83
19
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'dbus'
|
2
|
+
|
3
|
+
class Rkremap
|
4
|
+
class WinAttrKDE
|
5
|
+
attr_reader :focus_win
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@app_win = {}
|
9
|
+
|
10
|
+
bus = DBus.session_bus
|
11
|
+
kwin_service = bus.service('org.kde.KWin')
|
12
|
+
kwin_scripting = kwin_service.object('/Scripting')
|
13
|
+
kwin_scripting.introspect
|
14
|
+
full_path = File.expand_path('./kwin_script.js', __dir__)
|
15
|
+
if kwin_scripting.isScriptLoaded(full_path)[0]
|
16
|
+
kwin_scripting.unloadScript(full_path)
|
17
|
+
end
|
18
|
+
kwin_scripting.loadScript(full_path)
|
19
|
+
kwin_scripting.start
|
20
|
+
|
21
|
+
create_service
|
22
|
+
end
|
23
|
+
|
24
|
+
def class_name(window)
|
25
|
+
@app_win[window][:resource_class]
|
26
|
+
end
|
27
|
+
|
28
|
+
def app_title(win_id)
|
29
|
+
@app_win[win_id][:caption]
|
30
|
+
end
|
31
|
+
|
32
|
+
def set(id, caption, resource_name, resource_class)
|
33
|
+
@app_win[id] = {caption: caption, resource_name: resource_name, resource_class: resource_class}
|
34
|
+
@focus_win = id
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove(id)
|
38
|
+
@app_win.delete id
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_service
|
42
|
+
winattr = self
|
43
|
+
|
44
|
+
bus = DBus.session_bus
|
45
|
+
service = bus.request_service('net.tmtms.Rkremap')
|
46
|
+
klass = Class.new(DBus::Object) do
|
47
|
+
dbus_interface 'net.tmtms.RkremapInterface' do
|
48
|
+
dbus_method :windowActivated, 'in id:s, in caption:s, in resourceName:s, in resourceClass:s' do |id, caption, resource_name, resource_class|
|
49
|
+
winattr.set(id, caption, resource_name, resource_class)
|
50
|
+
end
|
51
|
+
dbus_method :windowRemoved, 'in id:s' do |id|
|
52
|
+
winattr.remove(id)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
obj = klass.new('/net/tmtms/Rkremap')
|
57
|
+
service.export(obj)
|
58
|
+
|
59
|
+
Thread.new do
|
60
|
+
loop = DBus::Main.new
|
61
|
+
loop << bus
|
62
|
+
loop.run
|
63
|
+
end
|
64
|
+
sleep 0.1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'fiddle/import'
|
2
|
+
|
3
|
+
class Rkremap
|
4
|
+
# アプリの name, class, title を取得する
|
5
|
+
class WinAttrX11
|
6
|
+
def initialize
|
7
|
+
@app_win = {}
|
8
|
+
@display = X11.XOpenDisplay(nil) or raise "Cannot open display: #{ENV['DISPLAY']}"
|
9
|
+
@buf = ' '*8
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Integer] Window
|
13
|
+
def focus_win
|
14
|
+
win = X11::Window.malloc(Fiddle::RUBY_FREE)
|
15
|
+
X11.XGetInputFocus(@display, win, @buf)
|
16
|
+
win.window
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Array<window, name, class>]
|
20
|
+
def app_win(window)
|
21
|
+
return @app_win[window] if @app_win[window]
|
22
|
+
class_hint = X11::XClassHint.malloc(Fiddle::RUBY_FREE)
|
23
|
+
parent = X11::Window.malloc(Fiddle::RUBY_FREE)
|
24
|
+
children = X11::Pointer.malloc(Fiddle::RUBY_FREE)
|
25
|
+
win = window
|
26
|
+
while win > 0
|
27
|
+
class_hint.name = nil
|
28
|
+
class_hint.class_name = nil
|
29
|
+
X11.XGetClassHint(@display, win, class_hint)
|
30
|
+
X11.XQueryTree(@display, win, @buf, parent, children, @buf)
|
31
|
+
X11.XFree(children.ptr)
|
32
|
+
break unless class_hint.name.null? && class_hint.class_name.null?
|
33
|
+
win = parent.window
|
34
|
+
end
|
35
|
+
unless class_hint.name.null?
|
36
|
+
win_name = class_hint.name.to_s
|
37
|
+
X11.XFree(class_hint.name)
|
38
|
+
end
|
39
|
+
unless class_hint.class_name.null?
|
40
|
+
win_class = class_hint.class_name.to_s
|
41
|
+
X11.XFree(class_hint.class_name)
|
42
|
+
end
|
43
|
+
@app_win[window] = [win, win_name, win_class]
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [String]
|
47
|
+
def class_name(window)
|
48
|
+
app_win(window)[2]
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [String]
|
52
|
+
def app_title(window)
|
53
|
+
win = app_win(window)[0]
|
54
|
+
return '' if win == 0
|
55
|
+
prop = X11::XTextProperty.malloc(Fiddle::RUBY_FREE)
|
56
|
+
text_list = X11::Pointer.malloc(Fiddle::RUBY_FREE)
|
57
|
+
X11.XGetWMName(@display, win, prop)
|
58
|
+
X11.Xutf8TextPropertyToTextList(@display, prop, text_list, @buf)
|
59
|
+
ptr = text_list.ptr
|
60
|
+
return '' if ptr.null?
|
61
|
+
title = ptr.ptr.to_s.force_encoding('utf-8')
|
62
|
+
X11.XFreeStringList(text_list.ptr)
|
63
|
+
title
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module X11
|
68
|
+
extend Fiddle::Importer
|
69
|
+
|
70
|
+
dlload 'libX11.so.6'
|
71
|
+
typealias 'XID', 'unsigned long'
|
72
|
+
typealias 'Window', 'XID'
|
73
|
+
typealias 'Status', 'int'
|
74
|
+
typealias 'Atom', 'unsigned long'
|
75
|
+
Window = struct ['Window window']
|
76
|
+
Pointer = struct ['void *ptr']
|
77
|
+
XClassHint = struct ['char *name', 'char *class_name']
|
78
|
+
XTextProperty = struct ['unsigned char *value', 'Atom encoding', 'int format', 'unsigned long nitems']
|
79
|
+
|
80
|
+
extern 'Display* XOpenDisplay(char*)'
|
81
|
+
extern 'int XGetInputFocus(Display*, Window*, int*)'
|
82
|
+
extern 'int XGetClassHint(Display*, Window, XClassHint*)'
|
83
|
+
extern 'Status XQueryTree(Display*, Window, Window*, Window*, Window**, unsigned int*)'
|
84
|
+
extern 'Status XGetWMName(Display*, Window, XTextProperty*)'
|
85
|
+
extern 'int Xutf8TextPropertyToTextList(Display*, XTextProperty*, char***, int*)'
|
86
|
+
extern 'int XFree(void*)'
|
87
|
+
extern 'void XFreeStringList(char**)'
|
88
|
+
end
|
89
|
+
end
|
data/lib/rkremap.rb
CHANGED
@@ -29,6 +29,7 @@ class Rkremap
|
|
29
29
|
attr_accessor :grab
|
30
30
|
attr_accessor :modifiers
|
31
31
|
attr_accessor :x11
|
32
|
+
attr_accessor :window_system
|
32
33
|
attr_accessor :auto_detect
|
33
34
|
attr_accessor :exclude
|
34
35
|
attr_accessor :mouse
|
@@ -48,6 +49,7 @@ class Rkremap
|
|
48
49
|
@uinput = Uinput.new
|
49
50
|
@grab = false
|
50
51
|
@x11 = false
|
52
|
+
@window_system = @x11 ? 'x11' : nil # 'x11' or 'kde'
|
51
53
|
@modifiers = [
|
52
54
|
KEY_LEFTCTRL, KEY_RIGHTCTRL,
|
53
55
|
KEY_LEFTSHIFT, KEY_RIGHTSHIFT,
|
@@ -82,7 +84,8 @@ class Rkremap
|
|
82
84
|
@evdev_list.grab = @grab
|
83
85
|
@evdev_list.detect_loop
|
84
86
|
@mod_state = @modifiers.map.to_h{|m| [m, false]}
|
85
|
-
|
87
|
+
@window_system = 'x11' if @x11
|
88
|
+
winattr = WinAttr.for(@window_system) if @window_system
|
86
89
|
@events = []
|
87
90
|
while true
|
88
91
|
@keys = []
|
@@ -134,7 +137,7 @@ class Rkremap
|
|
134
137
|
write_event(mcode, state ? 1 : 0)
|
135
138
|
end
|
136
139
|
block.call
|
137
|
-
mod_diff(mod).
|
140
|
+
mod_diff(mod).each_key do |mcode|
|
138
141
|
write_event(mcode, @mod_state[mcode] ? 1 : 0)
|
139
142
|
end
|
140
143
|
end
|
@@ -186,7 +189,7 @@ class Rkremap
|
|
186
189
|
|
187
190
|
# @return [String, nil]
|
188
191
|
def class_name
|
189
|
-
@winattr.
|
192
|
+
@winattr.class_name(@win) if @winattr && @win
|
190
193
|
end
|
191
194
|
|
192
195
|
# @return [String, nil]
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rkremap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- TOMITA Masahiro
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: tmtms_timer
|
@@ -33,6 +32,7 @@ extra_rdoc_files: []
|
|
33
32
|
files:
|
34
33
|
- README.md
|
35
34
|
- example/henkan-super.rb
|
35
|
+
- example/hoge.rb
|
36
36
|
- example/keylogger.rb
|
37
37
|
- example/mac-shortcut.rb
|
38
38
|
- example/tmtms.rb
|
@@ -45,6 +45,8 @@ files:
|
|
45
45
|
- lib/rkremap/uinput.rb
|
46
46
|
- lib/rkremap/version.rb
|
47
47
|
- lib/rkremap/winattr.rb
|
48
|
+
- lib/rkremap/winattr_kde.rb
|
49
|
+
- lib/rkremap/winattr_x11.rb
|
48
50
|
homepage: https://gitlab.com/tmtms/rkremap
|
49
51
|
licenses:
|
50
52
|
- MIT
|
@@ -53,7 +55,6 @@ metadata:
|
|
53
55
|
source_code_uri: https://gitlab.com/tmtms/rkremap
|
54
56
|
documentation_uri: https://www.rubydoc.info/gems/rkremap
|
55
57
|
rubygems_mfa_required: 'true'
|
56
|
-
post_install_message:
|
57
58
|
rdoc_options: []
|
58
59
|
require_paths:
|
59
60
|
- lib
|
@@ -68,8 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
69
|
- !ruby/object:Gem::Version
|
69
70
|
version: '0'
|
70
71
|
requirements: []
|
71
|
-
rubygems_version: 3.
|
72
|
-
signing_key:
|
72
|
+
rubygems_version: 3.7.0.dev
|
73
73
|
specification_version: 4
|
74
74
|
summary: key remapper
|
75
75
|
test_files: []
|