fusuma 2.5.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,9 +7,25 @@ module Fusuma
7
7
  module Buffers
8
8
  # manage events and generate command
9
9
  class GestureBuffer < Buffer
10
+ CacheEntry = Struct.new(:checked, :value)
10
11
  DEFAULT_SOURCE = "libinput_gesture_parser"
11
12
  DEFAULT_SECONDS_TO_KEEP = 100
12
13
 
14
+
15
+ def initialize(*args)
16
+ super(*args)
17
+ @cache = {}
18
+ @cache_select_by = {}
19
+ @cache_sum10 = {}
20
+ end
21
+
22
+ def clear
23
+ super.clear
24
+ @cache = {}
25
+ @cache_select_by = {}
26
+ @cache_sum10 = {}
27
+ end
28
+
13
29
  def config_param_types
14
30
  {
15
31
  source: [String],
@@ -40,6 +56,9 @@ module Fusuma
40
56
  MultiLogger.debug("#{self.class.name}##{__method__}")
41
57
 
42
58
  @events.delete(e)
59
+ @cache = {}
60
+ @cache_select_by = {}
61
+ @cache_sum10 = {}
43
62
  end
44
63
  end
45
64
 
@@ -59,11 +78,35 @@ module Fusuma
59
78
  def sum_attrs(attr)
60
79
  updating_events.map do |gesture_event|
61
80
  gesture_event.record.delta[attr].to_f
62
- end.inject(:+)
81
+ end.reduce(:+)
82
+ end
83
+
84
+ # @param attr [Symbol]
85
+ # @return [Float]
86
+ def sum_last10_attrs(attr) # sums last 10 values of attr (or all if length < 10)
87
+ cache_entry = ( @cache_sum10[attr] ||= CacheEntry.new(0, 0) )
88
+ upd_ev = updating_events
89
+ if upd_ev.length > cache_entry.checked + 1 then
90
+ cache_entry.value = upd_ev.last(10).map do |gesture_event|
91
+ gesture_event.record.delta[attr].to_f
92
+ end.reduce(:+)
93
+ elsif upd_ev.length > cache_entry.checked
94
+ cache_entry.value = cache_entry.value + upd_ev[-1].record.delta[attr].to_f - \
95
+ (upd_ev.length > 10 ? upd_ev[-11].record.delta[attr].to_f : 0)
96
+ else
97
+ return cache_entry.value
98
+ end
99
+ cache_entry.checked = upd_ev.length
100
+ cache_entry.value
63
101
  end
64
102
 
65
103
  def updating_events
66
- @events.select { |e| e.record.status == "update" }
104
+ cache_entry = ( @cache[:updating_events] ||= CacheEntry.new(0, []) )
105
+ cache_entry.checked.upto(@events.length - 1).each do |i|
106
+ (cache_entry.value << @events[i]) if @events[i].record.status == "update"
107
+ end
108
+ cache_entry.checked = @events.length
109
+ cache_entry.value
67
110
  end
68
111
 
69
112
  # @param attr [Symbol]
@@ -96,14 +139,28 @@ module Fusuma
96
139
  self.class.new events
97
140
  end
98
141
 
142
+ def select_by_type(type)
143
+ cache_entry = ( @cache_select_by[type] ||= CacheEntry.new(0, self.class.new([])) )
144
+ cache_entry.checked.upto(@events.length - 1).each do |i|
145
+ (cache_entry.value.events << @events[i]) if @events[i].record.gesture == type
146
+ end
147
+ cache_entry.checked = @events.length
148
+ cache_entry.value
149
+ end
150
+
99
151
  def select_from_last_begin
100
152
  return self if empty?
153
+ cache_entry = ( @cache[:last_begin] ||= CacheEntry.new(0, nil) )
154
+
155
+ cache_entry.value = (@events.length - 1).downto(cache_entry.checked).find do |i|
156
+ @events[i].record.status == "begin"
157
+ end || cache_entry.value
158
+ cache_entry.checked = @events.length
101
159
 
102
- index_from_last = @events.reverse.find_index { |e| e.record.status == "begin" }
103
- return GestureBuffer.new([]) if index_from_last.nil?
160
+ return self if cache_entry.value == 0
161
+ return GestureBuffer.new([]) if cache_entry.value.nil?
104
162
 
105
- index_last_begin = events.length - index_from_last - 1
106
- GestureBuffer.new(@events[index_last_begin..-1])
163
+ GestureBuffer.new(@events[cache_entry.value..-1])
107
164
  end
108
165
  end
109
166
  end
@@ -8,9 +8,18 @@ module Fusuma
8
8
  module Detectors
9
9
  # Inherite this base
10
10
  class Detector < Base
11
+ def initialize(*args)
12
+ super(*args)
13
+ @tag = self.class.tag
14
+ @type = self.class.type
15
+ end
16
+
17
+ attr_reader :tag
18
+ attr_reader :type
19
+
11
20
  # @return [Array<String>]
12
21
  def sources
13
- @source ||= self.class.const_get(:SOURCES)
22
+ @sources ||= self.class.const_get(:SOURCES)
14
23
  end
15
24
 
16
25
  # Always watch buffers and detect them or not
@@ -43,14 +52,6 @@ module Fusuma
43
52
  @last_time.nil?
44
53
  end
45
54
 
46
- def tag
47
- self.class.tag
48
- end
49
-
50
- def type
51
- self.class.type
52
- end
53
-
54
55
  class << self
55
56
  def tag
56
57
  name.split("Detectors::").last.underscore
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "./detector"
4
+ require_relative "../inputs/timer_input"
4
5
 
5
6
  module Fusuma
6
7
  module Plugin
@@ -10,6 +11,7 @@ module Fusuma
10
11
  SOURCES = %w[gesture timer].freeze
11
12
  BUFFER_TYPE = "gesture"
12
13
  GESTURE_RECORD_TYPE = "hold"
14
+ Timer = Inputs::TimerInput.instance
13
15
 
14
16
  BASE_THERESHOLD = 0.7
15
17
 
@@ -21,16 +23,15 @@ module Fusuma
21
23
  hold_buffer = find_hold_buffer(buffers)
22
24
  return if hold_buffer.empty?
23
25
 
24
- hold_events = hold_buffer.events
26
+ last_hold = hold_buffer.events.last
25
27
 
26
28
  timer_buffer = buffers.find { |b| b.type == "timer" }
27
- timer_events = timer_buffer.events
29
+ last_timer = timer_buffer.events.last
28
30
 
29
31
  finger = hold_buffer.finger
30
- holding_time = calc_holding_time(hold_events: hold_events, timer_events: timer_events)
32
+ holding_time = calc_holding_time(hold_events: hold_buffer.events, last_timer: last_timer)
31
33
 
32
- @timeout ||= nil
33
- status = case hold_events.last.record.status
34
+ status = case last_hold.record.status
34
35
  when "begin"
35
36
  if holding_time.zero?
36
37
  "begin"
@@ -42,16 +43,19 @@ module Fusuma
42
43
  when "end"
43
44
  "end"
44
45
  else
45
- last_record = hold_events.last.record.status
46
+ last_record = last_hold.record.status
46
47
  raise "Unexpected Status:#{last_record.status} in #{last_record}"
47
48
  end
48
49
 
49
50
  repeat_index = create_repeat_index(finger: finger, status: status)
50
51
  oneshot_index = create_oneshot_index(finger: finger)
51
52
 
52
- @timeout = nil if status == "begin"
53
-
54
- if status == "timer"
53
+ if status == "begin" then
54
+ @timeout = nil
55
+ if threshold(index: oneshot_index) < Timer.interval then
56
+ Timer.wake_early(Time.now + threshold(index: oneshot_index))
57
+ end
58
+ elsif status == "timer"
55
59
  return if @timeout
56
60
 
57
61
  return unless enough?(index: oneshot_index, holding_time: holding_time)
@@ -97,12 +101,12 @@ module Fusuma
97
101
  def find_hold_buffer(buffers)
98
102
  buffers.find { |b| b.type == BUFFER_TYPE }
99
103
  .select_from_last_begin
100
- .select_by_events { |e| e.record.gesture == GESTURE_RECORD_TYPE }
104
+ .select_by_type(GESTURE_RECORD_TYPE)
101
105
  end
102
106
 
103
- def calc_holding_time(hold_events:, timer_events:)
104
- last_time = if !timer_events.empty? && (hold_events.last.time < timer_events.last.time)
105
- timer_events.last.time
107
+ def calc_holding_time(hold_events:, last_timer:)
108
+ last_time = if last_timer && (hold_events.last.time < last_timer.time)
109
+ last_timer.time
106
110
  else
107
111
  hold_events.last.time
108
112
  end
@@ -110,7 +114,13 @@ module Fusuma
110
114
  end
111
115
 
112
116
  def enough?(index:, holding_time:)
113
- holding_time > threshold(index: index)
117
+ diff = threshold(index: index) - holding_time
118
+ if diff < 0 then
119
+ true
120
+ elsif diff < Timer.interval
121
+ Timer.wake_early(Time.now + diff)
122
+ false
123
+ end
114
124
  end
115
125
 
116
126
  def threshold(index:)
@@ -19,7 +19,7 @@ module Fusuma
19
19
  def detect(buffers)
20
20
  gesture_buffer = buffers.find { |b| b.type == BUFFER_TYPE }
21
21
  .select_from_last_begin
22
- .select_by_events { |e| e.record.gesture == GESTURE_RECORD_TYPE }
22
+ .select_by_type(GESTURE_RECORD_TYPE)
23
23
 
24
24
  updating_events = gesture_buffer.updating_events
25
25
  return if updating_events.empty?
@@ -100,10 +100,10 @@ module Fusuma
100
100
  def create_repeat_index(gesture:, finger:, direction:, status:)
101
101
  Config::Index.new(
102
102
  [
103
- Config::Index::Key.new(gesture),
104
- Config::Index::Key.new(finger.to_i),
105
- Config::Index::Key.new(direction, skippable: true),
106
- Config::Index::Key.new(status)
103
+ Config::Index::Key.new(gesture), # 'pinch'
104
+ Config::Index::Key.new(finger.to_i), # 2, 3, 4
105
+ Config::Index::Key.new(direction, skippable: true), # 'in', 'out'
106
+ Config::Index::Key.new(status) # 'begin', 'update', 'end'
107
107
  ]
108
108
  )
109
109
  end
@@ -115,9 +115,9 @@ module Fusuma
115
115
  def create_oneshot_index(gesture:, finger:, direction:)
116
116
  Config::Index.new(
117
117
  [
118
- Config::Index::Key.new(gesture),
119
- Config::Index::Key.new(finger.to_i, skippable: true),
120
- Config::Index::Key.new(direction)
118
+ Config::Index::Key.new(gesture), # 'pinch'
119
+ Config::Index::Key.new(finger.to_i, skippable: true), # 2, 3, 4
120
+ Config::Index::Key.new(direction) # 'in', 'out'
121
121
  ]
122
122
  )
123
123
  end
@@ -161,9 +161,9 @@ module Fusuma
161
161
 
162
162
  def calc
163
163
  if @target > @base
164
- IN
165
- else
166
164
  OUT
165
+ else
166
+ IN
167
167
  end
168
168
  end
169
169
  end
@@ -19,21 +19,14 @@ module Fusuma
19
19
  def detect(buffers)
20
20
  gesture_buffer = buffers.find { |b| b.type == BUFFER_TYPE }
21
21
  .select_from_last_begin
22
- .select_by_events { |e| e.record.gesture == GESTURE_RECORD_TYPE }
22
+ .select_by_type(GESTURE_RECORD_TYPE)
23
23
 
24
24
  updating_events = gesture_buffer.updating_events
25
25
  return if updating_events.empty?
26
26
 
27
- oneshot_angle = if updating_events.size >= 10
28
- updating_time = 100 * (updating_events[-1].time - updating_events[-10].time)
29
- last_10 = gesture_buffer.class.new(updating_events[-10..-1])
30
- last_10.sum_attrs(:rotate) / updating_time
31
- else
32
- updating_time = 100 * (updating_events.last.time - updating_events.first.time)
33
- gesture_buffer.sum_attrs(:rotate) / updating_time
34
- end
35
-
36
- return if updating_events.empty?
27
+ updating_time = 100 * (updating_events[-1].time -
28
+ (updating_events[-10] || updating_events.first).time)
29
+ oneshot_angle = gesture_buffer.sum_last10_attrs(:rotate) / updating_time
37
30
 
38
31
  finger = gesture_buffer.finger
39
32
 
@@ -19,22 +19,15 @@ module Fusuma
19
19
  def detect(buffers)
20
20
  gesture_buffer = buffers.find { |b| b.type == BUFFER_TYPE }
21
21
  .select_from_last_begin
22
- .select_by_events { |e| e.record.gesture == GESTURE_RECORD_TYPE }
22
+ .select_by_type(GESTURE_RECORD_TYPE)
23
23
 
24
24
  updating_events = gesture_buffer.updating_events
25
25
  return if updating_events.empty?
26
26
 
27
- oneshot_move_x, oneshot_move_y = if updating_events.size >= 10
28
- updating_time = 100 * (updating_events[-1].time - updating_events[-10].time)
29
- last_10 = gesture_buffer.class.new(updating_events[-10..-1])
30
- [last_10.sum_attrs(:move_x) / updating_time,
31
- last_10.sum_attrs(:move_y) / updating_time]
32
- else
33
- updating_time = 100 * (updating_events.last.time - updating_events.first.time)
34
- [gesture_buffer.sum_attrs(:move_x) / updating_time,
35
- gesture_buffer.sum_attrs(:move_y) / updating_time]
36
- end
37
- (gesture_buffer.sum_attrs(:move_x) / updating_time)
27
+ updating_time = 100 * (updating_events.last.time -
28
+ (updating_events[-10] || updating_events.first).time)
29
+ oneshot_move_x = gesture_buffer.sum_last10_attrs(:move_x) / updating_time
30
+ oneshot_move_y = gesture_buffer.sum_last10_attrs(:move_y) / updating_time
38
31
 
39
32
  finger = gesture_buffer.finger
40
33
  status = case gesture_buffer.events.last.record.status
@@ -67,8 +60,7 @@ module Fusuma
67
60
 
68
61
  oneshot_direction = Direction.new(move_x: oneshot_move_x, move_y: oneshot_move_y).to_s
69
62
  oneshot_quantity = Quantity.new(move_x: oneshot_move_x, move_y: oneshot_move_y).to_f
70
- oneshot_index = create_oneshot_index(gesture: type, finger: finger,
71
- direction: oneshot_direction)
63
+ oneshot_index = create_oneshot_index(gesture: type, finger: finger, direction: oneshot_direction)
72
64
  if enough_oneshot_threshold?(index: oneshot_index, quantity: oneshot_quantity)
73
65
  return [
74
66
  create_event(record: Events::Records::IndexRecord.new(
@@ -109,7 +101,7 @@ module Fusuma
109
101
  Config::Index.new(
110
102
  [
111
103
  Config::Index::Key.new(gesture),
112
- Config::Index::Key.new(finger.to_i, skippable: true),
104
+ Config::Index::Key.new(finger.to_i),
113
105
  Config::Index::Key.new(direction)
114
106
  ]
115
107
  )
@@ -11,9 +11,12 @@ module Fusuma
11
11
  # define gesture format
12
12
  attr_reader :status, :gesture, :finger, :delta
13
13
 
14
- Delta = Struct.new(:move_x, :move_y,
14
+ Delta = Struct.new(
15
+ :move_x, :move_y,
15
16
  :unaccelerated_x, :unaccelerated_y,
16
- :zoom, :rotate)
17
+ :zoom,
18
+ :rotate
19
+ )
17
20
 
18
21
  # @param status [String]
19
22
  # @param gesture [String]
@@ -35,7 +35,7 @@ module Fusuma
35
35
  raise "position is NOT body: #{self}" unless mergable?
36
36
 
37
37
  if records.empty?
38
- if Config.find_execute_key(index)
38
+ if Config.instance.find_execute_key(index)
39
39
  @index = index
40
40
  return self
41
41
  end
@@ -39,14 +39,14 @@ module Fusuma
39
39
  # @return [String]
40
40
  def search_command(event)
41
41
  command_index = Config::Index.new([*event.record.index.keys, :command])
42
- Config.search(command_index)
42
+ Config.instance.search(command_index)
43
43
  end
44
44
 
45
45
  # @param event [Event]
46
46
  # @return [Float]
47
47
  def args_accel(event)
48
48
  accel_index = Config::Index.new([*event.record.index.keys, :accel])
49
- (Config.search(accel_index) || 1).to_f
49
+ (Config.instance.search(accel_index) || 1).to_f
50
50
  end
51
51
  end
52
52
  end
@@ -26,12 +26,9 @@ module Fusuma
26
26
  end
27
27
 
28
28
  # @param event [Events::Event]
29
- # @param time [Time]
30
29
  # @return [TrueClass, FalseClass]
31
30
  def enough_interval?(event)
32
- # NOTE: Cache at the index that is actually used, reflecting Fallback and Skip.
33
- # Otherwise, a wrong index will cause invalid intervals.
34
- return true if event.record.index.with_context.keys.any? { |key| key.symbol == :end }
31
+ return true if event.record.index.keys.any? { |key| key.symbol == :end }
35
32
 
36
33
  return false if @wait_until && event.time < @wait_until
37
34
 
@@ -25,7 +25,8 @@ module Fusuma
25
25
  keep_device.reset
26
26
  return false
27
27
  end
28
- keep_device.all.map(&:id).any? { |device_id| record.to_s =~ /^[\s-]?#{device_id}\s/ }
28
+ device_id = record.to_s.match(/\S*/, 1).to_s
29
+ keep_device.all.map(&:id).include?(device_id)
29
30
  end
30
31
 
31
32
  def keep_device
@@ -9,8 +9,14 @@ module Fusuma
9
9
  # Inherite this base
10
10
  # @abstract Subclass and override {#io} to implement
11
11
  class Input < Base
12
+ def initialize(*args)
13
+ super(*args)
14
+ @tag = self.class.name.split("Inputs::").last.underscore
15
+ end
16
+
17
+ attr_reader :tag
18
+
12
19
  # Wait multiple inputs until it becomes readable
13
- # and read lines with nonblock
14
20
  # @param inputs [Array<Input>]
15
21
  # @return [Event]
16
22
  def self.select(inputs)
@@ -20,20 +26,17 @@ module Fusuma
20
26
  input = inputs.find { |i| i.io == io }
21
27
 
22
28
  begin
23
- line = io.readline_nonblock("\n").chomp
29
+ # NOTE: io.readline is blocking method
30
+ # each input plugin must write line to pipe (include `\n`)
31
+ line = io.readline(chomp: true)
24
32
  rescue EOFError => e
25
- warn "#{input.class.name}: #{e}"
26
- warn "Send SIGKILL to fusuma processes"
27
- inputs.reject { |i| i == input }.each do |i|
28
- warn "stop process: #{i.class.name.underscore}"
29
- Process.kill(:SIGKILL, i.pid)
30
- end
31
- exit 1
33
+ MultiLogger.error "#{input.class.name}: #{e}"
34
+ MultiLogger.error "Shutdown fusuma process..."
35
+ Process.kill("TERM", Process.pid)
32
36
  rescue => e
33
- warn "#{input.class.name}: #{e}"
37
+ MultiLogger.error "#{input.class.name}: #{e}"
34
38
  exit 1
35
39
  end
36
-
37
40
  input.create_event(record: line)
38
41
  end
39
42
 
@@ -53,32 +56,7 @@ module Fusuma
53
56
  MultiLogger.debug(input_event: e)
54
57
  e
55
58
  end
56
-
57
- def tag
58
- self.class.name.split("Inputs::").last.underscore
59
- end
60
59
  end
61
60
  end
62
61
  end
63
62
  end
64
-
65
- # ref: https://github.com/Homebrew/brew/blob/6b2dbbc96f7d8aa12f9b8c9c60107c9cc58befc4/Library/Homebrew/extend/io.rb
66
- class IO
67
- def readline_nonblock(sep = $INPUT_RECORD_SEPARATOR)
68
- line = +""
69
- buffer = +""
70
-
71
- loop do
72
- break if buffer == sep
73
-
74
- read_nonblock(1, buffer)
75
- line.concat(buffer)
76
- end
77
-
78
- line.freeze
79
- rescue IO::WaitReadable, EOFError => e
80
- raise e if line.empty?
81
-
82
- line.freeze
83
- end
84
- end
@@ -1,20 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "./input"
4
+ require "timeout"
4
5
 
5
6
  module Fusuma
6
7
  module Plugin
7
8
  module Inputs
8
9
  # libinput commands wrapper
9
10
  class TimerInput < Input
10
- DEFAULT_INTERVAL = 0.3
11
+ include Singleton
12
+ DEFAULT_INTERVAL = 5
13
+ EPSILON_TIME = 0.02
11
14
  def config_param_types
12
15
  {
13
16
  interval: [Float]
14
17
  }
15
18
  end
16
19
 
17
- attr_reader :pid
20
+ def initialize(*args, interval: nil)
21
+ super(*args)
22
+ @interval = interval || config_params(:interval) || DEFAULT_INTERVAL
23
+ @early_wake_queue = Queue.new
24
+ end
25
+
26
+ attr_reader :pid, :interval
18
27
 
19
28
  def io
20
29
  @io ||= begin
@@ -26,26 +35,36 @@ module Fusuma
26
35
  end
27
36
 
28
37
  def start(reader, writer)
29
- pid = fork do
30
- timer_loop(reader, writer)
38
+ Thread.new do
39
+ timer_loop(writer)
31
40
  end
32
- Process.detach(pid)
33
- writer.close
34
- pid
41
+ nil
35
42
  end
36
43
 
37
- def timer_loop(reader, writer)
38
- reader.close
39
- begin
40
- loop do
41
- sleep interval
42
- writer.puts "timer"
44
+ def timer_loop(writer)
45
+ delta_t = @interval
46
+ next_wake = Time.now + delta_t
47
+ loop do
48
+ sleep_time = next_wake - Time.now
49
+ if sleep_time <= 0
50
+ raise Timeout::Error
51
+ end
52
+
53
+ Timeout.timeout(sleep_time) do
54
+ next_wake = [@early_wake_queue.deq, next_wake].min
43
55
  end
44
- rescue Errno::EPIPE
45
- exit 0
46
- rescue => e
47
- MultiLogger.error e
56
+ rescue Timeout::Error
57
+ writer.puts "timer"
58
+ next_wake = Time.now + delta_t
48
59
  end
60
+ rescue Errno::EPIPE
61
+ exit 0
62
+ rescue => e
63
+ MultiLogger.error e
64
+ end
65
+
66
+ def wake_early(t)
67
+ @early_wake_queue.push(t + EPSILON_TIME)
49
68
  end
50
69
 
51
70
  private
@@ -53,10 +72,6 @@ module Fusuma
53
72
  def create_io
54
73
  IO.pipe
55
74
  end
56
-
57
- def interval
58
- config_params(:interval) || DEFAULT_INTERVAL
59
- end
60
75
  end
61
76
  end
62
77
  end