fusuma 1.10.2 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -7
  3. data/lib/fusuma.rb +91 -30
  4. data/lib/fusuma/config.rb +59 -60
  5. data/lib/fusuma/config/index.rb +39 -6
  6. data/lib/fusuma/config/searcher.rb +166 -0
  7. data/lib/fusuma/config/yaml_duplication_checker.rb +42 -0
  8. data/lib/fusuma/custom_process.rb +13 -0
  9. data/lib/fusuma/device.rb +22 -7
  10. data/lib/fusuma/environment.rb +6 -4
  11. data/lib/fusuma/hash_support.rb +40 -0
  12. data/lib/fusuma/libinput_command.rb +17 -21
  13. data/lib/fusuma/multi_logger.rb +2 -6
  14. data/lib/fusuma/plugin/base.rb +18 -15
  15. data/lib/fusuma/plugin/buffers/buffer.rb +3 -2
  16. data/lib/fusuma/plugin/buffers/gesture_buffer.rb +34 -25
  17. data/lib/fusuma/plugin/buffers/timer_buffer.rb +46 -0
  18. data/lib/fusuma/plugin/detectors/detector.rb +26 -5
  19. data/lib/fusuma/plugin/detectors/pinch_detector.rb +109 -58
  20. data/lib/fusuma/plugin/detectors/rotate_detector.rb +91 -50
  21. data/lib/fusuma/plugin/detectors/swipe_detector.rb +93 -56
  22. data/lib/fusuma/plugin/events/event.rb +5 -4
  23. data/lib/fusuma/plugin/events/records/context_record.rb +27 -0
  24. data/lib/fusuma/plugin/events/records/gesture_record.rb +9 -6
  25. data/lib/fusuma/plugin/events/records/index_record.rb +46 -14
  26. data/lib/fusuma/plugin/events/records/record.rb +1 -1
  27. data/lib/fusuma/plugin/events/records/text_record.rb +2 -1
  28. data/lib/fusuma/plugin/executors/command_executor.rb +21 -6
  29. data/lib/fusuma/plugin/executors/executor.rb +45 -3
  30. data/lib/fusuma/plugin/filters/filter.rb +1 -1
  31. data/lib/fusuma/plugin/filters/libinput_device_filter.rb +6 -7
  32. data/lib/fusuma/plugin/filters/libinput_timeout_filter.rb +2 -2
  33. data/lib/fusuma/plugin/inputs/input.rb +64 -8
  34. data/lib/fusuma/plugin/inputs/libinput_command_input.rb +19 -9
  35. data/lib/fusuma/plugin/inputs/timer_input.rb +63 -0
  36. data/lib/fusuma/plugin/manager.rb +22 -29
  37. data/lib/fusuma/plugin/parsers/libinput_gesture_parser.rb +10 -8
  38. data/lib/fusuma/plugin/parsers/parser.rb +8 -9
  39. data/lib/fusuma/string_support.rb +16 -0
  40. data/lib/fusuma/version.rb +1 -1
  41. data/spec/helpers/config_helper.rb +20 -0
  42. data/spec/lib/config/searcher_spec.rb +97 -0
  43. data/spec/lib/config_spec.rb +112 -0
  44. data/spec/lib/custom_process_spec.rb +28 -0
  45. data/spec/lib/device_spec.rb +98 -0
  46. data/spec/lib/dummy_config.yml +31 -0
  47. data/spec/lib/fusuma_spec.rb +103 -0
  48. data/spec/lib/libinput-list-devices_iberianpig-XPS-9360.txt +181 -0
  49. data/spec/lib/libinput-list-devices_magic_trackpad.txt +51 -0
  50. data/spec/lib/libinput-list-devices_razer_razer_blade.txt +252 -0
  51. data/spec/lib/libinput-list-devices_thejinx0r.txt +361 -0
  52. data/spec/lib/libinput-list-devices_unavailable.txt +36 -0
  53. data/spec/lib/libinput_command_spec.rb +167 -0
  54. data/spec/lib/plugin/base_spec.rb +74 -0
  55. data/spec/lib/plugin/buffers/buffer_spec.rb +80 -0
  56. data/spec/lib/plugin/buffers/dummy_buffer.rb +20 -0
  57. data/spec/lib/plugin/buffers/gesture_buffer_spec.rb +172 -0
  58. data/spec/lib/plugin/detectors/detector_spec.rb +43 -0
  59. data/spec/lib/plugin/detectors/dummy_detector.rb +24 -0
  60. data/spec/lib/plugin/detectors/pinch_detector_spec.rb +119 -0
  61. data/spec/lib/plugin/detectors/rotate_detector_spec.rb +125 -0
  62. data/spec/lib/plugin/detectors/swipe_detector_spec.rb +118 -0
  63. data/spec/lib/plugin/events/event_spec.rb +30 -0
  64. data/spec/lib/plugin/events/records/gesture_record_spec.rb +22 -0
  65. data/spec/lib/plugin/events/records/record_spec.rb +31 -0
  66. data/spec/lib/plugin/events/records/text_record_spec.rb +26 -0
  67. data/spec/lib/plugin/executors/command_executor_spec.rb +57 -0
  68. data/spec/lib/plugin/executors/executor_spec.rb +160 -0
  69. data/spec/lib/plugin/filters/filter_spec.rb +92 -0
  70. data/spec/lib/plugin/filters/libinput_filter_spec.rb +120 -0
  71. data/spec/lib/plugin/inputs/input_spec.rb +70 -0
  72. data/spec/lib/plugin/inputs/libinput_command_input_spec.rb +120 -0
  73. data/spec/lib/plugin/inputs/timer_input_spec.rb +40 -0
  74. data/spec/lib/plugin/manager_spec.rb +27 -0
  75. data/spec/lib/plugin/parsers/parser_spec.rb +45 -0
  76. data/spec/spec_helper.rb +20 -0
  77. metadata +91 -168
  78. data/.github/FUNDING.yml +0 -8
  79. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
  80. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
  81. data/.github/pull_request_template.md +0 -9
  82. data/.github/stale.yml +0 -18
  83. data/.gitignore +0 -17
  84. data/.reek.yml +0 -96
  85. data/.rspec +0 -2
  86. data/.rubocop.yml +0 -33
  87. data/.rubocop_todo.yml +0 -40
  88. data/.travis.yml +0 -11
  89. data/CHANGELOG.md +0 -419
  90. data/CODE_OF_CONDUCT.md +0 -74
  91. data/CONTRIBUTING.md +0 -72
  92. data/Gemfile +0 -6
  93. data/Rakefile +0 -15
  94. data/fusuma.gemspec +0 -38
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './text_record.rb'
3
+ require_relative './text_record'
4
4
 
