fusuma 1.11.1 → 2.0.2

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 +49 -7
  3. data/fusuma.gemspec +6 -16
  4. data/lib/fusuma.rb +91 -28
  5. data/lib/fusuma/config.rb +34 -62
  6. data/lib/fusuma/config/index.rb +39 -6
  7. data/lib/fusuma/config/searcher.rb +166 -0
  8. data/lib/fusuma/custom_process.rb +13 -0
  9. data/lib/fusuma/device.rb +22 -7
  10. data/lib/fusuma/environment.rb +6 -4
  11. data/lib/fusuma/hash_support.rb +40 -0
  12. data/lib/fusuma/libinput_command.rb +17 -21
  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 +46 -0
  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 -6
  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 +64 -8
  34. data/lib/fusuma/plugin/inputs/libinput_command_input.rb +19 -9
  35. data/lib/fusuma/plugin/inputs/timer_input.rb +63 -0
  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 +98 -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 +167 -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 +120 -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 +90 -167
  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 -37
  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 -6
  93. data/Rakefile +0 -15
@@ -19,6 +19,11 @@ module Fusuma
19
19
  [Key.new(keys)]
20
20
  end
21
21
  end
22
+
23
+ def inspect
24
+ @keys.map(&:inspect)
25
+ end
26
+
22
27
  attr_reader :keys
23
28
 
24
29
  def cache_key
@@ -32,17 +37,45 @@ module Fusuma
32
37
  end
33
38
  end
34
39
 
40
+ # @return [Index]
41
+ def with_context
42
+ keys = @keys.map do |key|
43
+ next if Searcher.skip? && key.skippable
44
+
45
+ if Searcher.fallback? && key.fallback
46
+ key.fallback
47
+ else
48
+ key
49
+ end
50
+ end
51
+ self.class.new(keys.compact)
52
+ end
53
+
35
54
  # Keys in Index
36
55
  class Key
37
- def initialize(symbol_word, skippable: false)
56
+ def initialize(symbol_word, skippable: false, fallback: nil)
38
57
  @symbol = begin
39
- symbol_word.to_sym
40
- rescue StandardError
41
- symbol_word
42
- end
58
+ symbol_word.to_sym
59
+ rescue StandardError
60
+ symbol_word
61
+ end
62
+
43
63
  @skippable = skippable
64
+
65
+ @fallback = begin
66
+ fallback.to_sym
67
+ rescue StandardError
68
+ fallback
69
+ end
44
70
  end
45
- attr_reader :symbol, :skippable
71
+
72
+ def inspect
73
+ skip_marker = @skippable && Searcher.skip? ? '(skip)' : ''
74
+ fallback_marker = @fallback && Searcher.fallback? ? '(fallback)' : ''
75
+ "#{@symbol}#{skip_marker}#{fallback_marker}"
76
+ end
77
+
78
+ attr_reader :symbol, :skippable, :fallback
46
79
  end
47
80
  end
48
81
  end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Index for searching value from config.yml
4
+ module Fusuma
5
+ class Config
6
+ # Search config.yml
7
+ class Searcher
8
+ def initialize
9
+ @cache = nil
10
+ end
11
+
12
+ # @param index [Index]
13
+ # @param location [Hash]
14
+ # @return [NilClass]
15
+ # @return [Hash]
16
+ # @return [Object]
17
+ def search(index, location:)
18
+ key = index.keys.first
19
+ return location if key.nil?
20
+
21
+ return nil if location.nil?
22
+
23
+ return nil unless location.is_a?(Hash)
24
+
25
+ next_index = Index.new(index.keys[1..-1])
26
+
27
+ value = nil
28
+ next_location_cadidates(location, key).find do |next_location|
29
+ value = search(next_index, location: next_location)
30
+ end
31
+ value
32
+ end
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]
50
+ def search_with_cache(index, location:)
51
+ cache([index.cache_key, Searcher.context, Searcher.skip?, Searcher.fallback?]) do
52
+ search_with_context(index, location: location, context: Searcher.context)
53
+ end
54
+ end
55
+
56
+ def cache(key)
57
+ @cache ||= {}
58
+ key = key.join(',') if key.is_a? Array
59
+ if @cache.key?(key)
60
+ @cache[key]
61
+ else
62
+ @cache[key] = block_given? ? yield : nil
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ # next locations' candidates sorted by priority
69
+ # 1. look up location with key
70
+ # 2. fallback to other key
71
+ # 3. skip the key and go to child location
72
+ def next_location_cadidates(location, key)
73
+ [
74
+ location[key.symbol],
75
+ Searcher.skip? && key.skippable && location
76
+ ].compact
77
+ end
78
+
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
+
141
+ def fallback?
142
+ @fallback
143
+ end
144
+
145
+ def skip?
146
+ @skip
147
+ end
148
+
149
+ # switch context for fallback
150
+ def fallback(&block)
151
+ @fallback = true
152
+ result = block.call
153
+ @fallback = false
154
+ result
155
+ end
156
+
157
+ def skip(&block)
158
+ @skip = true
159
+ result = block.call
160
+ @skip = false
161
+ result
162
+ end
163
+ end
164
+ end
165
+ end
166
+ 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