fusuma 1.10.0 → 2.0.0.pre2
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/.github/pull_request_template.md +9 -0
- data/.rubocop.yml +27 -0
- data/.rubocop_todo.yml +34 -19
- data/.solargraph.yml +16 -0
- data/.travis.yml +1 -3
- data/CHANGELOG.md +56 -4
- data/CONTRIBUTING.md +72 -0
- data/Gemfile +17 -0
- data/README.md +53 -7
- data/fusuma.gemspec +4 -13
- data/lib/fusuma.rb +91 -29
- data/lib/fusuma/config.rb +59 -60
- data/lib/fusuma/config/index.rb +39 -6
- data/lib/fusuma/config/searcher.rb +164 -0
- data/lib/fusuma/config/yaml_duplication_checker.rb +42 -0
- data/lib/fusuma/custom_process.rb +13 -0
- data/lib/fusuma/device.rb +22 -7
- data/lib/fusuma/environment.rb +5 -4
- data/lib/fusuma/hash_support.rb +40 -0
- data/lib/fusuma/libinput_command.rb +16 -20
- 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 +12 -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 +63 -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 +10 -28
- 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
- metadata +20 -149
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fusuma
|
4
|
+
class Config
|
5
|
+
# ref: https://github.com/rubocop-hq/rubocop/blob/97e4ffc8a71e9e5239a927c6a534dfc1e0da917f/lib/rubocop/yaml_duplication_checker.rb
|
6
|
+
# Find duplicated keys from YAML.
|
7
|
+
module YAMLDuplicationChecker
|
8
|
+
def self.check(yaml_string, filename, &on_duplicated)
|
9
|
+
# Ruby 2.6+
|
10
|
+
tree = if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0')
|
11
|
+
# Specify filename to display helpful message when it raises
|
12
|
+
# an error.
|
13
|
+
YAML.parse(yaml_string, filename: filename)
|
14
|
+
else
|
15
|
+
YAML.parse(yaml_string, filename)
|
16
|
+
end
|
17
|
+
return unless tree
|
18
|
+
|
19
|
+
traverse(tree, &on_duplicated)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.traverse(tree, &on_duplicated)
|
23
|
+
case tree
|
24
|
+
when Psych::Nodes::Mapping
|
25
|
+
tree.children.each_slice(2).with_object([]) do |(key, value), keys|
|
26
|
+
exist = keys.find { |key2| key2.value == key.value }
|
27
|
+
on_duplicated.call(exist, key) if exist
|
28
|
+
keys << key
|
29
|
+
traverse(value, &on_duplicated)
|
30
|
+
end
|
31
|
+
else
|
32
|
+
children = tree.children
|
33
|
+
return unless children
|
34
|
+
|
35
|
+
children.each { |c| traverse(c, &on_duplicated) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private_class_method :traverse
|
40
|
+
end
|
41
|
+
end
|
42
|
+
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.sort_by do |d|
|
40
|
+
d.capabilities.match(/gesture/).to_s
|
41
|
+
end
|
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,9 @@ 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}"
|
22
23
|
MultiLogger.info "OS: #{`uname -rsv`}".strip
|
23
24
|
MultiLogger.info "Distribution: #{`cat /etc/issue`}".strip
|
24
25
|
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,35 +30,32 @@ 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'
|
data/lib/fusuma/multi_logger.rb
CHANGED
@@ -12,15 +12,11 @@ module Fusuma
|
|
12
12
|
attr_accessor :debug_mode
|
13
13
|
|
14
14
|
def initialize
|
15
|
-
super(
|
16
|
-
@err_logger = Logger.new(
|
15
|
+
super($stdout)
|
16
|
+
@err_logger = Logger.new($stderr)
|
17
17
|
@debug_mode = false
|
18
18
|
end
|
19
19
|
|
20
|
-
def info(msg)
|
21
|
-
super(msg)
|
22
|
-
end
|
23
|
-
|
24
20
|
def debug(msg)
|
25
21
|
return unless debug_mode?
|
26
22
|
|
data/lib/fusuma/plugin/base.rb
CHANGED
@@ -1,22 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative './manager
|
4
|
-
require_relative '../config
|
3
|
+
require_relative './manager'
|
4
|
+
require_relative '../config'
|
5
|
+
require_relative '../custom_process'
|
5
6
|
|
6
7
|
module Fusuma
|
7
8
|
module Plugin
|
8
9
|
# Create a Plugin Class with extending this class
|
9
10
|
class Base
|
11
|
+
include CustomProcess
|
10
12
|
# when inherited from subclass
|
11
13
|
def self.inherited(subclass)
|
14
|
+
super
|
12
15
|
subclass_path = caller_locations(1..1).first.path
|
13
16
|
Manager.add(plugin_class: subclass, plugin_path: subclass_path)
|
14
17
|
end
|
15
18
|
|
16
|
-
# get
|
17
|
-
# @
|
18
|
-
# [Vectors::Vector]
|
19
|
-
# @return [Array]
|
19
|
+
# get subclasses
|
20
|
+
# @return [Array<Class>]
|
20
21
|
def self.plugins
|
21
22
|
Manager.plugins[name]
|
22
23
|
end
|
@@ -33,19 +34,21 @@ module Fusuma
|
|
33
34
|
|
34
35
|
return params unless key
|
35
36
|
|
36
|
-
|
37
|
-
|
37
|
+
@config_params ||= {}
|
38
|
+
@config_params["#{base.cache_key},#{key}"] ||=
|
39
|
+
params.fetch(key, nil).tap do |val|
|
40
|
+
next if val.nil?
|
38
41
|
|
39
|
-
|
40
|
-
|
42
|
+
# NOTE: Type checking for config.yml
|
43
|
+
param_types = Array(config_param_types.fetch(key))
|
41
44
|
|
42
|
-
|
45
|
+
next if param_types.any? { |klass| val.is_a?(klass) }
|
43
46
|
|
44
|
-
|
45
|
-
|
47
|
+
MultiLogger.error('Please fix config.yml.')
|
48
|
+
MultiLogger.error(":#{base.keys.map(&:symbol)
|
46
49
|
.join(' => :')} => :#{key} should be #{param_types.join(' OR ')}.")
|
47
|
-
|
48
|
-
|
50
|
+
exit 1
|
51
|
+
end
|
49
52
|
end
|
50
53
|
|
51
54
|
def config_index
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../base
|
3
|
+
require_relative '../base'
|
4
4
|
|
5
5
|
module Fusuma
|
6
6
|
module Plugin
|
@@ -8,6 +8,7 @@ module Fusuma
|
|
8
8
|
# buffer events and output
|
9
9
|
class Buffer < Base
|
10
10
|
def initialize(*args)
|
11
|
+
super()
|
11
12
|
@events = Array.new(*args)
|
12
13
|
end
|
13
14
|
|
@@ -15,7 +16,7 @@ module Fusuma
|
|
15
16
|
|
16
17
|
# @return [String]
|
17
18
|
def type
|
18
|
-
self.class.name.underscore.split('/').last.gsub('_buffer', '')
|
19
|
+
@type ||= self.class.name.underscore.split('/').last.gsub('_buffer', '')
|
19
20
|
end
|
20
21
|
|
21
22
|
# @param event [Event]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative './buffer
|
3
|
+
require_relative './buffer'
|
4
4
|
|
5
5
|
module Fusuma
|
6
6
|
module Plugin
|
@@ -8,17 +8,17 @@ module Fusuma
|
|
8
8
|
# manage events and generate command
|
9
9
|
class GestureBuffer < Buffer
|
10
10
|
DEFAULT_SOURCE = 'libinput_gesture_parser'
|
11
|
-
DEFAULT_SECONDS_TO_KEEP =
|
11
|
+
DEFAULT_SECONDS_TO_KEEP = 100
|
12
12
|
|
13
13
|
def config_param_types
|
14
14
|
{
|
15
|
-
|
16
|
-
|
15
|
+
source: [String],
|
16
|
+
seconds_to_keep: [Float, Integer]
|
17
17
|
}
|
18
18
|
end
|
19
19
|
|
20
20
|
# @param event [Event]
|
21
|
-
# @return [Buffer,
|
21
|
+
# @return [Buffer, FalseClass]
|
22
22
|
def buffer(event)
|
23
23
|
# TODO: buffering events into buffer plugins
|
24
24
|
# - gesture event buffer
|
@@ -26,16 +26,13 @@ module Fusuma
|
|
26
26
|
# - other event buffer
|
27
27
|
return if event&.tag != source
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
self
|
32
|
-
else
|
33
|
-
clear
|
34
|
-
false
|
35
|
-
end
|
29
|
+
@events.push(event)
|
30
|
+
self
|
36
31
|
end
|
37
32
|
|
38
33
|
def clear_expired(current_time: Time.now)
|
34
|
+
clear if ended?
|
35
|
+
|
39
36
|
@seconds_to_keep ||= (config_params(:seconds_to_keep) || DEFAULT_SECONDS_TO_KEEP)
|
40
37
|
@events.each do |e|
|
41
38
|
break if current_time - e.time < @seconds_to_keep
|
@@ -46,17 +43,28 @@ module Fusuma
|
|
46
43
|
end
|
47
44
|
end
|
48
45
|
|
46
|
+
def ended?
|
47
|
+
return false if empty?
|
48
|
+
|
49
|
+
@events.last.record.status == 'end'
|
50
|
+
end
|
51
|
+
|
49
52
|
# @param attr [Symbol]
|
50
53
|
# @return [Float]
|
51
54
|
def sum_attrs(attr)
|
52
|
-
|
53
|
-
|
55
|
+
updating_events.map do |gesture_event|
|
56
|
+
gesture_event.record.delta[attr].to_f
|
57
|
+
end.inject(:+)
|
58
|
+
end
|
59
|
+
|
60
|
+
def updating_events
|
61
|
+
@events.select { |e| e.record.status == 'update' }
|
54
62
|
end
|
55
63
|
|
56
64
|
# @param attr [Symbol]
|
57
65
|
# @return [Float]
|
58
66
|
def avg_attrs(attr)
|
59
|
-
sum_attrs(attr).to_f /
|
67
|
+
sum_attrs(attr).to_f / updating_events.length
|
60
68
|
end
|
61
69
|
|
62
70
|
# return [Integer]
|
@@ -76,20 +84,21 @@ module Fusuma
|
|
76
84
|
@events.empty?
|
77
85
|
end
|
78
86
|
|
79
|
-
def select_by_events
|
80
|
-
return enum_for(:
|
87
|
+
def select_by_events(&block)
|
88
|
+
return enum_for(:select_by_events) unless block_given?
|
81
89
|
|
82
|
-
events = @events.select
|
90
|
+
events = @events.select(&block)
|
83
91
|
self.class.new events
|
84
92
|
end
|
85
93
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
94
|
+
def select_from_last_begin
|
95
|
+
return self if empty?
|
96
|
+
|
97
|
+
index_from_last = @events.reverse.find_index { |e| e.record.status == 'begin' }
|
98
|
+
return GestureBuffer.new([]) if index_from_last.nil?
|
99
|
+
|
100
|
+
index_last_begin = events.length - index_from_last - 1
|
101
|
+
GestureBuffer.new(@events[index_last_begin..-1])
|
93
102
|
end
|
94
103
|
end
|
95
104
|
end
|