fusuma 1.10.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,48 +1,103 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './detector.rb'
3
+ require_relative './detector'
4
4
 
5
5
  module Fusuma
6
6
  module Plugin
7
7
  module Detectors
8
8
  class SwipeDetector < Detector
9
+ SOURCES = ['gesture'].freeze
9
10
  BUFFER_TYPE = 'gesture'
10
11
  GESTURE_RECORD_TYPE = 'swipe'
11
12
 
12
13
  FINGERS = [3, 4].freeze
13
- BASE_THERESHOLD = 10
14
- BASE_INTERVAL = 0.5
14
+ BASE_THERESHOLD = 25
15
15
 
16
16
  # @param buffers [Array<Buffers::Buffer>]
17
- # @return [Event] if event is detected
17
+ # @return [Events::Event] if event is detected
18
18
  # @return [NilClass] if event is NOT detected
19
19
  def detect(buffers)
20
- buffer = buffers.find { |b| b.type == BUFFER_TYPE }
21
- .select_by_events { |e| e.record.gesture == GESTURE_RECORD_TYPE }
22
-
23
- return if buffer.empty?
24
-
25
- move_x = buffer.avg_attrs(:move_x)
26
- move_y = buffer.avg_attrs(:move_y)
27
-
28
- finger = buffer.finger
29
- direction = Direction.new(move_x: move_x.to_f, move_y: move_y.to_f).to_s
30
- quantity = Quantity.new(move_x: move_x.to_f, move_y: move_y.to_f).to_f
31
-
32
- index = create_index(gesture: type,
33
- finger: finger,
34
- direction: direction)
35
-
36
- return unless enough?(index: index, quantity: quantity)
20
+ gesture_buffer = buffers.find { |b| b.type == BUFFER_TYPE }
21
+ .select_from_last_begin
22
+ .select_by_events { |e| e.record.gesture == GESTURE_RECORD_TYPE }
23
+
24
+ updating_events = gesture_buffer.updating_events
25
+ return if updating_events.empty?
26
+
27
+ updating_time = 100 * (updating_events.last.time - updating_events.first.time)
28
+ oneshot_move_x = gesture_buffer.sum_attrs(:move_x) / updating_time
29
+ oneshot_move_y = gesture_buffer.sum_attrs(:move_y) / updating_time
30
+
31
+ finger = gesture_buffer.finger
32
+ status = case gesture_buffer.events.last.record.status
33
+ when 'end'
34
+ 'end'
35
+ when 'update'
36
+ if updating_events.length == 1
37
+ 'begin'
38
+ else
39
+ 'update'
40
+ end
41
+ else
42
+ gesture_buffer.events.last.record.status
43
+ end
44
+
45
+ delta = if status == 'end'
46
+ gesture_buffer.events[-2].record.delta
47
+ else
48
+ gesture_buffer.events.last.record.delta
49
+ end
50
+
51
+ repeat_direction = Direction.new(move_x: delta.move_x, move_y: delta.move_y).to_s
52
+ repeat_quantity = Quantity.new(move_x: delta.move_x, move_y: delta.move_y).to_f
53
+
54
+ repeat_index = create_repeat_index(gesture: type, finger: finger,
55
+ direction: repeat_direction, status: status)
56
+
57
+ if status == 'update'
58
+ return unless moved?(repeat_quantity)
59
+
60
+ oneshot_direction = Direction.new(move_x: oneshot_move_x, move_y: oneshot_move_y).to_s
61
+ oneshot_quantity = Quantity.new(move_x: oneshot_move_x, move_y: oneshot_move_y).to_f
62
+ oneshot_index = create_oneshot_index(gesture: type, finger: finger,
63
+ direction: oneshot_direction)
64
+ if enough_oneshot_threshold?(index: oneshot_index, quantity: oneshot_quantity)
65
+ return [
66
+ create_event(record: Events::Records::IndexRecord.new(
67
+ index: oneshot_index, trigger: :oneshot, args: delta.to_h
68
+ )),
69
+ create_event(record: Events::Records::IndexRecord.new(
70
+ index: repeat_index, trigger: :repeat, args: delta.to_h
71
+ ))
72
+ ]
73
+ end
74
+ end
75
+ create_event(record: Events::Records::IndexRecord.new(
76
+ index: repeat_index, trigger: :repeat, args: delta.to_h
77
+ ))
78
+ end
37
79
 
