fusuma 2.0.0.pre → 2.0.3

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +50 -11
  3. data/fusuma.gemspec +5 -6
  4. data/lib/fusuma.rb +87 -33
  5. data/lib/fusuma/config.rb +33 -40
  6. data/lib/fusuma/config/index.rb +34 -8
  7. data/lib/fusuma/config/searcher.rb +80 -4
  8. data/lib/fusuma/custom_process.rb +13 -0
  9. data/lib/fusuma/device.rb +19 -6
  10. data/lib/fusuma/environment.rb +4 -3
  11. data/lib/fusuma/hash_support.rb +40 -0
  12. data/lib/fusuma/libinput_command.rb +10 -15
  13. data/lib/fusuma/multi_logger.rb +2 -6
  14. data/lib/fusuma/plugin/base.rb +18 -15
  15. data/lib/fusuma/plugin/buffers/buffer.rb +3 -2
  16. data/lib/fusuma/plugin/buffers/gesture_buffer.rb +34 -25
  17. data/lib/fusuma/plugin/buffers/timer_buffer.rb +3 -3
  18. data/lib/fusuma/plugin/detectors/detector.rb +26 -5
  19. data/lib/fusuma/plugin/detectors/pinch_detector.rb +109 -58
  20. data/lib/fusuma/plugin/detectors/rotate_detector.rb +91 -50
  21. data/lib/fusuma/plugin/detectors/swipe_detector.rb +93 -56
  22. data/lib/fusuma/plugin/events/event.rb +5 -4
  23. data/lib/fusuma/plugin/events/records/context_record.rb +27 -0
  24. data/lib/fusuma/plugin/events/records/gesture_record.rb +9 -6
  25. data/lib/fusuma/plugin/events/records/index_record.rb +46 -14
  26. data/lib/fusuma/plugin/events/records/record.rb +1 -1
  27. data/lib/fusuma/plugin/events/records/text_record.rb +2 -1
  28. data/lib/fusuma/plugin/executors/command_executor.rb +21 -4
  29. data/lib/fusuma/plugin/executors/executor.rb +45 -3
  30. data/lib/fusuma/plugin/filters/filter.rb +1 -1
  31. data/lib/fusuma/plugin/filters/libinput_device_filter.rb +6 -7
  32. data/lib/fusuma/plugin/filters/libinput_timeout_filter.rb +2 -2
  33. data/lib/fusuma/plugin/inputs/input.rb +20 -7
  34. data/lib/fusuma/plugin/inputs/libinput_command_input.rb +17 -5
  35. data/lib/fusuma/plugin/inputs/timer_input.rb +7 -7
  36. data/lib/fusuma/plugin/manager.rb +22 -29
  37. data/lib/fusuma/plugin/parsers/libinput_gesture_parser.rb +10 -8
  38. data/lib/fusuma/plugin/parsers/parser.rb +8 -9
  39. data/lib/fusuma/string_support.rb +16 -0
  40. data/lib/fusuma/version.rb +1 -1
  41. data/spec/helpers/config_helper.rb +20 -0
  42. data/spec/lib/config/searcher_spec.rb +97 -0
  43. data/spec/lib/config_spec.rb +112 -0
  44. data/spec/lib/custom_process_spec.rb +28 -0
  45. data/spec/lib/device_spec.rb +96 -0
  46. data/spec/lib/dummy_config.yml +31 -0
  47. data/spec/lib/fusuma_spec.rb +103 -0
  48. data/spec/lib/libinput-list-devices_iberianpig-XPS-9360.txt +181 -0
  49. data/spec/lib/libinput-list-devices_magic_trackpad.txt +51 -0
  50. data/spec/lib/libinput-list-devices_razer_razer_blade.txt +252 -0
  51. data/spec/lib/libinput-list-devices_thejinx0r.txt +361 -0
  52. data/spec/lib/libinput-list-devices_unavailable.txt +36 -0
  53. data/spec/lib/libinput_command_spec.rb +160 -0
  54. data/spec/lib/plugin/base_spec.rb +74 -0
  55. data/spec/lib/plugin/buffers/buffer_spec.rb +80 -0
  56. data/spec/lib/plugin/buffers/dummy_buffer.rb +20 -0
  57. data/spec/lib/plugin/buffers/gesture_buffer_spec.rb +172 -0
  58. data/spec/lib/plugin/detectors/detector_spec.rb +43 -0
  59. data/spec/lib/plugin/detectors/dummy_detector.rb +24 -0
  60. data/spec/lib/plugin/detectors/pinch_detector_spec.rb +119 -0
  61. data/spec/lib/plugin/detectors/rotate_detector_spec.rb +125 -0
  62. data/spec/lib/plugin/detectors/swipe_detector_spec.rb +118 -0
  63. data/spec/lib/plugin/events/event_spec.rb +30 -0
  64. data/spec/lib/plugin/events/records/gesture_record_spec.rb +22 -0
  65. data/spec/lib/plugin/events/records/record_spec.rb +31 -0
  66. data/spec/lib/plugin/events/records/text_record_spec.rb +26 -0
  67. data/spec/lib/plugin/executors/command_executor_spec.rb +57 -0
  68. data/spec/lib/plugin/executors/executor_spec.rb +160 -0
  69. data/spec/lib/plugin/filters/filter_spec.rb +92 -0
  70. data/spec/lib/plugin/filters/libinput_filter_spec.rb +120 -0
  71. data/spec/lib/plugin/inputs/input_spec.rb +70 -0
  72. data/spec/lib/plugin/inputs/libinput_command_input_spec.rb +121 -0
  73. data/spec/lib/plugin/inputs/timer_input_spec.rb +40 -0
  74. data/spec/lib/plugin/manager_spec.rb +27 -0
  75. data/spec/lib/plugin/parsers/parser_spec.rb +45 -0
  76. data/spec/spec_helper.rb +20 -0
  77. metadata +84 -38
  78. data/.github/FUNDING.yml +0 -8
  79. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
  80. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
  81. data/.github/pull_request_template.md +0 -9
  82. data/.github/stale.yml +0 -18
  83. data/.gitignore +0 -17
  84. data/.reek.yml +0 -96
  85. data/.rspec +0 -2
  86. data/.rubocop.yml +0 -40
  87. data/.rubocop_todo.yml +0 -40
  88. data/.travis.yml +0 -11
  89. data/CHANGELOG.md +0 -456
  90. data/CODE_OF_CONDUCT.md +0 -74
  91. data/CONTRIBUTING.md +0 -72
  92. data/Gemfile +0 -18
  93. data/Rakefile +0 -15
