fusuma 2.5.0 → 3.0.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.
@@ -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