fusuma 1.11.1 → 2.0.2
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 +49 -7
- data/fusuma.gemspec +6 -16
- data/lib/fusuma.rb +91 -28
- data/lib/fusuma/config.rb +34 -62
- data/lib/fusuma/config/index.rb +39 -6
- data/lib/fusuma/config/searcher.rb +166 -0
- data/lib/fusuma/custom_process.rb +13 -0
- data/lib/fusuma/device.rb +22 -7
- data/lib/fusuma/environment.rb +6 -4
- data/lib/fusuma/hash_support.rb +40 -0
- data/lib/fusuma/libinput_command.rb +17 -21
- data/lib/fusuma/multi_logger.rb +2 -6
- data/lib/fusuma/plugin/base.rb +18 -15
- data/lib/fusuma/plugin/buffers/buffer.rb +3 -2
- data/lib/fusuma/plugin/buffers/gesture_buffer.rb +34 -25
- data/lib/fusuma/plugin/buffers/timer_buffer.rb +46 -0
- data/lib/fusuma/plugin/detectors/detector.rb +26 -5
- data/lib/fusuma/plugin/detectors/pinch_detector.rb +109 -58
- data/lib/fusuma/plugin/detectors/rotate_detector.rb +91 -50
- data/lib/fusuma/plugin/detectors/swipe_detector.rb +93 -56
- data/lib/fusuma/plugin/events/event.rb +5 -4
- data/lib/fusuma/plugin/events/records/context_record.rb +27 -0
- data/lib/fusuma/plugin/events/records/gesture_record.rb +9 -6
- data/lib/fusuma/plugin/events/records/index_record.rb +46 -14
- data/lib/fusuma/plugin/events/records/record.rb +1 -1
- data/lib/fusuma/plugin/events/records/text_record.rb +2 -1
- data/lib/fusuma/plugin/executors/command_executor.rb +21 -6
- data/lib/fusuma/plugin/executors/executor.rb +45 -3
- data/lib/fusuma/plugin/filters/filter.rb +1 -1
- data/lib/fusuma/plugin/filters/libinput_device_filter.rb +6 -7
- data/lib/fusuma/plugin/filters/libinput_timeout_filter.rb +2 -2
- data/lib/fusuma/plugin/inputs/input.rb +64 -8
- data/lib/fusuma/plugin/inputs/libinput_command_input.rb +19 -9
- data/lib/fusuma/plugin/inputs/timer_input.rb +63 -0
- data/lib/fusuma/plugin/manager.rb +22 -29
- data/lib/fusuma/plugin/parsers/libinput_gesture_parser.rb +10 -8
- data/lib/fusuma/plugin/parsers/parser.rb +8 -9
- data/lib/fusuma/string_support.rb +16 -0
- data/lib/fusuma/version.rb +1 -1
- data/spec/helpers/config_helper.rb +20 -0
- data/spec/lib/config/searcher_spec.rb +97 -0
- data/spec/lib/config_spec.rb +112 -0
- data/spec/lib/custom_process_spec.rb +28 -0
- data/spec/lib/device_spec.rb +98 -0
- data/spec/lib/dummy_config.yml +31 -0
- data/spec/lib/fusuma_spec.rb +103 -0
- data/spec/lib/libinput-list-devices_iberianpig-XPS-9360.txt +181 -0
- data/spec/lib/libinput-list-devices_magic_trackpad.txt +51 -0
- data/spec/lib/libinput-list-devices_razer_razer_blade.txt +252 -0
- data/spec/lib/libinput-list-devices_thejinx0r.txt +361 -0
- data/spec/lib/libinput-list-devices_unavailable.txt +36 -0
- data/spec/lib/libinput_command_spec.rb +167 -0
- data/spec/lib/plugin/base_spec.rb +74 -0
- data/spec/lib/plugin/buffers/buffer_spec.rb +80 -0
- data/spec/lib/plugin/buffers/dummy_buffer.rb +20 -0
- data/spec/lib/plugin/buffers/gesture_buffer_spec.rb +172 -0
- data/spec/lib/plugin/detectors/detector_spec.rb +43 -0
- data/spec/lib/plugin/detectors/dummy_detector.rb +24 -0
- data/spec/lib/plugin/detectors/pinch_detector_spec.rb +119 -0
- data/spec/lib/plugin/detectors/rotate_detector_spec.rb +125 -0
- data/spec/lib/plugin/detectors/swipe_detector_spec.rb +118 -0
- data/spec/lib/plugin/events/event_spec.rb +30 -0
- data/spec/lib/plugin/events/records/gesture_record_spec.rb +22 -0
- data/spec/lib/plugin/events/records/record_spec.rb +31 -0
- data/spec/lib/plugin/events/records/text_record_spec.rb +26 -0
- data/spec/lib/plugin/executors/command_executor_spec.rb +57 -0
- data/spec/lib/plugin/executors/executor_spec.rb +160 -0
- data/spec/lib/plugin/filters/filter_spec.rb +92 -0
- data/spec/lib/plugin/filters/libinput_filter_spec.rb +120 -0
- data/spec/lib/plugin/inputs/input_spec.rb +70 -0
- data/spec/lib/plugin/inputs/libinput_command_input_spec.rb +120 -0
- data/spec/lib/plugin/inputs/timer_input_spec.rb +40 -0
- data/spec/lib/plugin/manager_spec.rb +27 -0
- data/spec/lib/plugin/parsers/parser_spec.rb +45 -0
- data/spec/spec_helper.rb +20 -0
- metadata +90 -167
- data/.github/FUNDING.yml +0 -8
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
- data/.github/pull_request_template.md +0 -9
- data/.github/stale.yml +0 -18
- data/.gitignore +0 -17
- data/.reek.yml +0 -96
- data/.rspec +0 -2
- data/.rubocop.yml +0 -37
- data/.rubocop_todo.yml +0 -40
- data/.travis.yml +0 -11
- data/CHANGELOG.md +0 -456
- data/CODE_OF_CONDUCT.md +0 -74
- data/CONTRIBUTING.md +0 -72
- data/Gemfile +0 -6
- data/Rakefile +0 -15
data/lib/fusuma/config/index.rb
CHANGED
@@ -19,6 +19,11 @@ module Fusuma
|
|
19
19
|
[Key.new(keys)]
|
20
20
|
end
|
21
21
|
end
|
22
|
+
|
23
|
+
def inspect
|
24
|
+
@keys.map(&:inspect)
|
25
|
+
end
|
26
|
+
|
22
27
|
attr_reader :keys
|
23
28
|
|
24
29
|
def cache_key
|
@@ -32,17 +37,45 @@ module Fusuma
|
|
32
37
|
end
|
33
38
|
end
|
34
39
|
|
40
|
+
# @return [Index]
|
41
|
+
def with_context
|
42
|
+
keys = @keys.map do |key|
|
43
|
+
next if Searcher.skip? && key.skippable
|
44
|
+
|
45
|
+
if Searcher.fallback? && key.fallback
|
46
|
+
key.fallback
|
47
|
+
else
|
48
|
+
key
|
49
|
+
end
|
50
|
+
end
|
51
|
+
self.class.new(keys.compact)
|
52
|
+
end
|
53
|
+
|
35
54
|
# Keys in Index
|
36
55
|
class Key
|
37
|
-
def initialize(symbol_word, skippable: false)
|
56
|
+
def initialize(symbol_word, skippable: false, fallback: nil)
|
38
57
|
@symbol = begin
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
58
|
+
symbol_word.to_sym
|
59
|
+
rescue StandardError
|
60
|
+
symbol_word
|
61
|
+
end
|
62
|
+
|
43
63
|
@skippable = skippable
|
64
|
+
|
65
|
+
@fallback = begin
|
66
|
+
fallback.to_sym
|
67
|
+
rescue StandardError
|
68
|
+
fallback
|
69
|
+
end
|
44
70
|
end
|
45
|
-
|
71
|
+
|
72
|
+
def inspect
|
73
|
+
skip_marker = @skippable && Searcher.skip? ? '(skip)' : ''
|
74
|
+
fallback_marker = @fallback && Searcher.fallback? ? '(fallback)' : ''
|
75
|
+
"#{@symbol}#{skip_marker}#{fallback_marker}"
|
76
|
+
end
|
77
|
+
|
78
|
+
attr_reader :symbol, :skippable, :fallback
|
46
79
|
end
|
47
80
|
end
|
48
81
|
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Index for searching value from config.yml
|
4
|
+
module Fusuma
|
5
|
+
class Config
|
6
|
+
# Search config.yml
|
7
|
+
class Searcher
|
8
|
+
def initialize
|
9
|
+
@cache = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param index [Index]
|
13
|
+
# @param location [Hash]
|
14
|
+
# @return [NilClass]
|
15
|
+
# @return [Hash]
|
16
|
+
# @return [Object]
|
17
|
+
def search(index, location:)
|
18
|
+
key = index.keys.first
|
19
|
+
return location if key.nil?
|
20
|
+
|
21
|
+
return nil if location.nil?
|
22
|
+
|
23
|
+
return nil unless location.is_a?(Hash)
|
24
|
+
|
25
|
+
next_index = Index.new(index.keys[1..-1])
|
26
|
+
|
27
|
+
value = nil
|
28
|
+
next_location_cadidates(location, key).find do |next_location|
|
29
|
+
value = search(next_index, location: next_location)
|
30
|
+
end
|
31
|
+
value
|
32
|
+
end
|
33
|
+
|
34
|
+
def search_with_context(index, location:, context:)
|
35
|
+
return nil if location.nil?
|
36
|
+
|
37
|
+
return search(index, location: location[0]) if context == {}
|
38
|
+
|
39
|
+
new_location = location.find do |conf|
|
40
|
+
search(index, location: conf) if conf[:context] == context
|
41
|
+
end
|
42
|
+
search(index, location: new_location)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param index [Index]
|
46
|
+
# @param location [Hash]
|
47
|
+
# @return [NilClass]
|
48
|
+
# @return [Hash]
|
49
|
+
# @return [Object]
|
50
|
+
def search_with_cache(index, location:)
|
51
|
+
cache([index.cache_key, Searcher.context, Searcher.skip?, Searcher.fallback?]) do
|
52
|
+
search_with_context(index, location: location, context: Searcher.context)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def cache(key)
|
57
|
+
@cache ||= {}
|
58
|
+
key = key.join(',') if key.is_a? Array
|
59
|
+
if @cache.key?(key)
|
60
|
+
@cache[key]
|
61
|
+
else
|
62
|
+
@cache[key] = block_given? ? yield : nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# next locations' candidates sorted by priority
|
69
|
+
# 1. look up location with key
|
70
|
+
# 2. fallback to other key
|
71
|
+
# 3. skip the key and go to child location
|
72
|
+
def next_location_cadidates(location, key)
|
73
|
+
[
|
74
|
+
location[key.symbol],
|
75
|
+
Searcher.skip? && key.skippable && location
|
76
|
+
].compact
|
77
|
+
end
|
78
|
+
|
79
|
+
class << self
|
80
|
+
# @return [Hash]
|
81
|
+
def conditions(&block)
|
82
|
+
{
|
83
|
+
nothing: -> { block.call },
|
84
|
+
skip: -> { Config::Searcher.skip { block.call } }
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
# Execute block with specified conditions
|
89
|
+
# @param conidtion [Symbol]
|
90
|
+
# @return [Object]
|
91
|
+
def with_condition(condition, &block)
|
92
|
+
conditions(&block)[condition].call
|
93
|
+
end
|
94
|
+
|
95
|
+
# Execute block with all conditions
|
96
|
+
# @return [Array<Symbol, Object>]
|
97
|
+
def find_condition(&block)
|
98
|
+
conditions(&block).find do |c, l|
|
99
|
+
result = l.call
|
100
|
+
return [c, result] if result
|
101
|
+
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Search with context from load_streamed Config
|
107
|
+
# @param context [Hash]
|
108
|
+
# @return [Object]
|
109
|
+
def with_context(context, &block)
|
110
|
+
@context = context || {}
|
111
|
+
result = block.call
|
112
|
+
@context = {}
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
# Return a matching context from config
|
117
|
+
# @params request_context [Hash]
|
118
|
+
# @return [Hash]
|
119
|
+
def find_context(request_context, &block)
|
120
|
+
# Search in blocks in the following order.
|
121
|
+
# 1. complete match config[:context] == request_context
|
122
|
+
# 2. partial match config[:context] =~ request_context
|
123
|
+
# 3. no context
|
124
|
+
Config.instance.keymap.each do |config|
|
125
|
+
next unless config[:context] == request_context
|
126
|
+
return config[:context] if with_context(config[:context]) { block.call }
|
127
|
+
end
|
128
|
+
if request_context.keys.size > 1
|
129
|
+
Config.instance.keymap.each do |config|
|
130
|
+
next if config[:context].nil?
|
131
|
+
|
132
|
+
next unless config[:context].all? { |k, v| request_context[k] == v }
|
133
|
+
return config[:context] if with_context(config[:context]) { block.call }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
return {} if with_context({}) { block.call }
|
137
|
+
end
|
138
|
+
|
139
|
+
attr_reader :context
|
140
|
+
|
141
|
+
def fallback?
|
142
|
+
@fallback
|
143
|
+
end
|
144
|
+
|
145
|
+
def skip?
|
146
|
+
@skip
|
147
|
+
end
|
148
|
+
|
149
|
+
# switch context for fallback
|
150
|
+
def fallback(&block)
|
151
|
+
@fallback = true
|
152
|
+
result = block.call
|
153
|
+
@fallback = false
|
154
|
+
result
|
155
|
+
end
|
156
|
+
|
157
|
+
def skip(&block)
|
158
|
+
@skip = true
|
159
|
+
result = block.call
|
160
|
+
@skip = false
|
161
|
+
result
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
data/lib/fusuma/device.rb
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative './multi_logger'
|
4
|
-
require_relative './libinput_command
|
4
|
+
require_relative './libinput_command'
|
5
5
|
|
6
6
|
module Fusuma
|
7
7
|
# detect input device
|
8
8
|
class Device
|
9
|
-
attr_reader :available
|
10
|
-
attr_reader :name
|
11
|
-
attr_reader :id
|
9
|
+
attr_reader :id, :name, :capabilities, :available
|
12
10
|
|
13
|
-
def initialize(id: nil, name: nil, available: nil)
|
11
|
+
def initialize(id: nil, name: nil, capabilities: nil, available: nil)
|
14
12
|
@id = id
|
15
13
|
@name = name
|
14
|
+
@capabilities = capabilities
|
16
15
|
@available = available
|
17
16
|
end
|
18
17
|
|
@@ -24,6 +23,8 @@ module Fusuma
|
|
24
23
|
@id = v
|
25
24
|
when :name
|
26
25
|
@name = v
|
26
|
+
when :capabilities
|
27
|
+
@capabilities = v
|
27
28
|
when :available
|
28
29
|
@available = v
|
29
30
|
end
|
@@ -31,9 +32,13 @@ module Fusuma
|
|
31
32
|
end
|
32
33
|
|
33
34
|
class << self
|
35
|
+
# Return devices
|
36
|
+
# sort devices by capabilities of gesture
|
34
37
|
# @return [Array]
|
35
38
|
def all
|
36
|
-
@all ||= fetch_devices
|
39
|
+
@all ||= fetch_devices.partition do |d|
|
40
|
+
d.capabilities.match?(/gesture/)
|
41
|
+
end.flatten
|
37
42
|
end
|
38
43
|
|
39
44
|
# @raise [SystemExit]
|
@@ -59,7 +64,9 @@ module Fusuma
|
|
59
64
|
# @return [Array]
|
60
65
|
def fetch_devices
|
61
66
|
line_parser = LineParser.new
|
62
|
-
|
67
|
+
|
68
|
+
libinput_command = Plugin::Inputs::LibinputCommandInput.new.command
|
69
|
+
libinput_command.list_devices do |line|
|
63
70
|
line_parser.push(line)
|
64
71
|
end
|
65
72
|
line_parser.generate_devices
|
@@ -102,6 +109,8 @@ module Fusuma
|
|
102
109
|
{ id: id }
|
103
110
|
elsif (name = name_from(line))
|
104
111
|
{ name: name }
|
112
|
+
elsif (capabilities = capabilities_from(line))
|
113
|
+
{ capabilities: capabilities }
|
105
114
|
elsif (available = available_from(line))
|
106
115
|
{ available: available }
|
107
116
|
else
|
@@ -121,6 +130,12 @@ module Fusuma
|
|
121
130
|
end
|
122
131
|
end
|
123
132
|
|
133
|
+
def capabilities_from(line)
|
134
|
+
line.match('^Capabilities:[[:space:]]*') do |m|
|
135
|
+
m.post_match.strip
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
124
139
|
def available_from(line)
|
125
140
|
# NOTE: is natural scroll available?
|
126
141
|
if line =~ /^Nat.scrolling: /
|
data/lib/fusuma/environment.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative './version
|
4
|
-
require_relative './libinput_command
|
5
|
-
require_relative './multi_logger
|
3
|
+
require_relative './version'
|
4
|
+
require_relative './libinput_command'
|
5
|
+
require_relative './multi_logger'
|
6
6
|
|
7
7
|
module Fusuma
|
8
8
|
# Output Environment information
|
@@ -17,8 +17,10 @@ module Fusuma
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def print_version
|
20
|
+
libinput_command = Plugin::Inputs::LibinputCommandInput.new.command
|
20
21
|
MultiLogger.info "Fusuma: #{VERSION}"
|
21
|
-
MultiLogger.info "libinput: #{
|
22
|
+
MultiLogger.info "libinput: #{libinput_command.version}"
|
23
|
+
MultiLogger.info "ruby #{ RUBY_VERSION }p#{ RUBY_PATCHLEVEL }"
|
22
24
|
MultiLogger.info "OS: #{`uname -rsv`}".strip
|
23
25
|
MultiLogger.info "Distribution: #{`cat /etc/issue`}".strip
|
24
26
|
MultiLogger.info "Desktop session: #{`echo $DESKTOP_SESSION $XDG_SESSION_TYPE`}".strip
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Patch to hash
|
4
|
+
class Hash
|
5
|
+
# activesupport-4.1.1/lib/active_support/core_ext/hash/keys.rb
|
6
|
+
def deep_symbolize_keys
|
7
|
+
deep_transform_keys do |key|
|
8
|
+
key.to_sym
|
9
|
+
rescue StandardError
|
10
|
+
key
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def deep_transform_keys(&block)
|
15
|
+
result = {}
|
16
|
+
each do |key, value|
|
17
|
+
result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
|
18
|
+
end
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
# activesupport/lib/active_support/core_ext/hash/deep_transform_values.rb
|
23
|
+
def deep_transform_values(&block)
|
24
|
+
_deep_transform_values_in_object(self, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Support methods for deep transforming nested hashes and arrays.
|
30
|
+
def _deep_transform_values_in_object(object, &block)
|
31
|
+
case object
|
32
|
+
when Hash
|
33
|
+
object.transform_values { |value| _deep_transform_values_in_object(value, &block) }
|
34
|
+
when Array
|
35
|
+
object.map { |e| _deep_transform_values_in_object(e, &block) }
|
36
|
+
else
|
37
|
+
yield(object)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'timeout'
|
3
|
+
require 'posix/spawn'
|
5
4
|
|
6
5
|
module Fusuma
|
7
6
|
# Execute libinput command
|
@@ -31,40 +30,37 @@ module Fusuma
|
|
31
30
|
end
|
32
31
|
|
33
32
|
# @yieldparam [String] gives a line in libinput list-devices output to the block
|
34
|
-
def list_devices
|
33
|
+
def list_devices(&block)
|
35
34
|
cmd = list_devices_command
|
36
35
|
MultiLogger.debug(list_devices: cmd)
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
p, i, o, e = POSIX::Spawn.popen4(cmd)
|
37
|
+
i.close
|
38
|
+
o.each(&block)
|
39
|
+
ensure
|
40
|
+
[i, o, e].each { |io| io.close unless io.closed? }
|
41
|
+
Process.waitpid(p)
|
40
42
|
end
|
41
43
|
|
42
|
-
# @
|
44
|
+
# @return [Integer, IO] return a latest line libinput debug-events
|
43
45
|
def debug_events
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
Timeout.timeout(wait_time) do
|
49
|
-
o.readline.chomp
|
50
|
-
end
|
51
|
-
rescue Timeout::Error
|
52
|
-
TIMEOUT_MESSAGE
|
53
|
-
end
|
54
|
-
yield(line)
|
55
|
-
end
|
46
|
+
@debug_events = begin
|
47
|
+
p, i, o, _e = POSIX::Spawn.popen4(debug_events_with_options)
|
48
|
+
i.close
|
49
|
+
[p, o]
|
56
50
|
end
|
57
51
|
end
|
58
52
|
|
59
53
|
# @return [String] command
|
60
54
|
# @raise [SystemExit]
|
61
55
|
def version_command
|
62
|
-
if
|
56
|
+
if @debug_events_command && @list_devices_command
|
57
|
+
"#{@list_devices_command} --version"
|
58
|
+
elsif which('libinput')
|
63
59
|
'libinput --version'
|
64
60
|
elsif which('libinput-list-devices')
|
65
61
|
'libinput-list-devices --version'
|
66
62
|
else
|
67
|
-
MultiLogger.error 'install libinput-tools'
|
63
|
+
MultiLogger.error 'Please install libinput-tools'
|
68
64
|
exit 1
|
69
65
|
end
|
70
66
|
end
|