@@ -6,7 +6,7 @@ module Fusuma
6
6
  # Search config.yml
7
7
  class Searcher
8
8
  def initialize
9
- @cache
9
+ @cache = nil
10
10
  end
11
11
 
12
12
  # @param index [Index]
@@ -31,9 +31,25 @@ module Fusuma
31
31
  value
32
32
  end
33
33
 
34
+ def search_with_context(index, location:, context:)
35
+ return nil if location.nil?
36
+
37
+ return search(index, location: location[0]) if context == {}
38
+
39
+ new_location = location.find do |conf|
40
+ search(index, location: conf) if conf[:context] == context
41
+ end
42
+ search(index, location: new_location)
43
+ end
44
+
45
+ # @param index [Index]
46
+ # @param location [Hash]
47
+ # @return [NilClass]
48
+ # @return [Hash]
49
+ # @return [Object]
34
50
  def search_with_cache(index, location:)
35
- cache([index.cache_key, Searcher.skip?, Searcher.fallback?]) do
36
- search(index, location: location)
51
+ cache([index.cache_key, Searcher.context, Searcher.skip?, Searcher.fallback?]) do
52
+ search_with_context(index, location: location, context: Searcher.context)
37
53
  end
38
54
  end
39
55
 
@@ -56,12 +72,72 @@ module Fusuma
56
72
  def next_location_cadidates(location, key)
57
73
  [
58
74
  location[key.symbol],
59
- Searcher.fallback? && key.fallback && location[key.fallback],
60
75
  Searcher.skip? && key.skippable && location
61
76
  ].compact
62
77
  end
63
78
 
64
79
  class << self
