fusuma 0.11.1 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
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