5
5
  module Fusuma
6
6
  module Plugin
@@ -9,19 +9,22 @@ module Fusuma
9
9
  # Gesture Record
10
10
  class GestureRecord < Record
11
11
  # define gesture format
12
- attr_reader :status, :gesture, :finger, :direction
12
+ attr_reader :status, :gesture, :finger, :delta
13
13
 
14
- Delta = Struct.new(:move_x, :move_y, :zoom, :rotate)
14
+ Delta = Struct.new(:move_x, :move_y,
15
+ :unaccelerated_x, :unaccelerated_y,
16
+ :zoom, :rotate)
15
17
 
16
18
  # @param status [String]
17
19
  # @param gesture [String]
18
20
  # @param finger [String, Integer]
19
- # @param direction [Delta, NilClass]
20
- def initialize(status:, gesture:, finger:, direction:)
21
+ # @param delta [Delta, NilClass]
22
+ def initialize(status:, gesture:, finger:, delta:)
23
+ super()
21
24
  @status = status
22
25
  @gesture = gesture
23
26
  @finger = finger.to_i
24
- @direction = direction
27
+ @delta = delta
25
28
  end
26
29
  end
27
30
  end
@@ -8,36 +8,68 @@ module Fusuma
8
8
  # have index
9
9
  class IndexRecord < Record
10
10
  # define gesture format
11
- attr_reader :index
12
- attr_reader :position
11
+ attr_accessor :index
12
+ attr_reader :position, :trigger, :args
13
13
 
14
14
  # @param [Config::Index] index
15
15
  # @param [Symbol] position [:prefix, :body, :surfix]
16
- def initialize(index:, position: :body)
16
+ # @param [Symbol] trigger [:oneshot, :repeat]
17
+ def initialize(index:, position: :body, trigger: :oneshot, args: {})
18
+ super()
17
19
  @index = index
18
20
  @position = position
21
+ @trigger = trigger
22
+ @args = args
19
23
  end
20
24
 
21
25
  def type
22
26
  :index
23
27
  end
24
28
 
29
+ # FIXME: move to Config::Index
25
30
  # @param records [Array<IndexRecord>]
26
- # @return [IndexRecord]
27
- def merge(records:)
31
+ # @return [IndexRecord] when merge is succeeded
32
+ # @return [NilClass] when merge is not succeeded
33
+ def merge(records:, index: @index)
34
+ # FIXME: cache
28
35
  raise "position is NOT body: #{self}" unless mergable?