38
- create_event(record: Events::Records::IndexRecord.new(index: index))
80
+ # @param [String] gesture
81
+ # @param [Integer] finger
82
+ # @param [String] direction
83
+ # @param [String] status
84
+ # @return [Config::Index]
85
+ def create_repeat_index(gesture:, finger:, direction:, status:)
86
+ Config::Index.new(
87
+ [
88
+ Config::Index::Key.new(gesture),
89
+ Config::Index::Key.new(finger.to_i),
90
+ Config::Index::Key.new(direction, skippable: true),
91
+ Config::Index::Key.new(status)
92
+ ]
93
+ )
39
94
  end
40
95
 
41
96
  # @param [String] gesture
42
97
  # @param [Integer] finger
43
98
  # @param [String] direction
44
99
  # @return [Config::Index]
45
- def create_index(gesture:, finger:, direction:)
100
+ def create_oneshot_index(gesture:, finger:, direction:)
46
101
  Config::Index.new(
47
102
  [
48
103
  Config::Index::Key.new(gesture),
@@ -54,41 +109,23 @@ module Fusuma
54
109
 
55
110
  private
56
111
 
57
- def enough?(index:, quantity:)
58
- enough_interval?(index: index) && enough_distance?(index: index, quantity: quantity)
112
+ def moved?(repeat_quantity)
113
+ repeat_quantity > 0.3
59
114
  end
60
115
 
61
- def enough_distance?(index:, quantity:)
116
+ def enough_oneshot_threshold?(index:, quantity:)
62
117
  quantity > threshold(index: index)
63
118
  end
64
119
 
65
- def enough_interval?(index:)
66
- return true if first_time?
67
- return true if (Time.now - last_time) > interval_time(index: index)
68
-
69
- false
70
- end
71
-
72
120
  def threshold(index:)
73
121
  @threshold ||= {}
74
122
  @threshold[index.cache_key] ||= begin
75
- keys_specific = Config::Index.new [*index.keys, 'threshold']
76
- keys_global = Config::Index.new ['threshold', type]
77
- config_value = Config.search(keys_specific) ||
78
- Config.search(keys_global) || 1
79
- BASE_THERESHOLD * config_value
80
- end
81
- end
82
-
83
- def interval_time(index:)
84
- @interval_time ||= {}
85
- @interval_time[index.cache_key] ||= begin
86
- keys_specific = Config::Index.new [*index.keys, 'interval']
87
- keys_global = Config::Index.new ['interval', type]
88
- config_value = Config.search(keys_specific) ||
89
- Config.search(keys_global) || 1
90
- BASE_INTERVAL * config_value
91
- end
123
+ keys_specific = Config::Index.new [*index.keys, 'threshold']
124
+ keys_global = Config::Index.new ['threshold', type]
125
+ config_value = Config.search(keys_specific) ||
126
+ Config.search(keys_global) || 1
127
+ BASE_THERESHOLD * config_value
128
+ end
92
129
  end
93
130
 
94
131
  # direction of gesture
@@ -99,8 +136,8 @@ module Fusuma
99
136
  UP = 'up'
100
137
 
101
138
  def initialize(move_x:, move_y:)
102
- @move_x = move_x
103
- @move_y = move_y
139
+ @move_x = move_x.to_f
140
+ @move_y = move_y.to_f
104
141
  end
105
142
 
106
143
  def to_s
@@ -109,8 +146,8 @@ module Fusuma
109
146
 
110
147
  def calc
111
148
  if @move_x.abs > @move_y.abs
112
- @move_x > 0 ? RIGHT : LEFT
113
- elsif @move_y > 0
149
+ @move_x.positive? ? RIGHT : LEFT
150
+ elsif @move_y.positive?
114
151
  DOWN
115
152
  else
116
153
  UP
@@ -121,8 +158,8 @@ module Fusuma
121
158
  # quantity of gesture
122
159
  class Quantity
123
160
  def initialize(move_x:, move_y:)
124
- @x = move_x.abs
125
- @y = move_y.abs
161
+ @x = move_x.to_f.abs
162
+ @y = move_y.to_f.abs
126
163
  end
127
164
 
128
165
  def to_f
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../base.rb'
4
- require_relative './records/record.rb'
5
- require_relative './records/text_record.rb'
3
+ require_relative '../base'
4
+ require_relative './records/record'
5
+ require_relative './records/text_record'
6
6
 
7
7
  module Fusuma
8
8
  module Plugin
@@ -15,7 +15,8 @@ module Fusuma
15
15
  # @param time [Time]
16
16
  # @param tag [Tag]
17
17
  # @param record [String, Record]
18
- def initialize(time: Time.now, tag:, record:)
18
+ def initialize(tag:, record:, time: Time.now)
19
+ super()
19
20
  @time = time
20
21
  @tag = tag
21
22
  @record = case record
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fusuma
4
+ module Plugin
5
+ module Events
6
+ module Records
7
+ # Context Record
8
+ class ContextRecord < Record
9
+ # define gesture format
10
+ attr_reader :name, :value
11
+
12
+ # @param name [#to_sym]
13
+ # @param value [String]
14
+ def initialize(name:, value:)
15
+ super()
16
+ @name = name.to_sym
17
+ @value = value
18
+ end
19
+
20
+ def type
21
+ :context
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './text_record.rb'
3
+ require_relative './text_record'
4
4
 
5
5
  module Fusuma
6
6
  module Plugin
@@ -9,19 +9,22 @@ module Fusuma
9
9
  # Gesture Record
10
10
  class GestureRecord < Record
11
11
  # define gesture format
12
- attr_reader :status, :gesture, :finger, :direction
12
+ attr_reader :status, :gesture, :finger, :delta
13
13
 
14
- Delta = Struct.new(:move_x, :move_y, :zoom, :rotate)
14
+ Delta = Struct.new(:move_x, :move_y,
15
+ :unaccelerated_x, :unaccelerated_y,
16
+ :zoom, :rotate)
15
17
 
16
18
  # @param status [String]
17
19
  # @param gesture [String]
18
20
  # @param finger [String, Integer]
19
- # @param direction [Delta, NilClass]
20
- def initialize(status:, gesture:, finger:, direction:)
21
+ # @param delta [Delta, NilClass]
22
+ def initialize(status:, gesture:, finger:, delta:)
23
+ super()
21
24
  @status = status
22
25
  @gesture = gesture
23
26
  @finger = finger.to_i
24
- @direction = direction
27
+ @delta = delta
25
28
  end
26
29
  end
27
30
  end
@@ -8,36 +8,68 @@ module Fusuma
8
8
  # have index
9
9
  class IndexRecord < Record
10
10
  # define gesture format
11
- attr_reader :index
12
- attr_reader :position
11
+ attr_accessor :index
12
+ attr_reader :position, :trigger, :args
13
13
 
14
14
  # @param [Config::Index] index
15
15
  # @param [Symbol] position [:prefix, :body, :surfix]
16
- def initialize(index:, position: :body)
16
+ # @param [Symbol] trigger [:oneshot, :repeat]
17
+ def initialize(index:, position: :body, trigger: :oneshot, args: {})
18
+ super()
17
19
  @index = index
18
20
  @position = position
21
+ @trigger = trigger
22
+ @args = args
19
23
  end
20
24
 
21
25
  def type
22
26
  :index
23
27
  end
24
28
 
29
+ # FIXME: move to Config::Index
25
30
  # @param records [Array<IndexRecord>]
26
- # @return [IndexRecord]
27
- def merge(records:)
31
+ # @return [IndexRecord] when merge is succeeded
32
+ # @return [NilClass] when merge is not succeeded
33
+ def merge(records:, index: @index)
34
+ # FIXME: cache
28
35
  raise "position is NOT body: #{self}" unless mergable?
29
36
 
30
- @index = records.reduce(@index) do |merged_index, record|
31
- case record.position
32
- when :prefix
33
- Config::Index.new([*record.index.keys, *merged_index.keys])
34
- when :surfix
35
- Config::Index.new([*merged_index.keys, *record.index.keys])
36
- else
37
- raise "invalid index position: #{record}"
37
+ if records.empty?
38
+ if Config.find_execute_key(index)
39
+ @index = index
40
+ return self
38
41
  end
42
+ return nil
43
+ end
44
+
45
+ record = records.shift
46
+ new_index = case record.position
47
+ when :surfix
48
+ Config::Index.new([*index.keys, *record.index.keys])
49
+ else
50
+ raise "invalid index position: #{record}"
51
+ end
52
+
53
+ return unless exist_on_conf?(new_index)
54
+
55
+ merge(records: records, index: new_index)
56
+ end
57
+
58
+ # @param [Config::Searcher] searcher
59
+ def exist_on_conf?(index = @index)
60
+ Config.search(index)
61
+ end
62
+
63
+ # @return [Integer]
64
+ def trigger_priority
65
+ case @trigger
66
+ when :oneshot
67
+ 10
68
+ when :repeat
69
+ 100
70
+ else
71
+ 1000
39
72
  end
40
- self
41
73
  end
42
74
 
43
75
  def mergable?
@@ -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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './record.rb'
3
+ require_relative './record'
4
4
 
5
5
  module Fusuma
6
6
  module Plugin
@@ -10,6 +10,7 @@ module Fusuma
10
10
  class TextRecord < Record
11
11
  # @param text [String]
12
12
  def initialize(text)
13
+ super()
13
14
  @text = text
14
15
  end
15
16
 
@@ -1,22 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './executor.rb'
3
+ require 'posix/spawn'
4
+ require_relative './executor'
4
5
 
5
6
  module Fusuma
6
7
  module Plugin
7
8
  module Executors
8
9
  # Exector plugin
9
10
  class CommandExecutor < Executor
11
+ # Executor parameter on config.yml
12
+ # @return [Array<Symbol>]
13
+ def execute_keys
14
+ [:command]
15
+ end
16
+
10
17
  def execute(event)
11
18
  search_command(event).tap do |command|
12
19
  break unless command
13
20
 
14
- MultiLogger.info(command: command)
15
- pid = fork do
16
- Process.daemon(true)
17
- exec(command.to_s)
18
- end
21
+ MultiLogger.info(command: command, args: event.record.args)
22
+
23
+ additional_env = event.record.args
24
+ .deep_transform_keys(&:to_s)
25
+ .deep_transform_values { |v| (v * args_accel(event)).to_s }
19
26
 
27
+ pid = POSIX::Spawn.spawn(additional_env, command.to_s)
20
28
  Process.detach(pid)
21
29
  end
22
30
  end
@@ -33,6 +41,13 @@ module Fusuma
33
41
  command_index = Config::Index.new([*event.record.index.keys, :command])
34
42
  Config.search(command_index)
35
43
  end
44
+
45
+ # @param event [Event]
46
+ # @return [Float]
47
+ def args_accel(event)
48
+ accel_index = Config::Index.new([*event.record.index.keys, :accel])
49
+ (Config.search(accel_index) || 1).to_f
50
+ end
36
51
  end
37
52
  end
38
53
  end