fusuma 3.0.0 → 3.1.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/exe/fusuma +4 -0
- data/fusuma.gemspec +3 -2
- data/lib/fusuma/config/index.rb +1 -1
- data/lib/fusuma/config/searcher.rb +57 -2
- data/lib/fusuma/config.rb +43 -6
- data/lib/fusuma/custom_process.rb +1 -1
- data/lib/fusuma/environment.rb +6 -0
- data/lib/fusuma/hash_support.rb +22 -0
- data/lib/fusuma/plugin/base.rb +12 -6
- data/lib/fusuma/plugin/buffers/gesture_buffer.rb +6 -7
- data/lib/fusuma/plugin/detectors/hold_detector.rb +11 -7
- data/lib/fusuma/plugin/detectors/swipe_detector.rb +1 -1
- data/lib/fusuma/plugin/inputs/libinput_command_input.yml +3 -0
- data/lib/fusuma/plugin/manager.rb +6 -0
- data/lib/fusuma/version.rb +1 -1
- data/lib/fusuma.rb +8 -3
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ff7ea9fcd08ba0c48e1c08d9eceeb4a1e30684a0616f37fef6f67a3e035c005
|
4
|
+
data.tar.gz: c68adc10a8e2b7fb5396c7c789f941b13bacc9d60d6d669a7d8004c773503d75
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 46fdbcb0b697ba9000e55de86a9550d783c81c6407f13ebe82375c2b741341a591bcf45fe6c707247a9a2017e4f3ca4834111af551a4a65001cc7e944487b28e
|
7
|
+
data.tar.gz: e0e579b0d73ea237202ae17f641c07a1ed7587a771c71bfe12d55edec62b3d9e3281fd010f646e6e7077a6245e0365d0ed73c7042528a8c8c5ac72bbeed956de
|
data/exe/fusuma
CHANGED
@@ -27,6 +27,10 @@ opt.on('--log=path/to/file',
|
|
27
27
|
option[:log_filepath] = v
|
28
28
|
end
|
29
29
|
|
30
|
+
opt.on('--show-config', 'Show config as YAML format which is loaded internally') do |v|
|
31
|
+
option[:show_config] = v
|
32
|
+
end
|
33
|
+
|
30
34
|
opt.on('--device="Device name"',
|
31
35
|
'Open the given device only (DEPRECATED)') do |v|
|
32
36
|
option[:device] = v
|
data/fusuma.gemspec
CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
"yard.run" => "yri" # use "yard" to build full HTML docs.
|
25
25
|
}
|
26
26
|
|
27
|
-
spec.required_ruby_version = ">= 2.
|
28
|
-
#
|
27
|
+
spec.required_ruby_version = ">= 2.7"
|
28
|
+
# https://packages.ubuntu.com/search?keywords=ruby&searchon=names&exact=1&suite=all§ion=main
|
29
|
+
# support focal (20.04LTS) 2.7
|
29
30
|
end
|
data/lib/fusuma/config/index.rb
CHANGED
@@ -76,6 +76,8 @@ module Fusuma
|
|
76
76
|
end
|
77
77
|
|
78
78
|
class << self
|
79
|
+
attr_reader :context
|
80
|
+
|
79
81
|
# Search with context from load_streamed Config
|
80
82
|
# @param context [Hash]
|
81
83
|
# @return [Object]
|
@@ -86,20 +88,50 @@ module Fusuma
|
|
86
88
|
result
|
87
89
|
end
|
88
90
|
|
91
|
+
CONEXT_SEARCH_ORDER = [:no_context, :complete_match_context, :partial_match_context]
|
89
92
|
# Return a matching context from config
|
90
93
|
# @params request_context [Hash]
|
91
94
|
# @return [Hash]
|
92
|
-
def find_context(request_context, &block)
|
95
|
+
def find_context(request_context, fallbacks = CONEXT_SEARCH_ORDER, &block)
|
93
96
|
# Search in blocks in the following order.
|
94
97
|
# 1. primary context(no context)
|
95
98
|
# 2. complete match config[:context] == request_context
|
96
99
|
# 3. partial match config[:context] =~ request_context
|
100
|
+
# no_context?(&block) ||
|
101
|
+
# complete_match_context(request_context, &block) ||
|
102
|
+
# partial_match_context(request_context, &block)
|
103
|
+
fallbacks.each do |method|
|
104
|
+
result = send(method, request_context, &block)
|
105
|
+
return result if result
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# No context(primary context)
|
112
|
+
# @return [Hash]
|
113
|
+
# @return [NilClass]
|
114
|
+
def no_context(_request_context, &block)
|
97
115
|
return {} if with_context({}, &block)
|
116
|
+
end
|
98
117
|
|
118
|
+
# Complete match request context
|
119
|
+
# @param request_context [Hash]
|
120
|
+
# @return [Hash] matched context
|
121
|
+
# @return [NilClass] if not matched
|
122
|
+
def complete_match_context(request_context, &block)
|
99
123
|
Config.instance.keymap.each do |config|
|
100
124
|
next unless config[:context] == request_context
|
101
125
|
return config[:context] if with_context(config[:context], &block)
|
102
126
|
end
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
|
130
|
+
# One of multiple request contexts matched
|
131
|
+
# @param request_context [Hash]
|
132
|
+
# @return [Hash] matched context
|
133
|
+
# @return [NilClass] if not matched
|
134
|
+
def partial_match_context(request_context, &block)
|
103
135
|
if request_context.keys.size > 1
|
104
136
|
Config.instance.keymap.each do |config|
|
105
137
|
next if config[:context].nil?
|
@@ -107,11 +139,34 @@ module Fusuma
|
|
107
139
|
next unless config[:context].all? { |k, v| request_context[k] == v }
|
108
140
|
return config[:context] if with_context(config[:context], &block)
|
109
141
|
end
|
142
|
+
nil
|
110
143
|
end
|
111
144
|
end
|
112
145
|
|
113
|
-
|
146
|
+
# Search context for plugin
|
147
|
+
# If the plugin_defaults key is a complete match,
|
148
|
+
# it is the default value for that plugin, so it is postponed.
|
149
|
+
# This is because prioritize overwriting by other plugins.
|
150
|
+
# The search order is as follows
|
151
|
+
# 1. complete match config[:context].key?(:plugin_defaults)
|
152
|
+
# 2. complete match config[:context] == request_context
|
153
|
+
# @param request_context [Hash]
|
154
|
+
# @return [Hash] matched context
|
155
|
+
# @return [NilClass] if not matched
|
156
|
+
def plugin_default_context(request_context, &block)
|
157
|
+
complete_match_context = nil
|
158
|
+
Config.instance.keymap.each do |config|
|
159
|
+
next unless config[:context]&.key?(:plugin_defaults)
|
160
|
+
|
161
|
+
if config[:context][:plugin_defaults] == request_context[:plugin_defaults]
|
162
|
+
complete_match_context = config[:context]
|
163
|
+
next
|
164
|
+
end
|
114
165
|
|
166
|
+
return config[:context] if with_context(config[:context], &block)
|
167
|
+
end
|
168
|
+
complete_match_context
|
169
|
+
end
|
115
170
|
end
|
116
171
|
end
|
117
172
|
end
|
data/lib/fusuma/config.rb
CHANGED
@@ -4,6 +4,7 @@ require_relative "./multi_logger"
|
|
4
4
|
require_relative "./config/index"
|
5
5
|
require_relative "./config/searcher"
|
6
6
|
require_relative "./config/yaml_duplication_checker"
|
7
|
+
require_relative "./plugin/manager"
|
7
8
|
require_relative "./hash_support"
|
8
9
|
require "singleton"
|
9
10
|
require "yaml"
|
@@ -32,7 +33,7 @@ module Fusuma
|
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
35
|
-
attr_reader :
|
36
|
+
attr_reader :custom_path, :searcher
|
36
37
|
|
37
38
|
def initialize
|
38
39
|
@searcher = Searcher.new
|
@@ -45,17 +46,47 @@ module Fusuma
|
|
45
46
|
reload
|
46
47
|
end
|
47
48
|
|
49
|
+
def keymap
|
50
|
+
# FIXME: @keymap is not initialized when called from outside Fusuma::Runner like fusuma-senkey
|
51
|
+
@keymap || reload.keymap
|
52
|
+
end
|
53
|
+
|
48
54
|
def reload
|
55
|
+
plugin_defaults = plugin_defaults_paths.map do |default_yml|
|
56
|
+
{
|
57
|
+
context: {plugin_defaults: default_yml.split("/").last.delete_suffix(".yml")},
|
58
|
+
**validate(default_yml)[0]
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
config_path = find_config_filepath
|
63
|
+
@keymap = validate(config_path) | plugin_defaults
|
64
|
+
MultiLogger.info "reload config: #{config_path}"
|
65
|
+
|
66
|
+
# reset searcher cache
|
49
67
|
@searcher = Searcher.new
|
50
|
-
|
51
|
-
MultiLogger.info "reload config: #{path}"
|
52
|
-
@keymap = validate(path)
|
68
|
+
|
53
69
|
self
|
54
70
|
rescue InvalidFileError => e
|
55
71
|
MultiLogger.error e.message
|
56
72
|
exit 1
|
57
73
|
end
|
58
74
|
|
75
|
+
# @param key [Symbol]
|
76
|
+
# @param base [Config::Index]
|
77
|
+
# @return [Hash]
|
78
|
+
def fetch_config_params(key, base)
|
79
|
+
request_context = {plugin_defaults: base.keys.last.symbol.to_s}
|
80
|
+
fallbacks = [:no_context, :plugin_default_context]
|
81
|
+
Config::Searcher.find_context(request_context, fallbacks) do
|
82
|
+
ret = Config.search(base)
|
83
|
+
if ret&.key?(key)
|
84
|
+
return ret
|
85
|
+
end
|
86
|
+
end
|
87
|
+
{}
|
88
|
+
end
|
89
|
+
|
59
90
|
# @return [Hash] If check passes
|
60
91
|
# @raise [InvalidFileError] If check does not pass
|
61
92
|
def validate(path)
|
@@ -75,7 +106,6 @@ module Fusuma
|
|
75
106
|
end
|
76
107
|
|
77
108
|
# @param index [Index]
|
78
|
-
# @param context [Hash]
|
79
109
|
def search(index)
|
80
110
|
@searcher.search_with_cache(index, location: keymap)
|
81
111
|
end
|
@@ -95,7 +125,7 @@ module Fusuma
|
|
95
125
|
|
96
126
|
private
|
97
127
|
|
98
|
-
def
|
128
|
+
def find_config_filepath
|
99
129
|
filename = "fusuma/config.yml"
|
100
130
|
if custom_path
|
101
131
|
return expand_custom_path if File.exist?(expand_custom_path)
|
@@ -120,5 +150,12 @@ module Fusuma
|
|
120
150
|
def expand_default_path(filename)
|
121
151
|
File.expand_path "../../#{filename}", __FILE__
|
122
152
|
end
|
153
|
+
|
154
|
+
def plugin_defaults_paths
|
155
|
+
Plugin::Manager.load_paths.map do |plugin_path|
|
156
|
+
yml = plugin_path.gsub(/\.rb$/, ".yml")
|
157
|
+
yml if File.exist?(yml)
|
158
|
+
end.compact
|
159
|
+
end
|
123
160
|
end
|
124
161
|
end
|
data/lib/fusuma/environment.rb
CHANGED
data/lib/fusuma/hash_support.rb
CHANGED
@@ -2,6 +2,28 @@
|
|
2
2
|
|
3
3
|
# Patch to hash
|
4
4
|
class Hash
|
5
|
+
# activesupport-5.2.0/lib/active_support/core_ext/hash/deep_merge.rb
|
6
|
+
def deep_merge(other_hash, &block)
|
7
|
+
dup.deep_merge!(other_hash, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Same as +deep_merge+, but modifies +self+.
|
11
|
+
def deep_merge!(other_hash, &block)
|
12
|
+
merge!(other_hash) do |key, this_val, other_val|
|
13
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
14
|
+
this_val.deep_merge(other_val, &block)
|
15
|
+
elsif block
|
16
|
+
block.call(key, this_val, other_val)
|
17
|
+
else
|
18
|
+
other_val
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def deep_stringify_keys
|
24
|
+
deep_transform_keys(&:to_s)
|
25
|
+
end
|
26
|
+
|
5
27
|
# activesupport-4.1.1/lib/active_support/core_ext/hash/keys.rb
|
6
28
|
def deep_symbolize_keys
|
7
29
|
deep_transform_keys do |key|
|
data/lib/fusuma/plugin/base.rb
CHANGED
@@ -31,14 +31,20 @@ module Fusuma
|
|
31
31
|
raise NotImplementedError, "override #{self.class.name}##{__method__}"
|
32
32
|
end
|
33
33
|
|
34
|
+
# @param key [Symbol]
|
35
|
+
# @param base [Config::Index]
|
34
36
|
# @return [Object]
|
35
|
-
def config_params(key = nil
|
36
|
-
|
37
|
+
def config_params(key = nil)
|
38
|
+
@config_params ||= {}
|
39
|
+
if @config_params["#{config_index.cache_key},#{key}"]
|
40
|
+
return @config_params["#{config_index.cache_key},#{key}"]
|
41
|
+
end
|
42
|
+
|
43
|
+
params = Config.instance.fetch_config_params(key, config_index)
|
37
44
|
|
38
45
|
return params unless key
|
39
46
|
|
40
|
-
@config_params
|
41
|
-
@config_params["#{base.cache_key},#{key}"] ||=
|
47
|
+
@config_params["#{config_index.cache_key},#{key}"] =
|
42
48
|
params.fetch(key, nil).tap do |val|
|
43
49
|
next if val.nil?
|
44
50
|
|
@@ -48,14 +54,14 @@ module Fusuma
|
|
48
54
|
next if param_types.any? { |klass| val.is_a?(klass) }
|
49
55
|
|
50
56
|
MultiLogger.error("Please fix config.yml.")
|
51
|
-
MultiLogger.error(":#{
|
57
|
+
MultiLogger.error(":#{config_index.keys.map(&:symbol)
|
52
58
|
.join(" => :")} => :#{key} should be #{param_types.join(" OR ")}.")
|
53
59
|
exit 1
|
54
60
|
end
|
55
61
|
end
|
56
62
|
|
57
63
|
def config_index
|
58
|
-
Config::Index.new(self.class.name.gsub("Fusuma::", "").underscore.split("/"))
|
64
|
+
@config_index ||= Config::Index.new(self.class.name.gsub("Fusuma::", "").underscore.split("/"))
|
59
65
|
end
|
60
66
|
end
|
61
67
|
end
|
@@ -11,7 +11,6 @@ module Fusuma
|
|
11
11
|
DEFAULT_SOURCE = "libinput_gesture_parser"
|
12
12
|
DEFAULT_SECONDS_TO_KEEP = 100
|
13
13
|
|
14
|
-
|
15
14
|
def initialize(*args)
|
16
15
|
super(*args)
|
17
16
|
@cache = {}
|
@@ -84,15 +83,15 @@ module Fusuma
|
|
84
83
|
# @param attr [Symbol]
|
85
84
|
# @return [Float]
|
86
85
|
def sum_last10_attrs(attr) # sums last 10 values of attr (or all if length < 10)
|
87
|
-
cache_entry = (
|
86
|
+
cache_entry = (@cache_sum10[attr] ||= CacheEntry.new(0, 0))
|
88
87
|
upd_ev = updating_events
|
89
|
-
if upd_ev.length > cache_entry.checked + 1
|
88
|
+
if upd_ev.length > cache_entry.checked + 1
|
90
89
|
cache_entry.value = upd_ev.last(10).map do |gesture_event|
|
91
90
|
gesture_event.record.delta[attr].to_f
|
92
91
|
end.reduce(:+)
|
93
92
|
elsif upd_ev.length > cache_entry.checked
|
94
93
|
cache_entry.value = cache_entry.value + upd_ev[-1].record.delta[attr].to_f - \
|
95
|
-
(upd_ev.length > 10 ? upd_ev[-11].record.delta[attr].to_f : 0)
|
94
|
+
((upd_ev.length > 10) ? upd_ev[-11].record.delta[attr].to_f : 0)
|
96
95
|
else
|
97
96
|
return cache_entry.value
|
98
97
|
end
|
@@ -101,7 +100,7 @@ module Fusuma
|
|
101
100
|
end
|
102
101
|
|
103
102
|
def updating_events
|
104
|
-
cache_entry = (
|
103
|
+
cache_entry = (@cache[:updating_events] ||= CacheEntry.new(0, []))
|
105
104
|
cache_entry.checked.upto(@events.length - 1).each do |i|
|
106
105
|
(cache_entry.value << @events[i]) if @events[i].record.status == "update"
|
107
106
|
end
|
@@ -140,7 +139,7 @@ module Fusuma
|
|
140
139
|
end
|
141
140
|
|
142
141
|
def select_by_type(type)
|
143
|
-
cache_entry = (
|
142
|
+
cache_entry = (@cache_select_by[type] ||= CacheEntry.new(0, self.class.new([])))
|
144
143
|
cache_entry.checked.upto(@events.length - 1).each do |i|
|
145
144
|
(cache_entry.value.events << @events[i]) if @events[i].record.gesture == type
|
146
145
|
end
|
@@ -150,7 +149,7 @@ module Fusuma
|
|
150
149
|
|
151
150
|
def select_from_last_begin
|
152
151
|
return self if empty?
|
153
|
-
cache_entry = (
|
152
|
+
cache_entry = (@cache[:last_begin] ||= CacheEntry.new(0, nil))
|
154
153
|
|
155
154
|
cache_entry.value = (@events.length - 1).downto(cache_entry.checked).find do |i|
|
156
155
|
@events[i].record.status == "begin"
|
@@ -11,10 +11,14 @@ module Fusuma
|
|
11
11
|
SOURCES = %w[gesture timer].freeze
|
12
12
|
BUFFER_TYPE = "gesture"
|
13
13
|
GESTURE_RECORD_TYPE = "hold"
|
14
|
-
Timer = Inputs::TimerInput.instance
|
15
14
|
|
16
15
|
BASE_THERESHOLD = 0.7
|
17
16
|
|
17
|
+
def initialize(*args)
|
18
|
+
super(*args)
|
19
|
+
@timer = Inputs::TimerInput.instance
|
20
|
+
end
|
21
|
+
|
18
22
|
# @param buffers [Array<Buffers::Buffer>]
|
19
23
|
# @return [Events::Event] if event is detected
|
20
24
|
# @return [Array<Events::Event>] if hold end event is detected
|
@@ -50,10 +54,10 @@ module Fusuma
|
|
50
54
|
repeat_index = create_repeat_index(finger: finger, status: status)
|
51
55
|
oneshot_index = create_oneshot_index(finger: finger)
|
52
56
|
|
53
|
-
if status == "begin"
|
57
|
+
if status == "begin"
|
54
58
|
@timeout = nil
|
55
|
-
if threshold(index: oneshot_index) <
|
56
|
-
|
59
|
+
if threshold(index: oneshot_index) < @timer.interval
|
60
|
+
@timer.wake_early(Time.now + threshold(index: oneshot_index))
|
57
61
|
end
|
58
62
|
elsif status == "timer"
|
59
63
|
return if @timeout
|
@@ -115,10 +119,10 @@ module Fusuma
|
|
115
119
|
|
116
120
|
def enough?(index:, holding_time:)
|
117
121
|
diff = threshold(index: index) - holding_time
|
118
|
-
if diff < 0
|
122
|
+
if diff < 0
|
119
123
|
true
|
120
|
-
elsif diff <
|
121
|
-
|
124
|
+
elsif diff < @timer.interval
|
125
|
+
@timer.wake_early(Time.now + diff)
|
122
126
|
false
|
123
127
|
end
|
124
128
|
end
|
@@ -115,6 +115,12 @@ module Fusuma
|
|
115
115
|
@plugins ||= {}
|
116
116
|
end
|
117
117
|
|
118
|
+
# @return [Array<String>]
|
119
|
+
# @example
|
120
|
+
# Manager.load_paths
|
121
|
+
# => ["/path/to/fusuma/lib/fusuma/plugin/inputs/input.rb",
|
122
|
+
# "/path/to/fusuma/lib/fusuma/plugin/inputs/libinput_command_input.rb",
|
123
|
+
# "/path/to/fusuma/lib/fusuma/plugin/inputs/timer_input.rb"]
|
118
124
|
def load_paths
|
119
125
|
@load_paths ||= []
|
120
126
|
end
|
data/lib/fusuma/version.rb
CHANGED
data/lib/fusuma.rb
CHANGED
@@ -27,13 +27,18 @@ module Fusuma
|
|
27
27
|
MultiLogger.filepath = option[:log_filepath]
|
28
28
|
MultiLogger.instance.debug_mode = option[:verbose]
|
29
29
|
|
30
|
-
load_custom_config(option[:config_path])
|
31
|
-
|
32
30
|
Plugin::Manager.require_base_plugins
|
33
31
|
|
32
|
+
load_custom_config(option[:config_path])
|
33
|
+
|
34
34
|
Environment.dump_information
|
35
35
|
Kernel.exit(0) if option[:version]
|
36
36
|
|
37
|
+
if option[:show_config]
|
38
|
+
Environment.print_config
|
39
|
+
Kernel.exit(0)
|
40
|
+
end
|
41
|
+
|
37
42
|
if option[:list]
|
38
43
|
Environment.print_device_list
|
39
44
|
Kernel.exit(0)
|
@@ -147,7 +152,7 @@ module Fusuma
|
|
147
152
|
matched_context = nil
|
148
153
|
event = main_events.find do |main_event|
|
149
154
|
matched_context = Config::Searcher.find_context(request_context) do
|
150
|
-
if modified_record = main_event.record.merge(records: modifiers.map(&:record))
|
155
|
+
if (modified_record = main_event.record.merge(records: modifiers.map(&:record)))
|
151
156
|
main_event.record = modified_record
|
152
157
|
elsif !modifiers.empty?
|
153
158
|
# try basically the same, but without any modifiers
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fusuma
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- iberianpig
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Fusuma is multitouch gesture recognizer. This gem makes your touchpad
|
14
14
|
on Linux able to recognize swipes or pinchs and assign command to them. Read installation
|
@@ -59,6 +59,7 @@ files:
|
|
59
59
|
- lib/fusuma/plugin/filters/libinput_device_filter.rb
|
60
60
|
- lib/fusuma/plugin/inputs/input.rb
|
61
61
|
- lib/fusuma/plugin/inputs/libinput_command_input.rb
|
62
|
+
- lib/fusuma/plugin/inputs/libinput_command_input.yml
|
62
63
|
- lib/fusuma/plugin/inputs/timer_input.rb
|
63
64
|
- lib/fusuma/plugin/manager.rb
|
64
65
|
- lib/fusuma/plugin/parsers/libinput_gesture_parser.rb
|
@@ -79,14 +80,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
80
|
requirements:
|
80
81
|
- - ">="
|
81
82
|
- !ruby/object:Gem::Version
|
82
|
-
version: 2.
|
83
|
+
version: '2.7'
|
83
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
85
|
requirements:
|
85
86
|
- - ">="
|
86
87
|
- !ruby/object:Gem::Version
|
87
88
|
version: '0'
|
88
89
|
requirements: []
|
89
|
-
rubygems_version: 3.
|
90
|
+
rubygems_version: 3.4.10
|
90
91
|
signing_key:
|
91
92
|
specification_version: 4
|
92
93
|
summary: Multitouch gestures with libinput driver, Linux
|