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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +8 -0
  3. data/.gitignore +6 -0
  4. data/.reek.yml +93 -45
  5. data/.rubocop.yml +2 -0
  6. data/.rubocop_todo.yml +16 -75
  7. data/Gemfile +2 -0
  8. data/README.md +12 -5
  9. data/Rakefile +2 -1
  10. data/bin/console +1 -1
  11. data/exe/fusuma +1 -0
  12. data/fusuma.gemspec +9 -2
  13. data/lib/fusuma.rb +88 -31
  14. data/lib/fusuma/config.rb +65 -66
  15. data/lib/fusuma/config/index.rb +49 -0
  16. data/lib/fusuma/device.rb +58 -37
  17. data/lib/fusuma/multi_logger.rb +3 -0
  18. data/lib/fusuma/plugin/base.rb +56 -0
  19. data/lib/fusuma/plugin/buffers/buffer.rb +41 -0
  20. data/lib/fusuma/plugin/buffers/gesture_buffer.rb +70 -0
  21. data/lib/fusuma/plugin/detectors/detector.rb +41 -0
  22. data/lib/fusuma/plugin/detectors/pinch_detector.rb +141 -0
  23. data/lib/fusuma/plugin/detectors/rotate_detector.rb +135 -0
  24. data/lib/fusuma/plugin/detectors/swipe_detector.rb +145 -0
  25. data/lib/fusuma/plugin/events/event.rb +38 -0
  26. data/lib/fusuma/plugin/events/records/gesture_record.rb +31 -0
  27. data/lib/fusuma/plugin/events/records/index_record.rb +53 -0
  28. data/lib/fusuma/plugin/events/records/record.rb +20 -0
  29. data/lib/fusuma/plugin/events/records/text_record.rb +28 -0
  30. data/lib/fusuma/plugin/executors/command_executor.rb +39 -0
  31. data/lib/fusuma/plugin/executors/executor.rb +27 -0
  32. data/lib/fusuma/plugin/filters/filter.rb +40 -0
  33. data/lib/fusuma/plugin/filters/libinput_device_filter.rb +42 -0
  34. data/lib/fusuma/plugin/inputs/input.rb +28 -0
  35. data/lib/fusuma/plugin/inputs/libinput_command_input.rb +133 -0
  36. data/lib/fusuma/plugin/manager.rb +118 -0
  37. data/lib/fusuma/plugin/parsers/libinput_gesture_parser.rb +54 -0
  38. data/lib/fusuma/plugin/parsers/parser.rb +46 -0
  39. data/lib/fusuma/version.rb +3 -1
  40. metadata +74 -14
  41. data/lib/fusuma/command_executor.rb +0 -43
  42. data/lib/fusuma/event_stack.rb +0 -87
  43. data/lib/fusuma/gesture_event.rb +0 -50
  44. data/lib/fusuma/libinput_commands.rb +0 -98
  45. data/lib/fusuma/pinch.rb +0 -58
  46. data/lib/fusuma/swipe.rb +0 -59
@@ -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 command(command_executor)
10
- instance.command(command_executor)
15
+ def search(keys)
16
+ instance.search(keys)
11
17
  end
12
18
 
13
- def shortcut(command_executor)
14
- instance.shortcut(command_executor)
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
- attr_accessor :custom_path
25
+ attr_reader :custom_path
32
26
 
33
27
  def initialize
34
- self.custom_path = nil
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
- def command(command_executor)
45
- seek_index = [*event_index(command_executor), 'command']
46
- search_config_cached(seek_index)
47
- end
48
-
49
- def shortcut(command_executor)
50
- seek_index = [*event_index(command_executor), 'shortcut']
51
- search_config_cached(seek_index)
52
- end
53
-
54
- def threshold(command_executor)
55
- seek_index_specific = [*event_index(command_executor), 'threshold']
56
- seek_index_global = ['threshold', command_executor.event_type]
57
- search_config_cached(seek_index_specific) ||
58
- search_config_cached(seek_index_global) || 1
59
- end
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[key] ||= block_given? ? yield : nil
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
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Fusuma
2
4
  # detect input device
3
5
  class Device
4
- attr_accessor :id
5
- attr_accessor :name
6
- attr_accessor :available
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
- self.id = v
21
+ @id = v
20
22
  when :name
21
- self.name = v
23
+ @name = v
22
24
  when :available
23
- self.available = v
25
+ @available = v
24
26
  end
25
27
  end
26
28
  end
27
29
 
28
30
  class << self
29
- # @return [Array]
30
- def ids
31
- available.map(&:id)
32
- end
31
+ attr_reader :given_devices
33
32
 
34
33
  # @return [Array]
35
- def names
36
- available.map(&:name)
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 ||= fetch_available.tap do |d|
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 => ex
47
- MultiLogger.error(ex.message)
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
- # @param [String]
56
- def given_device=(name)
57
- return if name.nil?
58
- @available = available.select { |d| d.name == name }
59
- return unless names.empty?
60
- MultiLogger.error("Device #{name} is not found.\n
61
- Check available device with: $ fusuma --list-devices\n")
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 fetch_available
72
+ def fetch_devices
69
73
  line_parser = LineParser.new
70
- LibinputCommands.new.list_devices do |line|
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 = nil
106
+ device = Device.new
92
107
  lines.each_with_object([]) do |line, devices|
93
- device ||= Device.new
94
- device.assign_attributes extract_attribute(line: line)
95
- if device.available
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 = nil
113
+ device = Device.new
98
114
  end
115
+
116
+ devices.last.assign_attributes(attributes)
99
117
  end
100
118
  end
101
119
 
102
- # @param [String]
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 = available?(line))
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 available?(line)
129
- # NOTE: natural scroll is available?
130
- return false unless line =~ /^Nat.scrolling: /
131
- return false if line =~ %r{n/a}
132
- true
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # module as namespace
2
4
  module Fusuma
3
5
  require 'logger'
@@ -21,6 +23,7 @@ module Fusuma
21
23
 
22
24
  def debug(msg)
23
25
  return unless debug_mode?
26
+
24
27
  super(msg)
25
28
  end
26
29
 
@@ -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