fusuma 2.5.1 → 3.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.
@@ -1,21 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fusuma/string_support"
3
+ require_relative "./string_support"
4
4
 
5
5
  module Fusuma
6
6
  # Rename process
7
7
  module CustomProcess
8
8
  attr_writer :proctitle
9
9
 
10
+ def child_pids
11
+ @child_pids ||= []
12
+ end
13
+
10
14
  def fork
11
- Process.fork do
15
+ pid = Process.fork do
12
16
  Process.setproctitle(proctitle)
17
+ set_trap # for child process
13
18
  yield
14
19
  end
20
+ child_pids << pid
21
+ pid
22
+ end
23
+
24
+ def shutdown
25
+ child_pids.each do |pid|
26
+ Process.kill("TERM", pid)
27
+ rescue Errno::ESRCH
28
+ # ignore
29
+ end
30
+
31
+ child_pids.each do |pid|
32
+ Process.wait(pid)
33
+ rescue Errno::ECHILD
34
+ # ignore
35
+ end
15
36
  end
16
37
 
17
38
  def proctitle
18
39
  @proctitle ||= self.class.name.underscore
19
40
  end
41
+
42
+ def set_trap
43
+ Signal.trap("INT") {
44
+ shutdown
45
+ exit
46
+ } # Trap ^C
47
+ Signal.trap("TERM") {
48
+ shutdown
49
+ exit
50
+ } # Trap `Kill `
51
+ end
20
52
  end
21
53
  end
data/lib/fusuma/device.rb CHANGED
@@ -66,6 +66,7 @@ module Fusuma
66
66
  line_parser = LineParser.new
67
67
 
68
68
  libinput_command = Plugin::Inputs::LibinputCommandInput.new.command
69
+ # note: this libinput command takes a nontrivial amout of time (~200ms)
69
70
  libinput_command.list_devices do |line|
70
71
  line_parser.push(line)
71
72
  end
@@ -39,6 +39,12 @@ module Fusuma
39
39
  puts device.name
40
40
  end
41
41
  end
42
+
43
+ def print_config
44
+ Config.instance.keymap.each do |conf|
45
+ puts conf.deep_stringify_keys.to_yaml
46
+ end
47
+ end
42
48
  end
43
49
  end
44
50
  end
@@ -2,6 +2,28 @@
2
2
 
3
3
  # Patch to hash
4
4
  class Hash
5
+ # activesupport-5.2.0/lib/active_support/core_ext/hash/deep_merge.rb
6
+ def deep_merge(other_hash, &block)
7
+ dup.deep_merge!(other_hash, &block)
8
+ end
9
+
10
+ # Same as +deep_merge+, but modifies +self+.
11
+ def deep_merge!(other_hash, &block)
12
+ merge!(other_hash) do |key, this_val, other_val|
13
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
14
+ this_val.deep_merge(other_val, &block)
15
+ elsif block
16
+ block.call(key, this_val, other_val)
17
+ else
18
+ other_val
19
+ end
20
+ end
21
+ end
22
+
23
+ def deep_stringify_keys
24
+ deep_transform_keys(&:to_s)
25
+ end
26
+
5
27
  # activesupport-4.1.1/lib/active_support/core_ext/hash/keys.rb
6
28
  def deep_symbolize_keys
7
29
  deep_transform_keys do |key|
@@ -32,6 +32,8 @@ module Fusuma
32
32
  def debug(msg)
33
33
  return unless debug_mode?
34
34
 
35
+ return if ignore_pattern?(msg)
36
+
35
37
  super(msg)
36
38
  end
37
39
 
@@ -47,6 +49,20 @@ module Fusuma
47
49
  debug_mode
48
50
  end
49
51
 
52
+ def ignore_pattern?(msg)
53
+ # TODO: configurable from config.yml
54
+ pattern = /timer_input/
55
+ case msg
56
+ when Hash
57
+ e = msg.values.find { |v| v.is_a? Fusuma::Plugin::Events::Event }
58
+ return unless e
59
+
60
+ e.tag.match?(pattern)
61
+ else
62
+ false
63
+ end
64
+ end
65
+
50
66
  class << self
51
67
  def info(msg)
52
68
  instance.info(msg)
@@ -8,7 +8,6 @@ module Fusuma
8
8
  module Plugin
9
9
  # Create a Plugin Class with extending this class
10
10
  class Base
11
- include CustomProcess
12
11
  # when inherited from subclass
13
12
  def self.inherited(subclass)
14
13
  super
@@ -22,20 +21,30 @@ module Fusuma
22
21
  Manager.plugins[name]
23
22
  end
24
23
 
24
+ # @abstract override `#shutdown` to implement
25
+ def shutdown
26
+ end
27
+
25
28
  # config parameter name and Type of the value of parameter
26
29
  # @return [Hash]
27
30
  def config_param_types
