fusuma 1.10.1 → 2.0.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/pull_request_template.md +9 -0
  3. data/.rubocop.yml +27 -0
  4. data/.rubocop_todo.yml +34 -19
  5. data/.solargraph.yml +16 -0
  6. data/.travis.yml +1 -3
  7. data/CHANGELOG.md +38 -1
  8. data/CONTRIBUTING.md +72 -0
  9. data/Gemfile +17 -0
  10. data/README.md +53 -7
  11. data/fusuma.gemspec +4 -13
  12. data/lib/fusuma.rb +91 -29
  13. data/lib/fusuma/config.rb +59 -60
  14. data/lib/fusuma/config/index.rb +39 -6
  15. data/lib/fusuma/config/searcher.rb +166 -0
  16. data/lib/fusuma/config/yaml_duplication_checker.rb +42 -0
  17. data/lib/fusuma/custom_process.rb +13 -0
  18. data/lib/fusuma/device.rb +22 -7
  19. data/lib/fusuma/environment.rb +6 -4
  20. data/lib/fusuma/hash_support.rb +40 -0
  21. data/lib/fusuma/libinput_command.rb +17 -21
  22. data/lib/fusuma/multi_logger.rb +2 -6
  23. data/lib/fusuma/plugin/base.rb +18 -15
  24. data/lib/fusuma/plugin/buffers/buffer.rb +3 -2
  25. data/lib/fusuma/plugin/buffers/gesture_buffer.rb +34 -25
  26. data/lib/fusuma/plugin/buffers/timer_buffer.rb +46 -0
  27. data/lib/fusuma/plugin/detectors/detector.rb +26 -5
  28. data/lib/fusuma/plugin/detectors/pinch_detector.rb +109 -58
  29. data/lib/fusuma/plugin/detectors/rotate_detector.rb +91 -50
  30. data/lib/fusuma/plugin/detectors/swipe_detector.rb +93 -56
  31. data/lib/fusuma/plugin/events/event.rb +5 -4
  32. data/lib/fusuma/plugin/events/records/context_record.rb +27 -0
  33. data/lib/fusuma/plugin/events/records/gesture_record.rb +9 -6
  34. data/lib/fusuma/plugin/events/records/index_record.rb +46 -14
  35. data/lib/fusuma/plugin/events/records/record.rb +1 -1
  36. data/lib/fusuma/plugin/events/records/text_record.rb +2 -1
  37. data/lib/fusuma/plugin/executors/command_executor.rb +21 -6
  38. data/lib/fusuma/plugin/executors/executor.rb +45 -3
  39. data/lib/fusuma/plugin/filters/filter.rb +1 -1
  40. data/lib/fusuma/plugin/filters/libinput_device_filter.rb +6 -7
  41. data/lib/fusuma/plugin/filters/libinput_timeout_filter.rb +2 -2
  42. data/lib/fusuma/plugin/inputs/input.rb +64 -8
  43. data/lib/fusuma/plugin/inputs/libinput_command_input.rb +19 -9
  44. data/lib/fusuma/plugin/inputs/timer_input.rb +63 -0
  45. data/lib/fusuma/plugin/manager.rb +22 -29
  46. data/lib/fusuma/plugin/parsers/libinput_gesture_parser.rb +10 -8
  47. data/lib/fusuma/plugin/parsers/parser.rb +8 -9
  48. data/lib/fusuma/string_support.rb +16 -0
  49. data/lib/fusuma/version.rb +1 -1
  50. metadata +21 -150
@@ -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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fusuma
4
+ # Rename process
5
+ module CustomProcess
6
+ def fork
7
+ Process.fork do
8
+ Process.setproctitle(self.class.name.underscore.to_s)
9
+ yield
10
+ end
11
+ end
12
+ end
13
+ 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.rb'
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
- LibinputCommand.new.list_devices do |line|
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: /
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './version.rb'
4
- require_relative './libinput_command.rb'
5
- require_relative './multi_logger.rb'
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: #{LibinputCommand.new.version}"
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 'open3'
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
- Open3.popen3(cmd) do |_i, o, _e, _w|
38
- o.each { |line| yield(line) }
39
- end
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
- # @yieldparam [String] gives a line in libinput debug-events output to the block
44
+ # @return [Integer, IO] return a latest line libinput debug-events
43
45
  def debug_events
44
- MultiLogger.debug(debug_events: debug_events_with_options)
45
- Open3.popen3(debug_events_with_options) do |_i, o, _e, _w|
46
- loop do
47
- line = begin
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 which('libinput')
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
@@ -12,15 +12,11 @@ module Fusuma
12
12
  attr_accessor :debug_mode
13
13
 
14
14
  def initialize
15
- super(STDOUT)
16
- @err_logger = Logger.new(STDERR)
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
 
@@ -1,22 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './manager.rb'
4
- require_relative '../config.rb'
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 inherited classes
17
- # @example
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
- params.fetch(key, nil).tap do |val|
37
- next if val.nil?
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
- # NOTE: Type checking for config.yml
40
- param_types = Array(config_param_types.fetch(key))
42
+ # NOTE: Type checking for config.yml
43
+ param_types = Array(config_param_types.fetch(key))
41
44
 
42
- next if param_types.any? { |klass| val.is_a?(klass) }
45
+ next if param_types.any? { |klass| val.is_a?(klass) }
43
46
 
44
- MultiLogger.error('Please fix config.yml.')
45
- MultiLogger.error(":#{base.keys.map(&:symbol)
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
- exit 1
48
- end
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.rb'
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.rb'
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 = 0.1
11
+ DEFAULT_SECONDS_TO_KEEP = 100
12
12
 
13
13
  def config_param_types
14
14
  {
15
- 'source': [String],
16
- 'seconds_to_keep': [Float, Integer]
15
+ source: [String],
16
+ seconds_to_keep: [Float, Integer]
17
17
  }
18
18
  end
19
19
 
20
20
  # @param event [Event]
21
- # @return [Buffer, false]
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
- if bufferable?(event)
30
- @events.push(event)
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
- @events.map { |gesture_event| gesture_event.record.direction[attr].to_f }
53
- .inject(:+)
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 / @events.length
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(:select) unless block_given?
87
+ def select_by_events(&block)
88
+ return enum_for(:select_by_events) unless block_given?
81
89
 
82
- events = @events.select { |event| yield event }
90
+ events = @events.select(&block)
83
91
  self.class.new events
84
92
  end
85
93
 
86
- def bufferable?(event)
87
- case event.record.status
88
- when 'begin', 'end'
89
- false
90
- else
91
- true
92
- end
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