fusuma 1.10.0 → 2.0.0.pre2

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 (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 +56 -4
  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 +164 -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 +5 -4
  20. data/lib/fusuma/hash_support.rb +40 -0
  21. data/lib/fusuma/libinput_command.rb +16 -20
  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 +12 -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 +63 -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 +10 -28
  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 +20 -149
@@ -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
@@ -8,14 +8,56 @@ module Fusuma
8
8
  module Executors
9
9
  # Inherite this base
10
10
  class Executor < Base
11
+ BASE_ONESHOT_INTERVAL = 0.3
12
+ BASE_REPEAT_INTERVAL = 0.1
13
+
14
+ # Executor parameter on config.yml
15
+ # @return [Array<Symbol>]
16
+ def execute_keys
17
+ # [name.split('Executors::').last.underscore.gsub('_executor', '').to_sym]
18
+ raise NotImplementedError, "override #{name}##{__method__}"
19
+ end
20
+
11
21
  # check executable
12
- # @param _event [Event]
22
+ # @param _event [Events::Event]
13
23
  # @return [TrueClass, FalseClass]
14
24
  def executable?(_event)
15
25
  raise NotImplementedError, "override #{self.class.name}##{__method__}"
16
26
  end
17
27
 
18
- # execute somthing
28
+ # @param event [Events::Event]
29
+ # @param time [Time]
30
+ # @return [TrueClass, FalseClass]
31
+ 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 }
35
+
36
+ return false if @wait_until && event.time < @wait_until
37
+
38
+ true
39
+ end
40
+
41
+ def update_interval(event)
42
+ @wait_until = event.time + interval(event).to_f
43
+ end
44
+
45
+ def interval(event)
46
+ @interval_time ||= {}
47
+ index = event.record.index
48
+ @interval_time[index.cache_key] ||= begin
49
+ config_value =
50
+ Config.search(Config::Index.new([*index.keys, 'interval'])) ||
51
+ Config.search(Config::Index.new(['interval', Detectors::Detector.type(event.tag)]))
52
+ if event.record.trigger == :oneshot
53
+ (config_value || 1) * BASE_ONESHOT_INTERVAL
54
+ else
55
+ (config_value || 1) * BASE_REPEAT_INTERVAL
56
+ end
57
+ end
58
+ end
59
+
60
+ # execute something
19
61
  # @param _event [Event]
20
62
  # @return [nil]
21
63
  def execute(_event)
@@ -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,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './filter.rb'
4
- require_relative '../../device.rb'
3
+ require_relative './filter'
4
+ require_relative '../../device'
5
5
 
6
6
  module Fusuma
7
7
  module Plugin
@@ -25,15 +25,14 @@ module Fusuma
25
25
  keep_device.reset
26
26
  return false
27
27
  end