29
36
 
30
- @index = records.reduce(@index) do |merged_index, record|
31
- case record.position
32
- when :prefix
33
- Config::Index.new([*record.index.keys, *merged_index.keys])
34
- when :surfix
35
- Config::Index.new([*merged_index.keys, *record.index.keys])
36
- else
37
- raise "invalid index position: #{record}"
37
+ if records.empty?
38
+ if Config.find_execute_key(index)
39
+ @index = index
40
+ return self
38
41
  end
42
+ return nil
43
+ end
44
+
45
+ record = records.shift
46
+ new_index = case record.position
47
+ when :surfix
48
+ Config::Index.new([*index.keys, *record.index.keys])
49
+ else
50
+ raise "invalid index position: #{record}"
51
+ end
52
+
53
+ return unless exist_on_conf?(new_index)
54
+
55
+ merge(records: records, index: new_index)
56
+ end
57
+
58
+ # @param [Config::Searcher] searcher
59
+ def exist_on_conf?(index = @index)
60
+ Config.search(index)
61
+ end
62
+
63
+ # @return [Integer]
64
+ def trigger_priority
65
+ case @trigger
66
+ when :oneshot
67
+ 10
68
+ when :repeat
69
+ 100
70
+ else
71
+ 1000
39
72
  end
40
- self
41
73
  end
42
74
 
43
75
  def mergable?
@@ -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,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './record.rb'
3
+ require_relative './record'
4
4
 
5
5
  module Fusuma
6
6
  module Plugin
@@ -10,6 +10,7 @@ module Fusuma
10
10
  class TextRecord < Record
11
11
  # @param text [String]
12
12
  def initialize(text)
13
+ super()
13
14
  @text = text
14
15
  end
15
16
 
@@ -1,22 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './executor.rb'
3
+ require 'posix/spawn'
4
+ require_relative './executor'
4
5
 
5
6
  module Fusuma
6
7
  module Plugin
7
8
  module Executors
8
9
  # Exector plugin
9
10
  class CommandExecutor < Executor
11
+ # Executor parameter on config.yml
12
+ # @return [Array<Symbol>]
13
+ def execute_keys
14
+ [:command]
15
+ end
16
+
10
17
  def execute(event)
11
18
  search_command(event).tap do |command|
12
19
  break unless command
13
20
 
14
- MultiLogger.info(command: command)
15
- pid = fork do
16
- Process.daemon(true)
17
- exec(command.to_s)
18
- end
21
+ MultiLogger.info(command: command, args: event.record.args)
22
+
23
+ additional_env = event.record.args
24
+ .deep_transform_keys(&:to_s)
25
+ .deep_transform_values { |v| (v * args_accel(event)).to_s }
19
26
 
27
+ pid = POSIX::Spawn.spawn(additional_env, command.to_s)
20
28
  Process.detach(pid)
21
29
  end
22
30
  end
@@ -33,6 +41,13 @@ module Fusuma
33
41
  command_index = Config::Index.new([*event.record.index.keys, :command])
34
42
  Config.search(command_index)
35
43
  end
44
+
45
+ # @param event [Event]
46
+ # @return [Float]
47
+ def args_accel(event)
48
+ accel_index = Config::Index.new([*event.record.index.keys, :accel])
49
+ (Config.search(accel_index) || 1).to_f
50
+ end
36
51
  end
37
52
  end
38
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
@@ -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,57 @@
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
+ warn "stop process: #{i.class.name.underscore}"
29
+ Process.kill(:SIGKILL, i.pid)
30
+ end
31
+ exit 1
32
+ rescue StandardError => e
33
+ warn "#{input.class.name}: #{e}"
34
+ exit 1
35
+ end
36
+
37
+ input.create_event(record: line)
38
+ end
39
+
40
+ # @return [Integer]
41
+ def pid
13
42
  raise NotImplementedError, "override #{self.class.name}##{__method__}"
14
43
  end
15
44
 
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
45
+ # @return [IO]
46
+ def io
47
+ raise NotImplementedError, "override #{self.class.name}##{__method__}"
48
+ end
49
+
50
+ # @return [Event]
51
+ def create_event(record: 'dummy input')
52
+ e = Events::Event.new(tag: tag, record: record)
53
+ MultiLogger.debug(input_event: e)
54
+ e
20
55
  end
21
56
 
22
57
  def tag
@@ -26,3 +61,24 @@ module Fusuma
26
61
  end
27
62
  end
28
63
  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,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')