fusuma 0.11.1 → 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/.github/FUNDING.yml +8 -0
- data/.gitignore +6 -0
- data/.reek.yml +93 -45
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +16 -75
- data/Gemfile +2 -0
- data/README.md +12 -5
- data/Rakefile +2 -1
- data/bin/console +1 -1
- data/exe/fusuma +1 -0
- data/fusuma.gemspec +9 -2
- data/lib/fusuma.rb +88 -31
- data/lib/fusuma/config.rb +65 -66
- data/lib/fusuma/config/index.rb +49 -0
- data/lib/fusuma/device.rb +58 -37
- data/lib/fusuma/multi_logger.rb +3 -0
- data/lib/fusuma/plugin/base.rb +56 -0
- data/lib/fusuma/plugin/buffers/buffer.rb +41 -0
- data/lib/fusuma/plugin/buffers/gesture_buffer.rb +70 -0
- data/lib/fusuma/plugin/detectors/detector.rb +41 -0
- data/lib/fusuma/plugin/detectors/pinch_detector.rb +141 -0
- data/lib/fusuma/plugin/detectors/rotate_detector.rb +135 -0
- data/lib/fusuma/plugin/detectors/swipe_detector.rb +145 -0
- data/lib/fusuma/plugin/events/event.rb +38 -0
- data/lib/fusuma/plugin/events/records/gesture_record.rb +31 -0
- data/lib/fusuma/plugin/events/records/index_record.rb +53 -0
- data/lib/fusuma/plugin/events/records/record.rb +20 -0
- data/lib/fusuma/plugin/events/records/text_record.rb +28 -0
- data/lib/fusuma/plugin/executors/command_executor.rb +39 -0
- data/lib/fusuma/plugin/executors/executor.rb +27 -0
- data/lib/fusuma/plugin/filters/filter.rb +40 -0
- data/lib/fusuma/plugin/filters/libinput_device_filter.rb +42 -0
- data/lib/fusuma/plugin/inputs/input.rb +28 -0
- data/lib/fusuma/plugin/inputs/libinput_command_input.rb +133 -0
- data/lib/fusuma/plugin/manager.rb +118 -0
- data/lib/fusuma/plugin/parsers/libinput_gesture_parser.rb +54 -0
- data/lib/fusuma/plugin/parsers/parser.rb +46 -0
- data/lib/fusuma/version.rb +3 -1
- metadata +74 -14
- data/lib/fusuma/command_executor.rb +0 -43
- data/lib/fusuma/event_stack.rb +0 -87
- data/lib/fusuma/gesture_event.rb +0 -50
- data/lib/fusuma/libinput_commands.rb +0 -98
- data/lib/fusuma/pinch.rb +0 -58
- data/lib/fusuma/swipe.rb +0 -59
data/lib/fusuma/config.rb
CHANGED
@@ -1,88 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './multi_logger.rb'
|
4
|
+
require_relative './config/index.rb'
|
5
|
+
require 'singleton'
|
6
|
+
require 'yaml'
|
7
|
+
|
1
8
|
# module as namespace
|
2
9
|
module Fusuma
|
3
|
-
require 'singleton'
|
4
10
|
# read keymap from yaml file
|
5
11
|
class Config
|
6
12
|
include Singleton
|
7
13
|
|
8
14
|
class << self
|
9
|
-
def
|
10
|
-
instance.
|
15
|
+
def search(keys)
|
16
|
+
instance.search(keys)
|
11
17
|
end
|
12
18
|
|
13
|
-
def
|
14
|
-
instance.
|
15
|
-
end
|
16
|
-
|
17
|
-
def threshold(command_executor)
|
18
|
-
instance.threshold(command_executor)
|
19
|
-
end
|
20
|
-
|
21
|
-
def interval(command_executor)
|
22
|
-
instance.interval(command_executor)
|
23
|
-
end
|
24
|
-
|
25
|
-
def reload
|
26
|
-
instance.reload
|
19
|
+
def custom_path=(new_path)
|
20
|
+
instance.custom_path = new_path
|
27
21
|
end
|
28
22
|
end
|
29
23
|
|
30
24
|
attr_reader :keymap
|
31
|
-
|
25
|
+
attr_reader :custom_path
|
32
26
|
|
33
27
|
def initialize
|
34
|
-
|
28
|
+
@custom_path = nil
|
29
|
+
@cache = nil
|
30
|
+
@keymap = nil
|
31
|
+
reload
|
32
|
+
end
|
33
|
+
|
34
|
+
def custom_path=(new_path)
|
35
|
+
@custom_path = new_path
|
35
36
|
reload
|
36
37
|
end
|
37
38
|
|
38
39
|
def reload
|
39
40
|
@cache = nil
|
40
|
-
@keymap = YAML.load_file(file_path)
|
41
|
+
@keymap = YAML.load_file(file_path).deep_symbolize_keys
|
42
|
+
MultiLogger.info "reload config : #{file_path}"
|
41
43
|
self
|
42
44
|
end
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
def interval(command_executor)
|
62
|
-
seek_index_specific = [*event_index(command_executor), 'interval']
|
63
|
-
seek_index_global = ['interval', command_executor.event_type]
|
64
|
-
search_config_cached(seek_index_specific) ||
|
65
|
-
search_config_cached(seek_index_global) || 1
|
46
|
+
# @param index [Index]
|
47
|
+
def search(index)
|
48
|
+
cache(index.cache_key) do
|
49
|
+
index.keys.reduce(keymap) do |location, key|
|
50
|
+
if location.is_a?(Hash)
|
51
|
+
begin
|
52
|
+
if key.skippable
|
53
|
+
location.fetch(key.symbol, location)
|
54
|
+
else
|
55
|
+
location.fetch(key.symbol, nil)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
else
|
59
|
+
location
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
66
63
|
end
|
67
64
|
|
68
65
|
private
|
69
66
|
|
70
|
-
def search_config_cached(seek_index)
|
71
|
-
cache(seek_index) { search_config(keymap, seek_index) }
|
72
|
-
end
|
73
|
-
|
74
|
-
def search_config(keymap_node, seek_index)
|
75
|
-
if seek_index == []
|
76
|
-
return nil if keymap_node.is_a? Hash
|
77
|
-
return keymap_node
|
78
|
-
end
|
79
|
-
key = seek_index[0]
|
80
|
-
child_node = keymap_node[key]
|
81
|
-
next_index = seek_index[1..-1]
|
82
|
-
return search_config(child_node, next_index) if child_node
|
83
|
-
search_config(keymap_node, next_index)
|
84
|
-
end
|
85
|
-
|
86
67
|
def file_path
|
87
68
|
filename = 'fusuma/config.yml'
|
88
69
|
if custom_path && File.exist?(expand_custom_path)
|
@@ -106,17 +87,35 @@ module Fusuma
|
|
106
87
|
File.expand_path "../../#{filename}", __FILE__
|
107
88
|
end
|
108
89
|
|
109
|
-
def event_index(command_executor)
|
110
|
-
event_type = command_executor.event_type
|
111
|
-
finger = command_executor.finger
|
112
|
-
direction = command_executor.direction
|
113
|
-
[event_type, finger, direction]
|
114
|
-
end
|
115
|
-
|
116
90
|
def cache(key)
|
117
91
|
@cache ||= {}
|
118
92
|
key = key.join(',') if key.is_a? Array
|
119
|
-
@cache
|
93
|
+
if @cache.key?(key)
|
94
|
+
@cache[key]
|
95
|
+
else
|
96
|
+
@cache[key] = block_given? ? yield : nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# activesupport-4.1.1/lib/active_support/core_ext/hash/keys.rb
|
103
|
+
class Hash
|
104
|
+
def deep_symbolize_keys
|
105
|
+
deep_transform_keys do |key|
|
106
|
+
begin
|
107
|
+
key.to_sym
|
108
|
+
rescue StandardError
|
109
|
+
key
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def deep_transform_keys(&block)
|
115
|
+
result = {}
|
116
|
+
each do |key, value|
|
117
|
+
result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
|
120
118
|
end
|
119
|
+
result
|
121
120
|
end
|
122
121
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Index for searching value from config.yml
|
4
|
+
module Fusuma
|
5
|
+
class Config
|
6
|
+
# index for config.yml
|
7
|
+
class Index
|
8
|
+
def initialize(keys)
|
9
|
+
@keys = case keys
|
10
|
+
when Array
|
11
|
+
keys.map do |key|
|
12
|
+
if key.is_a? Key
|
13
|
+
key
|
14
|
+
else
|
15
|
+
Key.new(key)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
else
|
19
|
+
[Key.new(keys)]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
attr_reader :keys
|
23
|
+
|
24
|
+
def cache_key
|
25
|
+
case @keys
|
26
|
+
when Array
|
27
|
+
@keys.map(&:symbol).join(',')
|
28
|
+
when Key
|
29
|
+
@keys.symbol
|
30
|
+
else
|
31
|
+
raise 'invalid keys'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Keys in Index
|
36
|
+
class Key
|
37
|
+
def initialize(symbol_word, skippable: false)
|
38
|
+
@symbol = begin
|
39
|
+
symbol_word.to_sym
|
40
|
+
rescue StandardError
|
41
|
+
symbol_word
|
42
|
+
end
|
43
|
+
@skippable = skippable
|
44
|
+
end
|
45
|
+
attr_reader :symbol, :skippable
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/fusuma/device.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fusuma
|
2
4
|
# detect input device
|
3
5
|
class Device
|
4
|
-
|
5
|
-
|
6
|
-
|
6
|
+
attr_reader :available
|
7
|
+
attr_reader :name
|
8
|
+
attr_reader :id
|
7
9
|
|
8
10
|
def initialize(id: nil, name: nil, available: nil)
|
9
11
|
@id = id
|
@@ -11,68 +13,81 @@ module Fusuma
|
|
11
13
|
@available = available
|
12
14
|
end
|
13
15
|
|
14
|
-
# @param [Hash]
|
16
|
+
# @param attributes [Hash]
|
15
17
|
def assign_attributes(attributes)
|
16
18
|
attributes.each do |k, v|
|
17
19
|
case k
|
18
20
|
when :id
|
19
|
-
|
21
|
+
@id = v
|
20
22
|
when :name
|
21
|
-
|
23
|
+
@name = v
|
22
24
|
when :available
|
23
|
-
|
25
|
+
@available = v
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
28
30
|
class << self
|
29
|
-
|
30
|
-
def ids
|
31
|
-
available.map(&:id)
|
32
|
-
end
|
31
|
+
attr_reader :given_devices
|
33
32
|
|
34
33
|
# @return [Array]
|
35
|
-
def
|
36
|
-
|
34
|
+
def all
|
35
|
+
@all ||= fetch_devices
|
37
36
|
end
|
38
37
|
|
39
38
|
# @raise [SystemExit]
|
40
39
|
# @return [Array]
|
41
40
|
def available
|
42
|
-
@available ||=
|
41
|
+
@available ||= all.select(&:available).tap do |d|
|
43
42
|
MultiLogger.debug(available_devices: d)
|
44
43
|
raise 'Touchpad is not found' if d.empty?
|
45
44
|
end
|
46
|
-
rescue RuntimeError =>
|
47
|
-
|
45
|
+
rescue RuntimeError => e
|
46
|
+
# FIXME: should not exit without Runner class
|
47
|
+
MultiLogger.error(e.message)
|
48
48
|
exit 1
|
49
49
|
end
|
50
50
|
|
51
51
|
def reset
|
52
|
+
@all = nil
|
52
53
|
@available = nil
|
53
54
|
end
|
54
55
|
|
55
|
-
#
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
56
|
+
# Narrow down available device list
|
57
|
+
# @param names [String, Array]
|
58
|
+
def given_devices=(names)
|
59
|
+
# NOTE: convert to Array
|
60
|
+
device_names = Array(names)
|
61
|
+
return if device_names.empty?
|
62
|
+
|
63
|
+
@given_devices = narrow_available_devices(device_names: device_names)
|
64
|
+
return unless @given_devices.empty?
|
65
|
+
|
62
66
|
exit 1
|
63
67
|
end
|
64
68
|
|
65
69
|
private
|
66
70
|
|
67
71
|
# @return [Array]
|
68
|
-
def
|
72
|
+
def fetch_devices
|
69
73
|
line_parser = LineParser.new
|
70
|
-
|
74
|
+
Plugin::Inputs::LibinputCommandInput.new.list_devices do |line|
|
71
75
|
line_parser.push(line)
|
72
76
|
end
|
73
77
|
line_parser.generate_devices
|
74
78
|
end
|
75
79
|
|
80
|
+
def narrow_available_devices(device_names:)
|
81
|
+
device_names.select do |name|
|
82
|
+
if available.map(&:name).include? name
|
83
|
+
MultiLogger.info("Touchpad is found: #{name}")
|
84
|
+
true
|
85
|
+
else
|
86
|
+
MultiLogger.warn("Touchpad is not found: #{name}")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
76
91
|
# parse line and generate devices
|
77
92
|
class LineParser
|
78
93
|
attr_reader :lines
|
@@ -88,25 +103,28 @@ module Fusuma
|
|
88
103
|
|
89
104
|
# @return [Array]
|
90
105
|
def generate_devices
|
91
|
-
device =
|
106
|
+
device = Device.new
|
92
107
|
lines.each_with_object([]) do |line, devices|
|
93
|
-
|
94
|
-
|
95
|
-
|
108
|
+
attributes = extract_attribute(line: line)
|
109
|
+
|
110
|
+
# detect new line including device name
|
111
|
+
if attributes[:name] && (device&.name != attributes[:name])
|
96
112
|
devices << device
|
97
|
-
device =
|
113
|
+
device = Device.new
|
98
114
|
end
|
115
|
+
|
116
|
+
devices.last.assign_attributes(attributes)
|
99
117
|
end
|
100
118
|
end
|
101
119
|
|
102
|
-
# @param
|
120
|
+
# @param line [String]
|
103
121
|
# @return [Hash]
|
104
122
|
def extract_attribute(line:)
|
105
123
|
if (id = id_from(line))
|
106
124
|
{ id: id }
|
107
125
|
elsif (name = name_from(line))
|
108
126
|
{ name: name }
|
109
|
-
elsif (available =
|
127
|
+
elsif (available = available_from(line))
|
110
128
|
{ available: available }
|
111
129
|
else
|
112
130
|
{}
|
@@ -125,11 +143,14 @@ module Fusuma
|
|
125
143
|
end
|
126
144
|
end
|
127
145
|
|
128
|
-
def
|
129
|
-
# NOTE: natural scroll
|
130
|
-
|
131
|
-
|
132
|
-
|
146
|
+
def available_from(line)
|
147
|
+
# NOTE: is natural scroll available?
|
148
|
+
if line =~ /^Nat.scrolling: /
|
149
|
+
return false if line =~ %r{n/a}
|
150
|
+
|
151
|
+
return true # disabled / enabled
|
152
|
+
end
|
153
|
+
nil
|
133
154
|
end
|
134
155
|
end
|
135
156
|
end
|
data/lib/fusuma/multi_logger.rb
CHANGED
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './manager.rb'
|
4
|
+
require_relative '../config.rb'
|
5
|
+
|
6
|
+
module Fusuma
|
7
|
+
module Plugin
|
8
|
+
# Create a Plugin Class with extending this class
|
9
|
+
class Base
|
10
|
+
# when inherited from subclass
|
11
|
+
def self.inherited(subclass)
|
12
|
+
subclass_path = caller_locations(1..1).first.path
|
13
|
+
Manager.add(plugin_class: subclass, plugin_path: subclass_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
# get inherited classes
|
17
|
+
# @example
|
18
|
+
# [Vectors::Vector]
|
19
|
+
# @return [Array]
|
20
|
+
def self.plugins
|
21
|
+
Manager.plugins[name]
|
22
|
+
end
|
23
|
+
|
24
|
+
# config parameter name and Type of the value of parameter
|
25
|
+
# @return [Hash]
|
26
|
+
def config_param_types
|
27
|
+
raise NotImplementedError, "override #{self.class.name}##{__method__}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Object]
|
31
|
+
def config_params(key = nil, base: config_index)
|
32
|
+
params = Config.search(base) || {}
|
33
|
+
|
34
|
+
return params unless key
|
35
|
+
|
36
|
+
params.fetch(key, nil).tap do |val|
|
37
|
+
next if val.nil?
|
38
|
+
|
39
|
+
# NOTE: Type checking for config.yml
|
40
|
+
param_types = Array(config_param_types.fetch(key))
|
41
|
+
|
42
|
+
next if param_types.any? { |klass| val.is_a?(klass) }
|
43
|
+
|
44
|
+
MultiLogger.error('Please fix config.yml.')
|
45
|
+
MultiLogger.error(":#{base.keys.map(&:symbol)
|
46
|
+
.join(' => :')} => :#{key} should be #{param_types.join(' OR ')}.")
|
47
|
+
exit 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def config_index
|
52
|
+
Config::Index.new(self.class.name.gsub('Fusuma::', '').underscore.split('/'))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|