28
-
29
- keep_device.all.map(&:id).any? { |device_id| record.to_s =~ /^\s?#{device_id}\s/ }
28
+ keep_device.all.map(&:id).any? { |device_id| record.to_s =~ /^[\s-]?#{device_id}\s/ }
30
29
  end
31
30
 
32
31
  def keep_device
33
32
  @keep_device ||= begin
34
- from_config = Array(config_params(:keep_device_names))
35
- KeepDevice.new(name_patterns: from_config)
36
- end
33
+ from_config = Array(config_params(:keep_device_names))
34
+ KeepDevice.new(name_patterns: from_config)
35
+ end
37
36
  end
38
37
 
39
38
  def config_param_sample
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './filter.rb'
4
- require_relative '../../libinput_command.rb'
3
+ require_relative './filter'
4
+ require_relative '../../libinput_command'
5
5
 
6
6
  module Fusuma
7
7
  module Plugin
@@ -1,22 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../base.rb'
4
- require_relative '../events/event.rb'
3
+ require_relative '../base'
4
+ require_relative '../events/event'
5
5
 
6
6
  module Fusuma
7
7
  module Plugin
8
- # input class
9
8
  module Inputs
10
9
  # Inherite this base
10
+ # @abstract Subclass and override {#io} to implement
11
11
  class Input < Base
12
- def run
12
+ # Wait multiple inputs until it becomes readable
13
+ # and read lines with nonblock
14
+ # @param inputs [Array<Input>]
15
+ # @return [Event]
16
+ def self.select(inputs)
17
+ ios = IO.select(inputs.map(&:io))
18
+ io = ios&.first&.first
19
+
20
+ input = inputs.find { |i| i.io == io }
21
+
22
+ begin
23
+ line = io.readline_nonblock("\n").chomp
24
+ 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
+ Process.kill(:SIGKILL, i.pid)
29
+ end
30
+ exit 1
31
+ rescue StandardError => e
32
+ warn "#{input.class.name}: #{e}"
33
+ exit 1
34
+ end
35
+
36
+ input.create_event(record: line)
37
+ end
38
+
39
+ # @return [Integer]
40
+ def pid
13
41
  raise NotImplementedError, "override #{self.class.name}##{__method__}"
14
42
  end
15
43
 
16
- def event(record: 'dummy input')
17
- Events::Event.new(tag: tag, record: record).tap do |e|
18
- MultiLogger.debug(input_event: e)
19
- end
44
+ # @return [IO]
45
+ def io
46
+ raise NotImplementedError, "override #{self.class.name}##{__method__}"
47
+ end
48
+
49
+ # @return [Event]
50
+ def create_event(record: 'dummy input')
51
+ e = Events::Event.new(tag: tag, record: record)
52
+ MultiLogger.debug(input_event: e)
53
+ e
20
54
  end
21
55
 
22
56
  def tag
@@ -26,3 +60,24 @@ module Fusuma
26
60
  end
27
61
  end
28
62
  end
63
+
64
+ # ref: https://github.com/Homebrew/brew/blob/6b2dbbc96f7d8aa12f9b8c9c60107c9cc58befc4/Library/Homebrew/extend/io.rb
65
+ class IO
66
+ def readline_nonblock(sep = $INPUT_RECORD_SEPARATOR)
67
+ line = +''
68
+ buffer = +''
69
+
70
+ loop do
71
+ break if buffer == sep
72
+
73
+ read_nonblock(1, buffer)
74
+ line.concat(buffer)
75
+ end
76
+
77
+ line.freeze
78
+ rescue IO::WaitReadable, EOFError => e
79
+ raise e if line.empty?
80
+
81
+ line.freeze
82
+ end
83
+ end
@@ -1,37 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../libinput_command.rb'
4
- require_relative './input.rb'
3
+ require_relative '../../libinput_command'
4
+ require_relative './input'
5
5
 
6
6
  module Fusuma
7
7
  module Plugin
8
8
  module Inputs
9
9
  # libinput commands wrapper
10
10
  class LibinputCommandInput < Input
11
+ attr_reader :pid
12
+
11
13
  def config_param_types
12
14
  {
13
- 'device': [String],
15
+ device: [String],
14
16
  'enable-dwt': [TrueClass, FalseClass],
15
17
  'enable-tap': [TrueClass, FalseClass],
16
18
  'show-keycodes': [TrueClass, FalseClass],
17
- 'verbose': [TrueClass, FalseClass],
19
+ verbose: [TrueClass, FalseClass],
18
20
  'libinput-debug-events': [String],
19
21
  'libinput-list-devices': [String]
20
22
  }
21
23
  end
22
24
 
23
- def run
24
- LibinputCommand.new(
25
+ # @return [IO]
26
+ def io
27
+ @io ||= begin
28
+ @pid, io = command.debug_events
29
+ io
30
+ end
31
+ end
32
+
33
+ # @return [LibinputCommand]
34
+ def command
35
+ @command ||= LibinputCommand.new(
25
36
  libinput_options: libinput_options,
26
37
  commands: {
27
38
  debug_events_command: debug_events_command,
28
39
  list_devices_command: list_devices_command
29
40
  }
30
- ).debug_events do |line|
31
- yield event(record: line)
32
- end
41
+ )
33
42
  end
34
43
 
44
+ # @return [Array]
35
45
  def libinput_options
36
46
  device = ("--device='#{config_params(:device)}'" if config_params(:device))
37
47
  enable_tap = '--enable-tap' if config_params(:'enable-tap')
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './input'
4
+
5
+ module Fusuma
6
+ module Plugin
7
+ module Inputs
8
+ # libinput commands wrapper
9
+ class TimerInput < Input
10
+ DEFAULT_INTERVAL = 0.3
11
+ def config_param_types
12
+ {
13
+ interval: [Float]
14
+ }
15
+ end
16
+
17
+ attr_reader :pid
18
+
19
+ def io
20
+ @io ||= begin
21
+ reader, writer = create_io
22
+ @pid = start(reader, writer)
23
+
24
+ reader
25
+ end
26
+ end
27
+
28
+ def start(reader, writer)
29
+ pid = fork do
30
+ timer_loop(reader, writer)
31
+ end
32
+ Process.detach(pid)
33
+ writer.close
34
+ pid
35
+ end
36
+
37
+ def timer_loop(reader, writer)
38
+ reader.close
39
+ begin
40
+ loop do
41
+ sleep interval
42
+ writer.puts 'timer'
43
+ end
44
+ rescue Errno::EPIPE
45
+ exit 0
46
+ rescue StandardError => e
47
+ MultiLogger.error e
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def create_io
54
+ IO.pipe
55
+ end
56
+
57
+ def interval
58
+ config_params(:interval) || DEFAULT_INTERVAL
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pathname'
4
- require_relative '../multi_logger.rb'
3
+ require_relative '../multi_logger'
4
+ require_relative '../string_support'
5
5
 
6
6
  module Fusuma
7
7
  module Plugin
@@ -58,14 +58,14 @@ module Fusuma
58
58
  end
59
59
 
60
60
  def require_base_plugins
61
- require_relative './base.rb'
62
- require_relative './events/event.rb'
63
- require_relative './inputs/input.rb'
64
- require_relative './filters/filter.rb'
65
- require_relative './parsers/parser.rb'
66
- require_relative './buffers/buffer.rb'
67
- require_relative './detectors/detector.rb'
68
- require_relative './executors/executor.rb'
61
+ require_relative './base'
62
+ require_relative './events/event'
63
+ require_relative './inputs/input'
64
+ require_relative './filters/filter'
65
+ require_relative './parsers/parser'
66
+ require_relative './buffers/buffer'
67
+ require_relative './detectors/detector'
68
+ require_relative './executors/executor'
69
69
  end
70
70
 
71
71
  def plugins
@@ -90,21 +90,3 @@ module Fusuma
90
90
  end
91
91
  end
92
92
  end
93
-
94
- # support camerize and underscore
95
- class String
96
- def camerize
97
- split('_').map do |w|
98
- w[0].upcase!
99
- w
100
- end.join
101
- end
102
-
103
- def underscore
104
- gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
105
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
106
- .gsub('::', '/')
107
- .tr('-', '_')
108
- .downcase
109
- end
110
- end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../events/records/record.rb'
4
- require_relative '../events/records/gesture_record.rb'
3
+ require_relative '../events/records/record'
4
+ require_relative '../events/records/gesture_record'
5
5
 
6
6
  module Fusuma
7
7
  module Plugin
@@ -15,7 +15,7 @@ module Fusuma
15
15
  def parse_record(record)
16
16
  case line = record.to_s
17
17
  when /GESTURE_SWIPE|GESTURE_PINCH/
18
- gesture, status, finger, direction = parse_libinput(line)
18
+ gesture, status, finger, delta = parse_libinput(line)
19
19
  else
20
20
  return
21
21
  end
@@ -23,7 +23,7 @@ module Fusuma
23
23
  Events::Records::GestureRecord.new(status: status,
24
24
  gesture: gesture,
25
25
  finger: finger,
26
- direction: direction)
26
+ delta: delta)
27
27
  end
28
28
 
29
29
  private
@@ -32,8 +32,8 @@ module Fusuma
32
32
  _device, event_name, _time, other = line.strip.split(nil, 4)
33
33
  finger, other = other.split(nil, 2)
34
34
 
35
- direction = parse_direction(other)
36
- [*detect_gesture(event_name), finger, direction]
35
+ delta = parse_delta(other)
36
+ [*detect_gesture(event_name), finger, delta]
37
37
  end
38
38
 
39
39
  def detect_gesture(event_name)
@@ -41,11 +41,13 @@ module Fusuma
41
41
  [Regexp.last_match(1).downcase, Regexp.last_match(2).downcase]
42
42
  end
43
43
 
44
- def parse_direction(line)
44
+ def parse_delta(line)
45
45
  return if line.nil?
46
46
 
47
- move_x, move_y, _, _, _, zoom, _, rotate = line.tr('/|(|)', ' ').split
47
+ move_x, move_y, unaccelerated_x, unaccelerated_y, _, zoom, _, rotate =
48
+ line.tr('/|(|)', ' ').split
48
49
  Events::Records::GestureRecord::Delta.new(move_x.to_f, move_y.to_f,
50
+ unaccelerated_x.to_f, unaccelerated_y.to_f,
49
51
  zoom.to_f, rotate.to_f)
50
52
  end
51
53
  end
@@ -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
@@ -13,15 +13,14 @@ module Fusuma
13
13
  # @param event [Event]
14
14
  # @return [Event]
15
15
  def parse(event)
16
- event.tap do |e|
17
- next if e.tag != source
16
+ return event if event.tag != source
18
17
 
19
- new_record = parse_record(e.record)
20
- next unless new_record
18
+ new_record = parse_record(event.record)
19
+ return event if new_record.nil?
21
20
 
22
- e.record = new_record
23
- e.tag = tag
24
- end
21
+ event.record = new_record
22
+ event.tag = tag
23
+ event
25
24
  end
26
25
 
27
26
  # Set source for tag from config.yml.
@@ -31,7 +30,7 @@ module Fusuma
31
30
  end
32
31
 
33
32
  def tag
34
- self.class.name.split('::').last.underscore
33
+ @tag ||= self.class.name.split('::').last.underscore
35
34
  end
36
35
 
37
36
  # parse Record object