28
31
  raise NotImplementedError, "override #{self.class.name}##{__method__}"
29
32
  end
30
33
 
34
+ # @param key [Symbol]
35
+ # @param base [Config::Index]
31
36
  # @return [Object]
32
- def config_params(key = nil, base: config_index)
33
- params = Config.search(base) || {}
37
+ def config_params(key = nil)
38
+ @config_params ||= {}
39
+ if @config_params["#{config_index.cache_key},#{key}"]
40
+ return @config_params["#{config_index.cache_key},#{key}"]
41
+ end
42
+
43
+ params = Config.instance.fetch_config_params(key, config_index)
34
44
 
35
45
  return params unless key
36
46
 
37
- @config_params ||= {}
38
- @config_params["#{base.cache_key},#{key}"] ||=
47
+ @config_params["#{config_index.cache_key},#{key}"] =
39
48
  params.fetch(key, nil).tap do |val|
40
49
  next if val.nil?
41
50
 
@@ -45,14 +54,14 @@ module Fusuma
45
54
  next if param_types.any? { |klass| val.is_a?(klass) }
46
55
 
47
56
  MultiLogger.error("Please fix config.yml.")
48
- MultiLogger.error(":#{base.keys.map(&:symbol)
57
+ MultiLogger.error(":#{config_index.keys.map(&:symbol)
49
58
  .join(" => :")} => :#{key} should be #{param_types.join(" OR ")}.")
50
59
  exit 1
51
60
  end
52
61
  end
53
62
 
54
63
  def config_index
55
- Config::Index.new(self.class.name.gsub("Fusuma::", "").underscore.split("/"))
64
+ @config_index ||= Config::Index.new(self.class.name.gsub("Fusuma::", "").underscore.split("/"))
56
65
  end
57
66
  end
58
67
  end
@@ -7,9 +7,24 @@ 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
+ def initialize(*args)
15
+ super(*args)
16
+ @cache = {}
17
+ @cache_select_by = {}
18
+ @cache_sum10 = {}
19
+ end
20
+
21
+ def clear
22
+ super.clear
23
+ @cache = {}
24
+ @cache_select_by = {}
25
+ @cache_sum10 = {}
26
+ end
27
+
13
28
  def config_param_types
14
29
  {
15
30
  source: [String],
@@ -40,6 +55,9 @@ module Fusuma
40
55
  MultiLogger.debug("#{self.class.name}##{__method__}")
41
56
 
42
57
  @events.delete(e)
58
+ @cache = {}
59
+ @cache_select_by = {}
60
+ @cache_sum10 = {}
43
61
  end
44
62
  end
45
63
 
@@ -59,11 +77,35 @@ module Fusuma
59
77
  def sum_attrs(attr)
60
78
  updating_events.map do |gesture_event|
61
79
  gesture_event.record.delta[attr].to_f
62
- end.inject(:+)
80
+ end.reduce(:+)
81
+ end
82
+
83
+ # @param attr [Symbol]
84
+ # @return [Float]
85
+ def sum_last10_attrs(attr) # sums last 10 values of attr (or all if length < 10)
86
+ cache_entry = (@cache_sum10[attr] ||= CacheEntry.new(0, 0))
87
+ upd_ev = updating_events
88
+ if upd_ev.length > cache_entry.checked + 1
89
+ cache_entry.value = upd_ev.last(10).map do |gesture_event|
90
+ gesture_event.record.delta[attr].to_f
91
+ end.reduce(:+)
92
+ elsif upd_ev.length > cache_entry.checked
93
+ cache_entry.value = cache_entry.value + upd_ev[-1].record.delta[attr].to_f - \
94
+ ((upd_ev.length > 10) ? upd_ev[-11].record.delta[attr].to_f : 0)
95
+ else
96
+ return cache_entry.value
97
+ end
98
+ cache_entry.checked = upd_ev.length
99
+ cache_entry.value
63
100
  end
64
101
 
65
102
  def updating_events
66
- @events.select { |e| e.record.status == "update" }
103
+ cache_entry = (@cache[:updating_events] ||= CacheEntry.new(0, []))
104
+ cache_entry.checked.upto(@events.length - 1).each do |i|
105
+ (cache_entry.value << @events[i]) if @events[i].record.status == "update"
106
+ end
107
+ cache_entry.checked = @events.length
108
+ cache_entry.value
67
109
  end
68
110
 
69
111
  # @param attr [Symbol]
@@ -96,14 +138,28 @@ module Fusuma
96
138
  self.class.new events
97
139
  end
98
140
 
141
+ def select_by_type(type)
142
+ cache_entry = (@cache_select_by[type] ||= CacheEntry.new(0, self.class.new([])))
143
+ cache_entry.checked.upto(@events.length - 1).each do |i|
144
+ (cache_entry.value.events << @events[i]) if @events[i].record.gesture == type
145
+ end
146
+ cache_entry.checked = @events.length
147
+ cache_entry.value
148
+ end
149
+
99
150
  def select_from_last_begin
100
151
  return self if empty?
152
+ cache_entry = (@cache[:last_begin] ||= CacheEntry.new(0, nil))
153
+
154
+ cache_entry.value = (@events.length - 1).downto(cache_entry.checked).find do |i|
155
+ @events[i].record.status == "begin"
156
+ end || cache_entry.value
157
+ cache_entry.checked = @events.length
101
158
 
102
- index_from_last = @events.reverse.find_index { |e| e.record.status == "begin" }
103
- return GestureBuffer.new([]) if index_from_last.nil?
159
+ return self if cache_entry.value == 0
160
+ return GestureBuffer.new([]) if cache_entry.value.nil?
104
161
 
105
- index_last_begin = events.length - index_from_last - 1
106
- GestureBuffer.new(@events[index_last_begin..-1])
162
+ GestureBuffer.new(@events[cache_entry.value..-1])
107
163
  end
108
164
  end
109
165
  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
@@ -13,6 +14,11 @@ module Fusuma
13
14
 
14
15
  BASE_THERESHOLD = 0.7
15
16
 
17
+ def initialize(*args)
18
+ super(*args)
19
+ @timer = Inputs::TimerInput.instance
20
+ end
21
+
16
22
  # @param buffers [Array<Buffers::Buffer>]
17
23
  # @return [Events::Event] if event is detected
18
24
  # @return [Array<Events::Event>] if hold end event is detected
@@ -21,16 +27,15 @@ module Fusuma
21
27
  hold_buffer = find_hold_buffer(buffers)
22
28
  return if hold_buffer.empty?
23
29
 
24
- hold_events = hold_buffer.events
30
+ last_hold = hold_buffer.events.last
25
31
 
26
32
  timer_buffer = buffers.find { |b| b.type == "timer" }
27
- timer_events = timer_buffer.events
33
+ last_timer = timer_buffer.events.last
28
34
 
29
35
  finger = hold_buffer.finger
30
- holding_time = calc_holding_time(hold_events: hold_events, timer_events: timer_events)
36
+ holding_time = calc_holding_time(hold_events: hold_buffer.events, last_timer: last_timer)
31
37
 
32
- @timeout ||= nil
33
- status = case hold_events.last.record.status
38
+ status = case last_hold.record.status
34
39
  when "begin"
35
40
  if holding_time.zero?
36
41
  "begin"
@@ -42,16 +47,19 @@ module Fusuma
42
47
  when "end"
43
48
  "end"
44
49
  else
45
- last_record = hold_events.last.record.status
50
+ last_record = last_hold.record.status
46
51
  raise "Unexpected Status:#{last_record.status} in #{last_record}"
47
52
  end
48
53
 
49
54
  repeat_index = create_repeat_index(finger: finger, status: status)
50
55
  oneshot_index = create_oneshot_index(finger: finger)
51
56
 
52
- @timeout = nil if status == "begin"
53
-
54
- if status == "timer"
57
+ if status == "begin"
58
+ @timeout = nil
59
+ if threshold(index: oneshot_index) < @timer.interval
60
+ @timer.wake_early(Time.now + threshold(index: oneshot_index))
61
+ end
62
+ elsif status == "timer"
55
63
  return if @timeout
56
64
 
57
65
  return unless enough?(index: oneshot_index, holding_time: holding_time)
@@ -97,12 +105,12 @@ module Fusuma
97
105
  def find_hold_buffer(buffers)
98
106
  buffers.find { |b| b.type == BUFFER_TYPE }
99
107
  .select_from_last_begin
100
- .select_by_events { |e| e.record.gesture == GESTURE_RECORD_TYPE }
108
+ .select_by_type(GESTURE_RECORD_TYPE)
101
109
  end
102
110
 
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
111
+ def calc_holding_time(hold_events:, last_timer:)
112
+ last_time = if last_timer && (hold_events.last.time < last_timer.time)
113
+ last_timer.time
106
114
  else
107
115
  hold_events.last.time
108
116
  end
@@ -110,7 +118,13 @@ module Fusuma
110
118
  end
111
119
 
112
120
  def enough?(index:, holding_time:)
113
- holding_time > threshold(index: index)
121
+ diff = threshold(index: index) - holding_time
122
+ if diff < 0
123
+ true
124
+ elsif diff < @timer.interval
125
+ @timer.wake_early(Time.now + diff)
126
+ false
127
+ end
114
128
  end
115
129
 
116
130
  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
  )
@@ -175,7 +167,7 @@ module Fusuma
175
167
  end
176
168
 
177
169
  def calc
178
- @x > @y ? @x.abs : @y.abs
170
+ (@x > @y) ? @x.abs : @y.abs
179
171
  end
180
172
  end
181
173
  end
@@ -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