80
+ # @return [Hash]
81
+ def conditions(&block)
82
+ {
83
+ nothing: -> { block.call },
84
+ skip: -> { Config::Searcher.skip { block.call } }
85
+ }
86
+ end
87
+
88
+ # Execute block with specified conditions
89
+ # @param conidtion [Symbol]
90
+ # @return [Object]
91
+ def with_condition(condition, &block)
92
+ conditions(&block)[condition].call
93
+ end
94
+
95
+ # Execute block with all conditions
96
+ # @return [Array<Symbol, Object>]
97
+ def find_condition(&block)
98
+ conditions(&block).find do |c, l|
99
+ result = l.call
100
+ return [c, result] if result
101
+
102
+ nil
103
+ end
104
+ end
105
+
106
+ # Search with context from load_streamed Config
107
+ # @param context [Hash]
108
+ # @return [Object]
109
+ def with_context(context, &block)
110
+ @context = context || {}
111
+ result = block.call
112
+ @context = {}
113
+ result
114
+ end
115
+
116
+ # Return a matching context from config
117
+ # @params request_context [Hash]
118
+ # @return [Hash]
119
+ def find_context(request_context, &block)
120
+ # Search in blocks in the following order.
121
+ # 1. complete match config[:context] == request_context
122
+ # 2. partial match config[:context] =~ request_context
123
+ # 3. no context
124
+ Config.instance.keymap.each do |config|
125
+ next unless config[:context] == request_context
126
+ return config[:context] if with_context(config[:context]) { block.call }
127
+ end
128
+ if request_context.keys.size > 1
129
+ Config.instance.keymap.each do |config|
130
+ next if config[:context].nil?
131
+
132
+ next unless config[:context].all? { |k, v| request_context[k] == v }
133
+ return config[:context] if with_context(config[:context]) { block.call }
134
+ end
135
+ end
136
+ return {} if with_context({}) { block.call }
137
+ end
138
+
139
+ attr_reader :context
140
+
65
141
  def fallback?
66
142
  @fallback
67
143
  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]
@@ -104,6 +109,8 @@ module Fusuma
104
109
  { id: id }
105
110
  elsif (name = name_from(line))
106
111
  { name: name }
112
+ elsif (capabilities = capabilities_from(line))
113
+ { capabilities: capabilities }
107
114
  elsif (available = available_from(line))
108
115
  { available: available }
109
116
  else
@@ -123,6 +130,12 @@ module Fusuma
123
130
  end
124
131
  end
125
132
 
133
+ def capabilities_from(line)
134
+ line.match('^Capabilities:[[:space:]]*') do |m|
135
+ m.post_match.strip
136
+ end
137
+ end
138
+
126
139
  def available_from(line)
127
140
  # NOTE: is natural scroll available?
128
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
@@ -20,6 +20,7 @@ module Fusuma
20
20
  libinput_command = Plugin::Inputs::LibinputCommandInput.new.command
21
21
  MultiLogger.info "Fusuma: #{VERSION}"
22
22
  MultiLogger.info "libinput: #{libinput_command.version}"
23
+ MultiLogger.info "ruby #{ RUBY_VERSION }p#{ RUBY_PATCHLEVEL }"
23
24
  MultiLogger.info "OS: #{`uname -rsv`}".strip
24
25
  MultiLogger.info "Distribution: #{`cat /etc/issue`}".strip
25
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,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'posix/spawn'
3
+ require 'open3'
4
4
 
5
5
  module Fusuma
6
6
  # Execute libinput command
@@ -30,24 +30,19 @@ module Fusuma
30
30
  end
31
31
 
32
32
  # @yieldparam [String] gives a line in libinput list-devices output to the block
33
- def list_devices
33
+ def list_devices(&block)
34
34
  cmd = list_devices_command
35
35
  MultiLogger.debug(list_devices: cmd)
36
- p, i, o, e = POSIX::Spawn.popen4(cmd)
36
+ i, o, e, _w = Open3.popen3(cmd)
37
+ MultiLogger.error(e.read) if o.eof?
37
38
  i.close
38
- o.each { |line| yield(line) }
39
- ensure
40
- [i, o, e].each { |io| io.close unless io.closed? }
41
- Process.waitpid(p)
39
+ e.close
40
+ o.each(&block)
42
41
  end
43
42
 
44
- # @return [String] return a latest line libinput debug-events
45
- def debug_events
46
- @debug_events ||= begin
47
- _p, i, o, _e = POSIX::Spawn.popen4(debug_events_with_options)
48
- i.close
49
- o
50
- end
43
+ # @return [Integer] return a latest line libinput debug-events
44
+ def debug_events(writer)
45
+ @debug_events ||= Process.spawn(debug_events_with_options, out: writer, in: '/dev/null')
51
46
  end
52
47
 
53
48
  # @return [String] command
@@ -60,7 +55,7 @@ module Fusuma
60
55
  elsif which('libinput-list-devices')
61
56
  'libinput-list-devices --version'
62
57
  else
63
- MultiLogger.error 'install libinput-tools'
58
+ MultiLogger.error 'Please install libinput-tools'
64
59
  exit 1
65
60
  end
66
61
  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