fusuma 0.11.1 → 1.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 (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
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../base.rb'
4
+
5
+ module Fusuma
6
+ module Plugin
7
+ module Buffers
8
+ # buffer events and output
9
+ class Buffer < Base
10
+ def initialize(*args)
11
+ @events = Array.new(*args)
12
+ end
13
+
14
+ attr_reader :events
15
+
16
+ # @return [String]
17
+ def type
18
+ self.class.name.underscore.split('/').last.gsub('_buffer', '')
19
+ end
20
+
21
+ # @param event [Event]
22
+ def buffer(event)
23
+ return if event&.tag != source
24
+
25
+ @events.push(event)
26
+ end
27
+
28
+ # clear buffer
29
+ def clear
30
+ @events.clear
31
+ end
32
+
33
+ # Set source for tag from config.yml.
34
+ # DEFAULT_SOURCE is defined in each plugins.
35
+ def source
36
+ @source ||= config_params(:source) || self.class.const_get('DEFAULT_SOURCE')
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './buffer.rb'
4
+
5
+ module Fusuma
6
+ module Plugin
7
+ module Buffers
8
+ # manage events and generate command
9
+ class GestureBuffer < Buffer
10
+ DEFAULT_SOURCE = 'libinput_gesture_parser'
11
+
12
+ # @param event [Event]
13
+ def buffer(event)
14
+ # TODO: buffering events into buffer plugins
15
+ # - gesture event buffer
16
+ # - window event buffer
17
+ # - other event buffer
18
+ return if event&.tag != source
19
+ return if event.record.type != :gesture
20
+
21
+ @events.push(event)
22
+ clear unless updating?
23
+ end
24
+
25
+ # @param attr [Symbol]
26
+ # @return [Float]
27
+ def sum_attrs(attr)
28
+ @events.map { |gesture_event| gesture_event.record.direction[attr].to_f }
29
+ .inject(:+)
30
+ end
31
+
32
+ # @param attr [Symbol]
33
+ # @return [Float]
34
+ def avg_attrs(attr)
35
+ sum_attrs(attr).to_f / @events.length
36
+ end
37
+
38
+ # return [Integer]
39
+ def finger
40
+ @events.last.record.finger.to_i
41
+ end
42
+
43
+ # @example
44
+ # event_buffer.gesture
45
+ # => 'swipe'
46
+ # @return [String]
47
+ def gesture
48
+ @events.last.record.gesture
49
+ end
50
+
51
+ def empty?
52
+ @events.empty?
53
+ end
54
+
55
+ def select_by_events
56
+ return enum_for(:select) unless block_given?
57
+
58
+ events = @events.select { |event| yield event }
59
+ self.class.new events
60
+ end
61
+
62
+ private
63
+
64
+ def updating?
65
+ return true unless @events.last.record.status =~ /begin|end/
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../base.rb'
4
+ require_relative '../events/event.rb'
5
+
6
+ module Fusuma
7
+ module Plugin
8
+ module Detectors
9
+ # Inherite this base
10
+ class Detector < Base
11
+ # @param _buffers [Array<Buffer>]
12
+ # @return [Event] if event is detected
13
+ # @return [NilClass] if event is NOT detected
14
+ def detect(_buffers)
15
+ raise NotImplementedError, "override #{self.class.name}##{__method__}"
16
+
17
+ # create_event(record:)
18
+ end
19
+
20
+ # @param record [Events::Records::Record]
21
+ # @return [Events::Event]
22
+ def create_event(record:)
23
+ @last_time = Time.now
24
+ Events::Event.new(time: Time.now, tag: tag, record: record)
25
+ end
26
+
27
+ def last_time
28
+ @last_time ||= Time.now
29
+ end
30
+
31
+ def tag
32
+ self.class.name.split('Detectors::').last.underscore
33
+ end
34
+
35
+ def type
36
+ self.class.name.underscore.split('/').last.gsub('_detector', '')
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './detector.rb'
4
+
5
+ module Fusuma
6
+ module Plugin
7
+ module Detectors
8
+ class PinchDetector < Detector
9
+ BUFFER_TYPE = 'gesture'
10
+ GESTURE_RECORD_TYPE = 'pinch'
11
+
12
+ FINGERS = [2, 3, 4].freeze
13
+ BASE_THERESHOLD = 0.1
14
+ BASE_INTERVAL = 0.1
15
+
16
+ # @param buffers [Array<Buffer>]
17
+ # @return [Event] if event is detected
18
+ # @return [NilClass] if event is NOT detected
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
+ finger = buffer.finger
26
+
27
+ avg_zoom = buffer.avg_attrs(:zoom)
28
+ first_zoom = buffer.events.first.record.direction.zoom
29
+ diameter = avg_zoom / first_zoom
30
+
31
+ direction = Direction.new(diameter: diameter).to_s
32
+ quantity = Quantity.new(diameter: diameter).to_f
33
+
34
+ index = create_index(gesture: type,
35
+ finger: finger,
36
+ direction: direction)
37
+
38
+ return unless enough?(index: index, quantity: quantity)
39
+
40
+ create_event(record: Events::Records::IndexRecord.new(index: index))
41
+ end
42
+
43
+ # @param [String] gesture
44
+ # @param [Integer] finger
45
+ # @param [String] direction
46
+ # @return [Config::Index]
47
+ def create_index(gesture:, finger:, direction:)
48
+ Config::Index.new(
49
+ [
50
+ Config::Index::Key.new(gesture),
51
+ Config::Index::Key.new(finger.to_i, skippable: true),
52
+ Config::Index::Key.new(direction)
53
+ ]
54
+ )
55
+ end
56
+
57
+ private
58
+
59
+ def enough?(index:, quantity:)
60
+ enough_interval?(index: index) && enough_diameter?(index: index, quantity: quantity)
61
+ end
62
+
63
+ def enough_diameter?(index:, quantity:)
64
+ MultiLogger.info(type: type, quantity: quantity,
65
+ quantity_threshold: threshold(index: index))
66
+ quantity >= threshold(index: index)
67
+ end
68
+
69
+ def enough_interval?(index:)
70
+ return true if first_time?
71
+ return true if (Time.now - @last_time) > interval_time(index: index)
72
+
73
+ false
74
+ end
75
+
76
+ def first_time?
77
+ !@last_time
78
+ end
79
+
80
+ def threshold(index:)
81
+ @threshold ||= {}
82
+ @threshold[index.cache_key] ||= begin
83
+ keys_specific = Config::Index.new [*index.keys, 'threshold']
84
+ keys_global = Config::Index.new ['threshold', type]
85
+ config_value = Config.search(keys_specific) ||
86
+ Config.search(keys_global) || 1
87
+ BASE_THERESHOLD * config_value
88
+ end
89
+ end
90
+
91
+ def interval_time(index:)
92
+ @interval_time ||= {}
93
+ @interval_time[index.cache_key] ||= begin
94
+ keys_specific = Config::Index.new [*index.keys, 'interval']
95
+ keys_global = Config::Index.new ['interval', type]
96
+ config_value = Config.search(keys_specific) ||
97
+ Config.search(keys_global) || 1
98
+ BASE_INTERVAL * config_value
99
+ end
100
+ end
101
+
102
+ # direction of gesture
103
+ class Direction
104
+ IN = 'in'
105
+ OUT = 'out'
106
+
107
+ def initialize(diameter:)
108
+ @diameter = diameter
109
+ end
110
+
111
+ def to_s
112
+ calc
113
+ end
114
+
115
+ def calc
116
+ if @diameter > 1
117
+ IN
118
+ else
119
+ OUT
120
+ end
121
+ end
122
+ end
123
+
124
+ # quantity of gesture
125
+ class Quantity
126
+ def initialize(diameter:)
127
+ @diameter = diameter
128
+ end
129
+
130
+ def to_f
131
+ calc.to_f
132
+ end
133
+
134
+ def calc
135
+ (1.0 - @diameter).abs
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './detector.rb'
4
+
5
+ module Fusuma
6
+ module Plugin
7
+ module Detectors
8
+ class RotateDetector < Detector
9
+ BUFFER_TYPE = 'gesture'
10
+ GESTURE_RECORD_TYPE = 'pinch'
11
+
12
+ FINGERS = [2, 3, 4].freeze
13
+ BASE_THERESHOLD = 0.5
14
+ BASE_INTERVAL = 0.1
15
+
16
+ # @param buffers [Array<Buffer>]
17
+ # @return [Event] if event is detected
18
+ # @return [NilClass] if event is NOT detected
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
+ angle = buffer.avg_attrs(:rotate)
26
+
27
+ finger = buffer.finger
28
+ direction = Direction.new(angle: angle).to_s
29
+ quantity = Quantity.new(angle: angle).to_f
30
+
31
+ index = create_index(gesture: type,
32
+ finger: finger,
33
+ direction: direction)
34
+
35
+ return unless enough?(index: index, quantity: quantity)
36
+
37
+ create_event(record: Events::Records::IndexRecord.new(index: index))
38
+ end
39
+
40
+ # @param [String] gesture
41
+ # @param [Integer] finger
42
+ # @param [String] direction
43
+ # @return [Config::Index]
44
+ def create_index(gesture:, finger:, direction:)
45
+ Config::Index.new(
46
+ [
47
+ Config::Index::Key.new(gesture),
48
+ Config::Index::Key.new(finger.to_i, skippable: true),
49
+ Config::Index::Key.new(direction)
50
+ ]
51
+ )
52
+ end
53
+
54
+ private
55
+
56
+ def enough?(index:, quantity:)
57
+ enough_interval?(index: index) && enough_angle?(index: index, quantity: quantity)
58
+ end
59
+
60
+ def enough_angle?(index:, quantity:)
61
+ MultiLogger.info(type: type, quantity: quantity,
62
+ quantity_threshold: threshold(index: index))
63
+
64
+ quantity > threshold(index: index)
65
+ end
66
+
67
+ def enough_interval?(index:)
68
+ return true if first_time?
69
+ return true if (Time.now - @last_time) > interval_time(index: index)
70
+
71
+ false
72
+ end
73
+
74
+ def first_time?
75
+ !@last_time
76
+ end
77
+
78
+ def threshold(index:)
79
+ @threshold ||= {}
80
+ @threshold[index.cache_key] ||= begin
81
+ keys_specific = Config::Index.new [*index.keys, 'threshold']
82
+ keys_global = Config::Index.new ['threshold', type]
83
+ config_value = Config.search(keys_specific) ||
84
+ Config.search(keys_global) || 1
85
+ BASE_THERESHOLD * config_value
86
+ end
87
+ end
88
+
89
+ def interval_time(index:)
90
+ @interval_time ||= {}
91
+ @interval_time[index.cache_key] ||= begin
92
+ keys_specific = Config::Index.new [*index.keys, 'interval']
93
+ keys_global = Config::Index.new ['interval', type]
94
+ config_value = Config.search(keys_specific) ||
95
+ Config.search(keys_global) || 1
96
+ BASE_INTERVAL * config_value
97
+ end
98
+ end
99
+
100
+ # direction of gesture
101
+ class Direction
102
+ CLOCKWISE = 'clockwise'
103
+ COUNTERCLOCKWISE = 'counterclockwise'
104
+
105
+ def initialize(angle:)
106
+ @angle = angle
107
+ end
108
+
109
+ def to_s
110
+ calc
111
+ end
112
+
113
+ def calc
114
+ if @angle > 0
115
+ CLOCKWISE
116
+ else
117
+ COUNTERCLOCKWISE
118
+ end
119
+ end
120
+ end
121
+
122
+ # quantity of gesture
123
+ class Quantity
124
+ def initialize(angle:)
125
+ @angle = angle.abs
126
+ end
127
+
128
+ def to_f
129
+ @angle.to_f
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './detector.rb'
4
+
5
+ module Fusuma
6
+ module Plugin
7
+ module Detectors
8
+ class SwipeDetector < Detector
9
+ BUFFER_TYPE = 'gesture'
10
+ GESTURE_RECORD_TYPE = 'swipe'
11
+
12
+ FINGERS = [3, 4].freeze
13
+ BASE_THERESHOLD = 10
14
+ BASE_INTERVAL = 0.5
15
+
16
+ # @param buffers [Array<Buffers::Buffer>]
17
+ # @return [Event] if event is detected
18
+ # @return [NilClass] if event is NOT detected
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)
37
+
38
+ create_event(record: Events::Records::IndexRecord.new(index: index))
39
+ end
40
+
41
+ # @param [String] gesture
42
+ # @param [Integer] finger
43
+ # @param [String] direction
44
+ # @return [Config::Index]
45
+ def create_index(gesture:, finger:, direction:)
46
+ Config::Index.new(
47
+ [
48
+ Config::Index::Key.new(gesture),
49
+ Config::Index::Key.new(finger.to_i, skippable: true),
50
+ Config::Index::Key.new(direction)
51
+ ]
52
+ )
53
+ end
54
+
55
+ private
56
+
57
+ def enough?(index:, quantity:)
58
+ enough_interval?(index: index) && enough_distance?(index: index, quantity: quantity)
59
+ end
60
+
61
+ def enough_distance?(index:, quantity:)
62
+ MultiLogger.info(type: type, quantity: quantity,
63
+ quantity_threshold: threshold(index: index))
64
+ quantity > threshold(index: index)
65
+ end
66
+
67
+ def enough_interval?(index:)
68
+ return true if first_time?
69
+ return true if (Time.now - @last_time) > interval_time(index: index)
70
+
71
+ false
72
+ end
73
+
74
+ def first_time?
75
+ !@last_time
76
+ end
77
+
78
+ def threshold(index:)
79
+ @threshold ||= {}
80
+ @threshold[index.cache_key] ||= begin
81
+ keys_specific = Config::Index.new [*index.keys, 'threshold']
82
+ keys_global = Config::Index.new ['threshold', type]
83
+ config_value = Config.search(keys_specific) ||
84
+ Config.search(keys_global) || 1
85
+ BASE_THERESHOLD * config_value
86
+ end
87
+ end
88
+
89
+ def interval_time(index:)
90
+ @interval_time ||= {}
91
+ @interval_time[index.cache_key] ||= begin
92
+ keys_specific = Config::Index.new [*index.keys, 'interval']
93
+ keys_global = Config::Index.new ['interval', type]
94
+ config_value = Config.search(keys_specific) ||
95
+ Config.search(keys_global) || 1
96
+ BASE_INTERVAL * config_value
97
+ end
98
+ end
99
+
100
+ # direction of gesture
101
+ class Direction
102
+ RIGHT = 'right'
103
+ LEFT = 'left'
104
+ DOWN = 'down'
105
+ UP = 'up'
106
+
107
+ def initialize(move_x:, move_y:)
108
+ @move_x = move_x
109
+ @move_y = move_y
110
+ end
111
+
112
+ def to_s
113
+ calc
114
+ end
115
+
116
+ def calc
117
+ if @move_x.abs > @move_y.abs
118
+ @move_x > 0 ? RIGHT : LEFT
119
+ elsif @move_y > 0
120
+ DOWN
121
+ else
122
+ UP
123
+ end
124
+ end
125
+ end
126
+
127
+ # quantity of gesture
128
+ class Quantity
129
+ def initialize(move_x:, move_y:)
130
+ @x = move_x.abs
131
+ @y = move_y.abs
132
+ end
133
+
134
+ def to_f
135
+ calc.to_f
136
+ end
137
+
138
+ def calc
139
+ @x > @y ? @x.abs : @y.abs
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end